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