【F# + ASP.NET Web API】 JSONPでクロス ドメイン通信を行うには

◆ 参考

JSONPってなに?JSONP with ASP.NET Web API

◆ JSONP形式へ変換

namespace Sample.Web

open System
open System.IO
open System.Web
open System.Net
open System.Net.Http
open System.Net.Http.Formatting
open System.Net.Http.Headers
open System.Threading.Tasks

// JSONP形式へ変換
type JsonpMediaTypeFormatter() =
    inherit JsonMediaTypeFormatter()

    do
        base.SupportedMediaTypes.Add(JsonMediaTypeFormatter.DefaultMediaType)
        base.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"))
        base.MediaTypeMappings.Add(new UriPathExtensionMapping(
            "jsonp", JsonMediaTypeFormatter.DefaultMediaType))
    
    // コールバック・パラメータ(callback)
    let mutable _callbackQueryParameter = String.Empty
    member x.CallbackQueryParameter
        with get() = 
            if (_callbackQueryParameter.Equals(String.Empty)) then
                "callback"
            else
                _callbackQueryParameter
        and set v = _callbackQueryParameter <- v

    member val callback = String.Empty with get, set

    // URLにクエリ文字列「callback」が存在するか確認
    member x.IsJsonpRequest() =
        let test = HttpContext.Current.Request.HttpMethod

        // HTTP GET 以外の場合は、処理終了
        if (HttpContext.Current.Request.HttpMethod <> "GET") then
            false
        else
            // クエリ文字列「callback」を取得
            x.callback <- HttpContext.Current.Request.QueryString
                .[x.CallbackQueryParameter]            

            not(String.IsNullOrEmpty(x.callback))        

    member x.BaseWriteToStreamAsync(
        typeValue:Type, value:obj, writeStream:Stream
        ,content:HttpContent ,transportContext:TransportContext) =

        base.WriteToStreamAsync(
            typeValue, value, writeStream
            , content, transportContext).Wait()

    override x.WriteToStreamAsync(
        typeValue:Type, value:obj , writeStream:Stream 
        ,content:HttpContent ,transportContext:TransportContext) =

        if (x.IsJsonpRequest()) then
            // URLにクエリ文字列「callback」が存在する場合         
            Task.Factory.StartNew(fun () ->
                // JSONP形式 = "コールバック関数(JOSNデータ)" に変換
                use writer = new StreamWriter(writeStream)
                writer.Write(x.callback + "(")
                writer.Flush()
                x.BaseWriteToStreamAsync(typeValue, value
                    , writeStream, content, transportContext)
                writer.Write(")")
                writer.Flush()
            )            
        else
            base.WriteToStreamAsync(typeValue, value
                , writeStream, content, transportContext)

◆ Global.fs

namespace Sample.Web

open System
open System.Web
open System.Web.Mvc
open System.Web.Routing
open System.Web.Http
open System.Data.Entity

type Route = { controller : string
               action : string
               id : UrlParameter }

type MapHttpRouteSettings = { id : obj }

type Global() =
    inherit System.Web.HttpApplication() 

    static member RegisterGlobalFilters(filters:GlobalFilterCollection) =
        filters.Add(new HandleErrorAttribute())

    static member RegisterRoutes(routes:RouteCollection) =
        // 省略

    member this.Start() =
        AreaRegistration.RegisterAllAreas()
        
        // JSONP 形式変換を処理を適用
        let config = GlobalConfiguration.Configuration
        config.Formatters.Insert(0, new  JsonpMediaTypeFormatter())
        
        Global.RegisterGlobalFilters(GlobalFilters.Filters)
        Global.RegisterRoutes(RouteTable.Routes)

◆ Controller

namespace Sample.Web.Controllers

open System
open System.Web
open System.Web.Mvc
open System.Net.Http
open System.Web.Http
open System.Collections.Generic

// #r "System.Runtime.Serialization"
open System.Runtime.Serialization

// MongoDB 関連
open  MongoDB.Driver;
open  MongoDB.Driver.Builders
open  MongoDB.Bson
open  MongoDB.Bson.Serialization.Attributes

// JSONの型
[<DataContract>]
type SampleEntity() =
    [<BsonId>]
    member val _id = ObjectId.GenerateNewId() with get, set
    [<DataMember>]
    member val Name = String.Empty with get, set
    [<DataMember>]
    member val Age = Int32.MinValue with get, set
    
type SampleController() =
    inherit ApiController()

    member x.Get(name:string) = 
        // MongoDBへ接続
        let server = MongoServer.Create("mongodb://localhost/?safe=true")
        let db = server.GetDatabase("SampleDB")
        let collection = db.GetCollection<SampleEntity>("SampleTable")

        // 条件を指定して、ドキュメントを取得        
        let query = new QueryDocument();
        query.Add("Name", BsonString.Create(name))        
        |> ignore

        collection.Find(query)

