Sinatra風にASP.NET(F# 編)
前回 id:fits:20100920 作成したサンプルを F# で実装しなおしてみた。
(ASP.NET で F# を使う設定に関しては id:fits:20100906 参照)
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20100923/
Get, Post メソッドの定義
C# では拡張メソッドを使って実装していた Get, Post メソッドを F# の型拡張で。
そして、各種インターフェースの実装を F# のオブジェクト式で実装。
CustomRouting.fs
namespace Fits.Sample.Web.Routing open System open System.Web open System.Web.Routing module CustomRouting = //HttpApplication に型拡張を適用 type HttpApplication with member this.Get(pattern: string, proc: Func<RequestContext, string>) = this.Action("GET", pattern, proc) member this.Post(pattern: string, proc: Func<RequestContext, string>) = this.Action("POST", pattern, proc) member this.Action(methodType: string, pattern: string, proc: Func<RequestContext, string>) = let r = new Route(pattern, { //IRouteHandler インターフェースの実装 new IRouteHandler with member this.GetHttpHandler(rctx: RequestContext) = { //IHttpHandler インターフェースの実装 new IHttpHandler with member this.IsReusable = true member this.ProcessRequest(hctx: HttpContext) = let res = proc.Invoke(rctx) hctx.Response.Write(res) } }) //httpMethod の制約を設定 let rv = RouteValueDictionary() rv.Add("httpMethod", new HttpMethodConstraint(methodType)) r.Constraints <- rv //ルーティングの追加 RouteTable.Routes.Add(r)
上記ファイルをビルドして、bin ディレクトリに DLL を配置しておきます。
ビルド
>fsc --target:library --out:bin\Fits.Sample.Web.dll CustomRouting.fs
なお、F# のホームディレクトリにある v4.0\bin 内の fsc.exe でビルドしなければならない点に注意。(ホームディレクトリ直下の bin\fsc.exe を使用すると System.Web.Routing が参照できない)
Global.asax の定義
前回と同等の Global.asax を F# で実装します。今回は実行時に Global.asax.fs がビルドされるように Codebehind 属性の代わりに CodeFile 属性を使いました。
Global.asax
<%@ Application CodeFile="Global.asax.fs" Inherits="Fits.Sample.Web.Global" Language="F#" %>
基本的に C# 版と同等の実装ですが、型拡張を行うために CustomRouting を「名前空間.モジュール名」で open している点と RouteData.Values から値を取得する際に ".[キー名]" としている点に注意。
Global.asax.fs
namespace Fits.Sample.Web open System open System.Web //型拡張の適用(名前空間.モジュール名) open Fits.Sample.Web.Routing.CustomRouting type Global() = inherit HttpApplication() member this.Application_Start(sender: Object, e: EventArgs) = // test/:index への POST に対する定義 //(例) "test/1" への POST で WebForm1.aspx にリダイレクト this.Post("test/{index}", fun ctx -> ctx.HttpContext.Response.Redirect("/WebForm1.aspx") null ) // /:name/:index への GET に対する定義 //(例) "test/1" への GET で "hello test - 1" という文字列を表示 this.Get("{name}/{index}", fun ctx -> let pm = ctx.RouteData.Values sprintf "hello %O - %O" pm.["name"] pm.["index"] )
動作確認
WebDev.WebServer40.exe を使って動作確認できます。
ASP.NET 開発サーバー実行バッチ例(start_webserver.bat)
WebDev.WebServer40.exe /port:8080 /path:%~d0%~p0