Nancyを使っていて、ViewとNancy間でデータのやり取りをしたくなりました。
何か良い方法がないかを調べたところ、NancyにはModelBindingやContent Negotiationという機能があったため、それらを試してみることにしました。
環境
- Windows7 x64
- Nancy 1.1
- 前回作成した、以下のプロジェクトテンプレートを使用
- HTMLのFormを使ってデータをPOST
- 今回は以下の入力欄に対してModelBindingやContent Negotiationを使う
- text
- textarea
- tel
- url
- date
- checkbox
- radio button
- 今回は以下の入力欄に対してModelBindingやContent Negotiationを使う
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
フォームへの入力
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点を追加します。
@inherits
を使用して、Nancyからの値(Model)を受け取る@using Nancy.ViewEngines.Razor;
を追加して、@Model
を利用可能にするvalue
やchecked
属性に、Modelのプロパティをセット- dateの場合は、string.Formatでフォーマット指定してセット
- radioの場合は、checked属性にboolを返すModelのプロパティをセット
似たような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
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:あまりきれいな形に見えないので、他の方法があるかもしれません