★Windows Store apps(JavaScript) + KnockoutJS + ASP.NET Web API(F#) による開発を行うには

◆ 事前準備

1. Windowsストア ・プロジェクト(JavaScript)を新規作成
2. Windowsストア ・プロジェクトに KnockoutJS を導入
3. Windowsストア ・プロジェクトに jQuery を導入
4. オンラインテンプレート "F# C# MVC 4" を元に ASP.NET Web API プロジェクトを追加。


◆ Windowsストア ・プロジェクト

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta charset="utf-8" />
    <title>Sample</title>

    <!-- WinJS 参照 -->
    <link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.1.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.1.0/js/ui.js"></script>

    <link href="/css/default.css" rel="stylesheet" />
    <script src="/js/default.js"></script>

    <!-- KnockoutJS & jQuery 参照 -->
    <script src="Scripts/knockout-2.1.0.debug.js"></script>
    <script src="Scripts/jquery-1.8.1.js"></script>    
</head>
<body>
    <button id="Exec">実行</button>
    <ul data-bind="template: { foreach: DataList }">
        <!-- KnockoutJS によるデータ・バインド -->
        <li><span data-bind="text: Id"></span></li>
    </ul>

    <script type="text/javascript">
        // ViewModel
        var data = [];
        var viewModel = {
            DataList: ko.observableArray(data)
        };
        ko.applyBindings(viewModel);

        $(function () {
            // 実行ボタン クリック処理
            $('#Exec').click(function () {
                // ASP.NET Web APIの呼び出し
                $.ajax({
                    url: 'http://localhost:59885/api/values',
                    dataType: 'json',
                    success: function (data) {
                        viewModel.DataList(data);
                    }
                });
            });
        });
    </script>
</body>
</html>


◆ ASP.NET Web API(F#) プロジェクト

namespace FsWeb.Controllers

open System.Web
open System.Web.Mvc
open System.Net.Http
open System.Web.Http
// #r "System.Runtime.Serialization"
open System.Runtime.Serialization

// データ型
[<DataContract>]  
type Model =  
    { [<DataMember>] Id : string }

type ValuesController() =
    inherit ApiController()
    
    // JSON形式で取得
    member x.Get()
        = [| { Id = "hoge1" }; { Id = "hoge2" }; { Id = "hoge3" } |]

【F# + ASP.NET MVC】 非同期処理にタイムアウトを指定するには

◆ Controller

namespace Sample.Controllers

open System.Web
open System.Web.Mvc

type HomeController() =
    inherit AsyncController()

    // 非同期処理(3秒でタイムアウト)
    [<AsyncTimeout(3000)>]
    member this.IndexAsync() =
        this.AsyncManager.OutstandingOperations.Increment() |> ignore
        
        // 非同期処理を実行
        async {
            // テスト用に3秒待機
            do! Async.Sleep(3000)

            this.AsyncManager.Parameters.["result"] <- "タイムアウト失敗"
            this.AsyncManager.OutstandingOperations.Decrement() |> ignore
        }
        |> Async.Start

    // 完了処理
    member this.IndexCompleted(result : string) =
        this.ViewData.["Html"] <- result
        this.View() :> ActionResult

【F# + ASP.NET MVC】 カスタム非同期コントローラーを作成するには(その2)

◆ 参考

Asynchronous Workflow Controller

◆ Controller

namespace Sample.Controllers

open Unchecked
open System
open System.Web.Mvc
open System.Web.Mvc.Async
open System.Net

exception PreserveStackTraceWrapper of exn

// カスタム非同期コントローラー
// そのままコピペ。。。後で詳細を調べてみよう。
type FSharpAsyncController() = 
    inherit AsyncController()

    override __.CreateActionInvoker() = 
        upcast {   
            new AsyncControllerActionInvoker() with
                member __.GetControllerDescriptor(controllerContext) =
                    let controllerType = controllerContext.Controller.GetType()
                    upcast {
                        new ReflectedControllerDescriptor(controllerType) with
                            member ctrlDesc.FindAction(controllerContext, actionName) =
                                let forwarder = base.FindAction(controllerContext, actionName) :?> ReflectedActionDescriptor
                                if (forwarder <> null && forwarder.MethodInfo.ReturnType = typeof<Async<ActionResult>>) then 
                                    let endAsync' = ref (defaultof<IAsyncResult -> Choice<ActionResult, exn>>)
                                    upcast {
                                        new AsyncActionDescriptor() with
                                            member actionDesc.ActionName = forwarder.ActionName
                                            member actionDesc.ControllerDescriptor = upcast ctrlDesc
                                            member actionDesc.GetParameters() = forwarder.GetParameters()
                                            member actionDesc.BeginExecute(controllerContext, parameters, callback, state) =
                                                let asyncWorkflow = 
                                                    forwarder.Execute(controllerContext, parameters) :?> Async<ActionResult>
                                                    |> Async.Catch
                                                let beginAsync, endAsync, _ = Async.AsBeginEnd(fun () -> asyncWorkflow)
                                                endAsync' := endAsync
                                                beginAsync((), callback, state)
                                            member actionDesc.EndExecute(asyncResult) =
                                                match endAsync'.Value(asyncResult) with
                                                    | Choice1Of2 value -> box value
                                                    | Choice2Of2 why -> raise <| PreserveStackTraceWrapper(why)
                                    } 
                                else 
                                    upcast forwarder 
                    } 
        }

type HomeController() = 
    // カスタム非同期コントローラーを継承
    inherit FSharpAsyncController()
    // 非同期処理
    member this.Index() = 
        let v = this.View()
        async {
            // WebページのHTMLを取得
            let wc = new WebClient()
            let! html = wc.AsyncDownloadString(
                            new Uri("http://www.google.co.jp"))
            // 非同期処理の結果をセット
            this.ViewData.["Html"] <- html
            return v :> ActionResult
        }
                

【F# + ASP.NET MVC】 カスタム非同期コントローラーを作成するには

◆ 参考

Asynchronous Controller Helper

◆ Controller

namespace Sample.Controllers

open System
open System.Web
open System.Web.Mvc
open System.Net

// 非同期ユーティリティ
type AsyncActionBuilder(asyncMgr : Async.AsyncManager) = 
    // 'async' operations
    member this.Bind(v, f) = async.Bind(v, f)
    member this.Return(v) = async.Return(v)    
    (* omit
    member this.Combine(a, b) = async.Combine(a, b)
    member this.Delay(f) = async.Delay(f)
    member this.For(s, f) = async.For(s, f)
    member this.ReturnFrom(a) = async.ReturnFrom(a)
    member this.TryFinally(a, b) = async.TryFinally(a, b)
    member this.TryWith(a, b) = async.TryWith(a, b)
    member this.Using(r, f) = async.Using(r, f)
    member this.While(c, f) = async.While(c, f)
    member this.Zero() = async.Zero()
    omit *)

    member this.Run(workflow) = 
        // 保留中の操作数を1増やす
        asyncMgr.OutstandingOperations.Increment() |> ignore
        // 非同期処理を実行
        async {        
            let! res = workflow        
            // 処理結果をコレクションに追加すると、
            // Completedメソッドの引数に自動的にバインドされる。
            asyncMgr.Parameters.["result"] <- res
            asyncMgr.OutstandingOperations.Decrement() |> ignore
        }
        |> Async.Start

// カスタム非同期コントローラー
type FSharpAsyncController() = 
    inherit AsyncController()
    member this.AsyncAction = 
        new AsyncActionBuilder(this.AsyncManager)

type HomeController() =
    // カスタム非同期コントローラーを継承
    inherit FSharpAsyncController()
    // 非同期処理    
    member this.IndexAsync() = this.AsyncAction {
        // WebページのHTMLを取得
        let wc = new WebClient()
        let! html = wc.AsyncDownloadString(
                        new Uri("http://www.google.co.jp"))
        return html
    }

    // 完了処理(AsyncManager.Parameters が自動的にバインドされる)
    member this.IndexCompleted(result : string) =
        // 非同期処理の結果をセット
        this.ViewData.["Html"] <- result
        this.View() :> ActionResult

【F# + ASP.NET MVC】 非同期処理を行うには

◆ Controller

namespace Sample.Controllers

open System
open System.Web
open System.Web.Mvc
open System.Net

type HomeController() =
    inherit AsyncController()

    // 非同期処理
    member this.IndexAsync() =
        // 保留中の操作数を1増やす
        this.AsyncManager.OutstandingOperations.Increment() |> ignore
        // 非同期処理を実行
        async {
            // WebページのHTMLを取得
            let wc = new WebClient()
            let! html = wc.AsyncDownloadString(
                            new Uri("http://www.google.co.jp"))

            // 処理結果をコレクションに追加すると、
            // Completedメソッドの引数に自動的にバインドされる。
            this.AsyncManager.Parameters.["result"] <- html
            // 保留中の操作数を1減らす
            this.AsyncManager.OutstandingOperations.Decrement() |> ignore
        }
        |> Async.Start

    // 完了処理(AsyncManager.Parameters が自動的にバインドされる)
    member this.IndexCompleted(result : string) =
        // 非同期処理の結果をセット
        this.ViewData.["Html"] <- result
        this.View() :> ActionResult

【F# + ASP.NET MVC】 複数の入力ソースからリクエスト・データを取得するには

◆ Controller

namespace Sample.Controllers

open System.Web
open System.Web.Mvc

type HomeController() =
    inherit Controller()
    member this.Index (id:string) =
        (* ValueProvider プロパティにより、複数の入力ソース(※)から取得可能。
            【取得可能データ(優先順位順)】
               ①子アクションの値(Controllerのメソッド引数)
               ②フォームデータ
               ③ルートデータ
               ④クエリ文字列
               ⑤送信ファイル                            
                 ※ IValueProvider を実装したカスタム・プロバイダーにより
                    Cookie等を追加することも可能
        *)
        // 子アクションの値
        let actionParam = this.ValueProvider.GetValue("id").AttemptedValue        
        this.View() :> ActionResult

    [<HttpPost>]
    member this.Index() =
        // フォームデータ
        let formParam = this.ValueProvider.GetValue("fm").AttemptedValue
        this.View() :> ActionResult

    [<HttpPost>]
    member this.Upload(fl:HttpPostedFileBase) =
        // 送信ファイル
        let file = this.ValueProvider.GetValue("fl").RawValue
        this.View() :> ActionResult

◆ View

@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <title>Index</title>    
</head>
<body>
    <div>
        F# + ASP.NET MVC = Cool!
        @using (Html.BeginForm()) {
            
            @Html.Hidden("fm", "FormValue")
            <p><input type="submit" value="フォームデータ送信" /></p>         
        }

        @using (Html.BeginForm("Upload", "Home", FormMethod.Post
            , new { enctype = "multipart/form-data" }))
        {
            <input type="file" name="fl"/>
            <input type="submit" value="アップロード" />
        }
    </div>
</body>
</html>

【F# + ASP.NET MVC】 ルーティング・データを取得するには

◆ Controller

namespace Sample.Controllers

open System.Web
open System.Web.Mvc

type HomeController() =
    inherit Controller()
    member this.Index () =
        // ルーティング・データを取得
        let controllerName = this.RouteData.Values.["controller"]
        let actionName = this.RouteData.Values.["action"]

        this.View() :> ActionResult