C# + Nancyで、ModelBindingやContent Negotiationを使って、ViewとNancy間でデータを受け渡す

Nancyを使っていて、ViewとNancy間でデータのやり取りをしたくなりました。

何か良い方法がないかを調べたところ、NancyにはModelBindingやContent Negotiationという機能があったため、それらを試してみることにしました。

 

環境

  • Windows7 x64
  • Nancy 1.1
  • HTMLのFormを使ってデータをPOST
    • 今回は以下の入力欄に対してModelBindingやContent Negotiationを使う
      • text
      • textarea
      • tel
      • url
      • email
      • date
      • checkbox
      • radio button

 

ViewからNancyへデータを受け渡す場合

ModelBindingで使うクラスの作成

以下のようなクラスを用意します。なお、ラジオボタンの場合はvalue属性の値がViewから渡されるので、Nancyではstring型で受け取ります。

public class HomeModel
{
    public string Text { get; set; }
    public string TextArea { get; set; }
    public string Tel { get; set; }
    public string Url { get; set; }
    public string Email { get; set; }
    public DateTime Date { get; set; }
    public bool Checkbox { get; set; }

    // radio button
    // bind to `value` element
    public string RadioButtonValue { get; set; }
}

 

Viewの作成

name属性に、上記のクラスのプロパティ名と同じ値を設定するよう、Razor VIewを作ります。

なお、ラジオボタンの場合はvalue属性にNancyへ渡したい値を設定します。

<fieldset>
    <div>
        <label for="Text">Name:</label>
        <input type="text" name="Text"/>
    </div>

    <div>
        <label for="TextArea">TextArea:</label>
        <textarea name="TextArea"></textarea>
    </div>

    <div>
        <label for="Tel">Tel:</label>
        <input type="tel" name="Tel"/>
    </div>

    <div>
        <label for="Url">Url:</label>
        <input type="url" name="Url"/>
    </div>

    <div>
        <label for="Email">Email:</label>
        <input type="email" name="Email"/>
    </div>

    <div>
        <label for="Date">Date:</label>
        <input type="date" name="Date"/>
    </div>

    <div>
        <label for="RememberMe">Checkbox:</label>
        <input type="checkbox" name="Checkbox"/>
    </div>

    <div>
        <label for="Status">RadioButtonValue:</label>
        <label for="radio_good">Good</label>
        <input type="radio" name="RadioButtonValue" value="good" id="radio_good"/>
        <label for="rad_normal">Normal</label>
        <input type="radio" name="RadioButtonValue" value="normal" id="rad_normal"/>
        <label for="rad_bad">Bad</label>
        <input type="radio" name="RadioButtonValue" value="bad" id="rad_bad"/>
    </div>

    <div>
        <button type="submit">Submit</button>
    </div>
</fieldset>

 

Moduleの作成

GETで上記のRazor Viewを表示し、POSTで入力結果をJSON表示するModuleを作ります。

public HomeModule()
{
    Get["/vtom"] = _ => View["v_to_m", new HomeModel()];

    Post["/vtom"] = _ =>
    {
        // ModelBinding
        var model = this.Bind<HomeModel>();

        return Response.AsJson<HomeModel>(model);
    };
}

 

実行結果

ViewからNancyへModelBindingできました。

GET

f:id:thinkAmi:20150404185329p:plain

 

フォームへの入力

f:id:thinkAmi:20150404185336p:plain

 

POST
{
    "checkbox": true, 
    "date": "2015-04-01T00:00:00.0000000+09:00", 
    "email": "fuga@example.com", 
    "isBad": true, 
    "isGood": false, 
    "isNormal": false, 
    "radioButtonValue": "bad", 
    "tel": "1234-56-7890", 
    "text": "hoge", 
    "textArea": "hello world", 
    "url": "http://example.com"
}

 

NancyからViewへデータを受け渡す場合

今度は逆に、NancyからViewへContent Negotiationを使ってデータを受け渡してみます。

 

ModelBindingで使うクラスへの追加

ラジオボタンの初期選択を設定する場合、checked属性へはcheckedというテキストではなく、boolを渡す必要があります。

そのため、各ラジオボタンごとにboolを返すプロパティを用意します*1
asp.net - NancyFx repopulate radio button after server side validation - Stack Overflow

public class HomeModel
{
    ...
    // bind to `checked` element
    public bool IsGood { get { return RadioButtonValue == "good" ? true : false; } }
    public bool IsNormal { get { return RadioButtonValue == "normal" ? true : false; } }
    public bool IsBad { get { return RadioButtonValue == "bad" ? true : false; } }
}

 

Viewへの追加

大きく分けて3点を追加します。

 
似たようなinput typeは省略しましたが、だいたい以下の様な感じになります。

@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<ModelBinding.HomeModel>
@using Nancy.ViewEngines.Razor;
...
<body>
    <form method="post" action="/">
        <fieldset>
            <div>
                <label for="Text">Text:</label>
                <input type="text" name="Text" value="@Model.Text" />
            </div>
            
            <div>
                <label for="TextArea">TextArea:</label>
                <textarea name="TextArea">@Model.TextArea</textarea>
            </div>
            ...
            <div>
                <label for="Date">Date:</label>
                <input type="date" name="Date" value="@string.Format("{0:yyyy-MM-dd}", @Model.Date)" />
            </div>

            <div>
                <label for="RememberMe">Checkbox:</label>
                <input type="checkbox" name="Checkbox" checked="@Model.Checkbox" />
            </div>

            <div>
                <label for="Status">RadioButtonValue:</label>
                <label for="radio_good">Good</label>
                <input type="radio" name="RadioButtonValue" value="good" id="radio_good" checked="@Model.IsGood" />
                <label for="rad_normal">Normal</label>
                <input type="radio" name="RadioButtonValue" value="normal" id="rad_normal" checked="@Model.IsNormal" />
                <label for="rad_bad">Bad</label>
                <input type="radio" name="RadioButtonValue" value="bad" id="rad_bad" checked="@Model.IsBad" />
            </div>
            ...

 

Moduleに追加

初期値を入れるGETと、結果をJSONで表示するPOSTを追加します。

Get["/mtov"] = _ =>
{
    var model = new HomeModel()
    {
        Text = "piyo",
        TextArea = "hello razor",
        Tel = "987-6543-210",
        Url = "https://example.com",
        Email = "hogehoge@example.com",
        Date = DateTime.Parse("2015/1/1"),
        Checkbox = true,
        RadioButtonValue = "good"
    };

    return Negotiate
        .WithView("m_to_v")
        .WithModel(model);
};

Post["/mtov"] = _ => Response.AsJson<HomeModel>(this.Bind<HomeModel>());

 

実行結果
GET

f:id:thinkAmi:20150404185354p:plain

POST
{
    "checkbox": true, 
    "date": "2015-01-01T00:00:00.0000000+09:00", 
    "email": "hogehoge@example.com", 
    "isBad": false, 
    "isGood": true, 
    "isNormal": false, 
    "radioButtonValue": "good", 
    "tel": "987-6543-210", 
    "text": "piyo", 
    "textArea": "hello razor", 
    "url": "https://example.com"
}

 

ソースコード

GitHubに上げました。
thinkAmi-sandbox/NancyModelBinding-sample

*1:あまりきれいな形に見えないので、他の方法があるかもしれません