◆ HTML(jQuery)

$(function () {
    $.ajax({            
        type: "GET",
        url: 'http://localhost:54465/api/Sample/Taro' + "?callback=?",
        data: {},
        dataType: 'jsonp',
        jsonp: 'jsoncallback',
        success: function (data) {
            //通信が成功した場合の処理 (JSONPのコールバック関数はこれが呼ばれます)
            alert('success');
        },
        error: function (data, status) {
            //通信終了時の処理
            alert('error');
        },
        complete: function (data) {
            //通信終了時の処理
            alert('complete');
        },
    });
});

【F# + ASP.NET Web API】 MongoDBへデータを挿入するには

◆ Controller

namespace FsWeb.Controllers

open System.Web
open System.Web.Mvc
open System.Net.Http
open System.Web.Http
// MongoDB 関連
open  MongoDB.Driver
open  MongoDB.Bson

type ValuesController() =
    inherit ApiController()
    
    member x.Post([<FromBody>] name:string, email:string) =
        // MongoDBへ接続
        let server = MongoServer.Create("mongodb://localhost/?safe=true")
        let db = server.GetDatabase("sample_db")        
        
        // 更新用データを作成
        let emement = [|new BsonElement("name", BsonString.Create(name));
                            new BsonElement("email", BsonString.Create(email))|]
        let user = new BsonDocument(emement)
        
        // コレクションを取得
        let users = db.GetCollection<BsonDocument>("users")        
        // データをを挿入
        users.Insert(user) |> ignore
        

【F# + ASP.NET Web API】 MongoDBからデータを取得するには

◆ 事前準備

・Nuget からOfficial MongoDB C# driver を導入

◆ Controller

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
// MongoDB 関連
open  MongoDB.Bson.Serialization.Attributes
open  MongoDB.Driver.Builders
open  MongoDB.Driver
open  MongoDB.Bson

// JSONの型
[<DataContract>]
type User() =
    [<BsonId>]
    member val _id = ObjectId.GenerateNewId() with get, set
    [<DataMember>]
    member val name = "" with get, set
    [<DataMember>]
    member val email = "" with get, set

type ValuesController() =
    inherit ApiController()
    member x.Get() =
        // MongoDBへ接続
        let server = MongoServer.Create("mongodb://localhost/?safe=true")
        let db = server.GetDatabase("sample_db")        
        // コレクションを取得
        let users = db.GetCollection<User>("users")
        // 条件を指定して、ドキュメントを取得
        let query = Query.And(Query.EQ("name", BsonString.Create("Jiro")))
        users.FindOne(query)

【F# + ASP.NET MVC】 例外発生時にカスタム・エラーページを表示するには

◆ Web.config

<configuration>
  <system.web>
    <!-- 
    カスタム・エラーページの設定
    defaultRedirect属性を指定することにより、
      1./Views/コントローラ名/defaultRedirect属性.cshtml
      2./Views/コントローラ名/defaultRedirect属性.cshtml
      3./Views/Shared/defaultRedirect属性.cshtml
      4./Views/Shared/defaultRedirect属性.cshtml
    順にビューを検索する。
    -->
    <customErrors mode="On" defaultRedirect="Error">
      <!-- カスタム・エラーページ で例外が発生した場合、ErrorLast.htm を表示-->
      <error statusCode="500" redirect="/ErrorLast.htm" />
    </customErrors>
  </system.web>

◆ Controller

namespace Sample.Controllers

open System.Web
open System.Web.Mvc
open System.Collections.Generic

type HomeController() =
    inherit Controller()

    // カスタム・エラーを有効化
    [<HandleError>]
    member this.Index () =
        // 例外を発生させる
        raise (System.Exception("例外が発生"))
        this.View() :> ActionResult

◆ /Views/Shared/Error.cshtml

<!DOCTYPE html>
<html>
<head>
    <title>Error</title>
</head>
<body>
    <div>
        <ul>
            <li>【例外名】
                @Html.Encode(Model.Exception.GetType().Name)</li>
            <li>【メッセージ】
                @Html.Encode(Model.Exception.Message)</li>
            <li>【発生元】
                @Html.Encode(Model.ControllerName)
                /@Html.Encode(Model.ActionName) アクション</li>
        </ul>
    </div>
</body>

【ASP.NET MVC】 HTMLヘルパー一覧

◆ View

@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <title>Index</title>    
</head>
<body>
    <div>
        <!--*********************************************************-->
        <!--form タグ ***********************************************-->
        <!--*********************************************************-->
        <!-- 開始 <form> タグを書き込みます。 -->
        @using (@Html.BeginForm() ) { }
        <form action="/" method="post"></form> 
        <!-- 指定したルートの開始 <form> タグを書き込みます。 -->
        @using (@Html.BeginRouteForm("Default") ) { }
        <form action="/" method="post"></form>
        

        <!--*********************************************************-->
        <!--input タグ **********************************************-->
        <!--*********************************************************-->
        <!-- テキストの input 要素を返します。 -->
        @Html.TextBox("txt1")
        <input id="txt1" name="txt1" type="text" value="" />

        <!-- HTML input 要素を返します。 -->
        @Html.Editor("edt1")
        <input class="text-box single-line" id="edt1" name="edt1"
            type="text" value="" />

        <!-- チェック ボックスの input 要素を返します。 -->
        @Html.CheckBox("chk1")
        <input id="chk1" name="name" type="checkbox" value="true" />
        <input name="chk1" type="hidden" value="false" />

        <!-- オプション ボタンの input 要素を返します。 -->
        @Html.RadioButton("rdb1", "item1")
        <input id="rdb1" name="rdb1" type="radio" value="item1" />

        <!-- 非表示の input 要素を返します。 -->
        @Html.Hidden("hdn1")
        <input id="hdn1" name="hdn1" type="hidden" value="" />

        <!-- パスワードの input 要素を返します。 -->
        @Html.Password("pwd1")
        <input id="pwd1" name="pwd1" type="password" />


        <!--*********************************************************-->
        <!--textarea タグ ******************************************-->
        <!--*********************************************************-->
        <!-- textarea 要素を返します。 -->
        @Html.TextArea("txa1")
        <textarea cols="20" id="txa1" name="txa1" rows="2"></textarea>


        <!--*********************************************************-->
        <!--select タグ *********************************************-->
        <!--*********************************************************-->
        <!-- 単一選択の select 要素を返します。 -->
        @Html.DropDownList("dlp1",
            (IEnumerable<SelectListItem>)ViewData["selectList"])
        <select id="dlp1" name="dlp1">
            <option value="itemValue1">item1</option>
            <option value="itemValue2">item2</option>
        </select>


        <!--*********************************************************-->
        <!--label タグ **********************************************-->
        <!--*********************************************************-->
        <!-- HTML label 要素を返します。 -->
        @Html.Label("lbl1")
        <label for="lbl1">lbl1</label>


        <!--*********************************************************-->
        <!--a タグ **************************************************-->
        <!--*********************************************************--> 
        <!-- アンカー要素 (a 要素) を返します。 -->
        @Html.ActionLink("linkText", "actionName")
        <a href="/Home/actionName">linkText</a>

        <!-- ルートのアンカー要素 (a 要素) を返します。 -->
        @Html.RouteLink("rtl1", "Default")
        <a href="/">rtl1</a>


        <!--*********************************************************-->
        <!--検証 ****************************************************-->
        <!--*********************************************************-->
        <!-- フィールドにエラーが存在する場合、検証メッセージを表示します。 -->
        @Html.ValidationMessage("Body")
        <span class="field-validation-valid" data-valmsg-for="Body"
             data-valmsg-replace="true"></span>

        <!-- 検証メッセージの順序なしのリスト (ul 要素) を返します。 -->
        @Html.ValidationSummary()
        <div class="validation-summary-valid" data-valmsg-summary="true">
            <ul><li style="display:none"></li></ul></div>


        <!--*********************************************************-->
        <!--etc. ****************************************************-->
        <!--*********************************************************-->
        <!-- 指定された子アクション メソッドを呼び出し、
             結果を HTML 文字列として返します。 -->
        @Html.Action("Test")
        Testメソッド結果

        <!-- ViewDataのプロパティを表示します。 -->
        @Html.Display("Message")
         "Hello F# + ASP.NET MVC!"

    </div>
</body>
</html>

◆ Controller

namespace Sample.Controllers

open System.Web
open System.Web.Mvc
open System.Collections.Generic

type HomeController() =
    inherit Controller()

    member this.Index () =
        // @Html.Display 用
        this.ViewData.["Message"] <-  "Hello F# + ASP.NET MVC!"
        
        // @Html.DropDownList 用
        let selectListItem1 = new SelectListItem();
        selectListItem1.Text <- "item1"
        selectListItem1.Value <- "itemValue1"
        let selectListItem2 = new SelectListItem();
        selectListItem2.Text <- "item2"
        selectListItem2.Value <- "itemValue2"
        // IEnumerable<SelectListItem> 形式へ変換
        this.ViewData.["selectList"]
            <- [| selectListItem1; selectListItem2 |]
        
        this.View() :> ActionResult

    // @Html.Action 用
    member this.Test() =
        "Testメソッド結果"