新年前の最後のブログは、Nancy の ModelBinding に任せることにしました
相変わらず、分かりやすく比較しやすいように MVC と組み合わせました
MVC の簡単な ModelBinding を見てみましょうまずは
1 // POST: Authors/Create 2 // To protect from overposting attacks, please enable the specific properties you want to bind to, for 3 // more details see http://go.microsoft.com/fwlink/?LinkId=317598. 4 [HttpPost] 5 [ValidateAntiForgeryToken] 6 public ActionResult Create([Bind(Include = "AuthorId,AuthorName,AuthorGender,AuthorEmail,AuthorAddress,AuthorPhone")] Author author) 7 { 8 if (ModelState.IsValid) 9 {10 db.Authors.Add(author);11 db.SaveChanges();12 return RedirectToAction("Index");13 }14 return View(author);15 }
上記のコードは、ModelBinding を使用する次のタイプのコントローラーを使用して私が生成した追加メソッドです
このような比較的単純なモデル バインディングは、誰もがよく知っているはずです。
おそらくあなたはすでにそれを暗記しているでしょう。
MVC のモデル バインディングの詳細な説明については、以下を参照してください。非常に詳細なので、詳しく説明しません
[ASP.NET MVC Mavericks Road] 15 - モデル バインディング
ModelBinder— —ASP.NET MVC のコア モデル バインディング
Nancy のモデル バインディングを見てみましょう。
最初に具体的な例を見てみましょう。この例に従ってこのセクションの内容を説明しましょう
この例で使用する参照は Nancy、Nancy.Hosting.Aspnet です
まずその内容を見てみましょう。デフォルトのバインディングを決定します
まず従業員モデルを作成します
このモデルでは、いくつかのフィールドにデフォルト値を設定します。Nancy でデフォルトの ModelBinding をテストするためのビュー default.html を作成します
1 public class Employee 2 { 3 public Employee() 4 { 5 this.EmployeeNumber = "Num1"; 6 this.EmployeeName = "Catcher8"; 7 this.EmployeeAge = 18; 8 } 9 public string EmployeeNumber { get; set; }10 public string EmployeeName { get; set; }11 public int EmployeeAge { get; set; }12 public List<string> EmployeeHobby { get; set; }13 }
説明を減らすために、多くのコメントを追加しました
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>default</title> 5 <meta charset="utf-8" /> 6 </head> 7 <body> 8 <form action="/default" method="post"> 9 <label>员工编号</label>10 <input type="text" name="EmployeeNumber" /> <br />11 <label>员工姓名</label>12 <input type="text" name="EmployeeName" /> <br />13 <label>员工年龄</label>14 <input type="text" name="EmployeeAge" /> <br />15 16 <input type="checkbox" name="EmployeeHobby" value="篮球" />篮球17 <input type="checkbox" name="EmployeeHobby" value="足球" />足球18 <input type="checkbox" name="EmployeeHobby" value="排球" />排球19 <input type="checkbox" name="EmployeeHobby" value="网球" />网球20 <br />21 <input type="submit" value="提交" />22 </form>23 </body>24 </html>
これらの内容をフォームに入力し、上記の各値の変化を監視します
従業員_Using_Bind のバインドが間違っていることがわかります。これを書いているので、コメントに正しい書き方を記載しました。
employee_Empty、employee_Using_BindWithTModel、employee_Using_BindingTo、employee_Using_BindingToWithTModel
これらはすべて、最終的には同じ効果になります。 !ここで「final」と言ったのは、employee_Empty が最初にインスタンス化されるときに、それが設定したデフォルト値になるはずだからです。
employee_Using_BindAndBlacklistStyle1 およびemployee_Using_BindAndBlacklistStyle2 には、Bind の後にパラメーターがあります。
これらのパラメーターは、バインド時に無視される、いわゆるブラックリストです。その結果、設定したデフォルト値の Catcher8 と 18 が得られます。
なぜこのようにバインドできるのかについては、カスタム バインディングを確認するとより明確になるかもしれません。
次のステップは、カスタム バインディング メソッドを使用することです:
今はまだ Employee.cs モデルを使用しています
新しいビュー Custom.html がここに追加されます。これは、基本的に以前の default.html と同じです。別のアクション
1 public class TestModule : NancyModule 2 { 3 public TestModule() 4 { 5 Get["/default"] = _ => 6 { 7 return View["default"]; 8 }; 9 Post["/default"] = _ =>10 {11 Employee employee_Empty = new Employee();12 //这种写法有问题,应该是 Employee xxx = this.Bind(); 才对!13 //因为这里的this.Bind() 是 dynamic 类型,没有直接指明类型14 //所以它会提示我们 “找不到此对象的进一步信息”15 var employee_Using_Bind = this.Bind();16 17 //这里在bind的时候指明了类型。这个会正常绑定数据。(推荐这种写法)18 var employee_Using_BindWithTModel = this.Bind<Employee>();19 //这里是将数据绑定到我们实例化的那个employee_Empty对象20 //运行到这里之后,会发现employee_Empty的默认值被替换了!!21 var employee_Using_BindTo = this.BindTo(employee_Empty);22 //与上面的写法等价!23 var employee_Using_BindToWithTModel = this.BindTo<Employee>(employee_Empty);24 //这个主要是演示“黑名单”的用法,就是绑定数据的时候忽略某几个东西25 //这里忽略了EmployeeName和EmployeeAge,所以得到的最终还是我们设置的默认值26 var employee_Using_BindAndBlacklistStyle1 = this.Bind<Employee>(e=>e.EmployeeName,e=>e.EmployeeAge);27 //与上面的写法等价,演示不同的写法而已! 28 var employee_Using_BindAndBlacklistStyle2 = this.Bind<Employee>("EmployeeName", "EmployeeAge");29 return Response.AsRedirect("/default");30 }; 31 }32 }
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>custom</title> 5 <meta charset="utf-8" /> 6 </head> 7 <body> 8 <form action="/custom" method="post"> 9 <label>员工编号</label>10 <input type="text" name="EmployeeNumber" /> <br />11 <label>员工姓名</label>12 <input type="text" name="EmployeeName" /> <br />13 <label>员工年龄</label>14 <input type="text" name="EmployeeAge" /> <br />15 <input type="checkbox" name="EmployeeHobby" value="篮球" />篮球16 <input type="checkbox" name="EmployeeHobby" value="足球" />足球17 <input type="checkbox" name="EmployeeHobby" value="排球" />排球18 <input type="checkbox" name="EmployeeHobby" value="网球" />网球19 <br />20 <input type="submit" value="提交" />21 </form>22 </body>23 </html>
1 public class MyModelBinder : IModelBinder 2 { 3 public bool CanBind(Type modelType) 4 { 5 return modelType == typeof(Employee); 6 } 7 public object Bind(NancyContext context, Type modelType, object instance, BindingConfig configuration, params string[] blackList) 8 { 9 var employee = (instance as Employee) ?? new Employee();10 employee.EmployeeName = context.Request.Form["EmployeeName"] ?? employee.EmployeeName;11 employee.EmployeeNumber = context.Request.Form["EmployeeNumber"] ?? employee.EmployeeNumber;12 employee.EmployeeAge = 24;//我们把年龄写死,方便看见差异 13 employee.EmployeeHobby = ConvertStringToList(context.Request.Form["EmployeeHobby"]) ?? employee.EmployeeHobby;14 return employee;15 }16 17 private List<string> ConvertStringToList(string input)18 {19 if (string.IsNullOrEmpty(input))20 {21 return null;22 }23 var items = input.Split(',');24 return items.AsEnumerable().ToList<string>();25 }26 }
これらの内容をフォームに入力し、employee1 とemployee2 に同時に監視を追加します
明らかにわかります, カスタマイズされたバインダーが有効になり、年齢は設定した 24 歳になりました。
Nancy では、さらに便利なのは、json と xml もバインドできることです。この 2 つは非常に似ているため、ここでは json のみを紹介します。
同様に、例がすべてを物語っています。
json.html ビューを追加します
1 Get["/custom"] = _ => 2 { 3 return View["custom"]; 4 }; 5 Post["/custom"] = x => 6 { 7 //此时就会调用我们自己定义的Binder了 8 var employee1 = this.Bind<Employee>(); 9 Employee employee2 = this.Bind(); 10 return Response.AsRedirect("/custom");11 };
もう一つ注意点があります。参照される js ファイルについては、規約設定を記述したくない場合は、js を content フォルダーに置くだけです。詳細については、以前の bolgNancy の静的ファイル処理を参照してください。
そうでない場合は、「$ が定義されていません」というエラーが表示されます
次に、TestModule.cs に次のコードを追加します
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>default</title> 5 <meta charset="utf-8" /> 6 7 <script src="../../content/jquery-1.10.2.min.js"></script> 8 <script type="text/javascript"> 9 $(document).ready(function(){ 10 var dat = "{\"EmployeeName\":\"catcher1234\", \"EmployeeAge\":\"33\"}";11 $.ajax({12 type: "POST",13 url: "/json",14 contentType: "application/json",15 data: dat,16 success: function (data) {17 alert("Response:\n" + data);18 }19 });20 }); 21 </script>22 </head>23 <body> 24 </body>25 </html>
実行して効果を確認してください
監視している内容を見てみましょう! !
非常に素晴らしい、まさに私たちが望むものです。数値には値が割り当てられておらず、デフォルト値が自動的に取得されます。
いつものように、この作品のソースコードを簡単に分析してみましょう。
ModelBinding は Nancy プロジェクトの下にあり、内部のコンテンツは次のとおりです:
もちろん、最初に DefaultBinder.cs を確認する必要があります。すべてのデフォルト実装では、Nancy には Default という単語が含まれるためです。
DefaultBinder は IBinder インターフェイスを実装します。 、このインターフェイスには 1 つだけあります: Bind
1 Get["/json"] = _ => 2 { 3 return View["json"]; 4 }; 5 Post["/json"] = _ => 6 { 7 var employee = this.Bind<Employee>(); 8 var sb = new StringBuilder(); 9 sb.AppendLine("绑定的employee的值:");10 sb.Append("编号: ");11 sb.AppendLine(employee.EmployeeNumber);12 sb.Append("姓名: ");13 sb.AppendLine(employee.EmployeeName);14 sb.Append("年龄: ");15 sb.AppendLine(employee.EmployeeAge.ToString()); 16 return sb.ToString();17 };
DefaultBinder の実装は
まずバインドされた型が配列コレクションであるかどうかを判断し、そうであれば 1 つの処理戦略を、そうでない場合は別の処理戦略を決定します。
内部の判断にはもう 1 つの重要な概念があります。この設定はバインド動作を変更できるため
ここでは、公式 Web サイトのドキュメントの画像を直接スクリーンショットして表示します
BodyOnly が true に設定されている場合、ボディがバインドされると、バインダーはすぐに停止します。
IgnoreErrors が false の場合、バインドは続行されません。 true の場合、デフォルト値は false です。
Overwrite为ture时,允许binder去覆盖我们设置的那些默认值,为false时,就是不允许,默认值是true!
DefaultBinder里面有个GetDataFields的私有方法
1 private IDictionary<string, string> GetDataFields(NancyContext context) 2 { 3 var dictionaries = new IDictionary<string, string>[] 4 { 5 ConvertDynamicDictionary(context.Request.Form), 6 ConvertDynamicDictionary(context.Request.Query), 7 ConvertDynamicDictionary(context.Parameters) 8 }; 9 return dictionaries.Merge();10 }
从中我们可以看出,它处理绑定的时候用到了字典,包含了表单的数据、url的参数,这点与mvc里面的基本一致!
所以我们在写页面的时候,我们只要把表单元素的name属性设置为对应的字段名就可以,同样的,这个在mvc中也一致
我们在mvc视图中用的 @Html.EditorFor之类的强类型绑定,生成的页面都是把name设置为字段名称!
下面看看ITypeConverter这个接口
1 public interface ITypeConverter2 { 3 bool CanConvertTo(Type destinationType, BindingContext context);4 5 object Convert(string input, Type destinationType, BindingContext context);6 }
这个接口提供了一种转换类型的方法
CanConvertTo 是判断是否能转化为目的类型,
Convert才是真正的转化!
Nancy默认的Converters包含了Collection、DateTime、Fallback和Numeric
当然,有了这个接口,我们可以实现更多的拓展,怎么用着方便怎么来!
当然不能忘了我们自定义模型绑定用到的接口 IModelBinder
1 public interface IModelBinder : IBinder2 {3 bool CanBind(Type modelType);4 }
IModerBinder这个接口除了自己定义的CanBind方法外,还继承了IBinder这个接口,所以我们自定义ModelBinder的时候只需要
实现这个接口就可以了。作用就是绑定数据到相应的模型中。
我们前面自定义就是用的这个,把数据绑定到了Employee中。
最后就讲讲“黑名单”的内容!
“黑名单”的实现,还用到了DynamicModelBinderAdapter这个东西,但最为主要的是
DefaultBinder里面的CreateBindingContext这个私有方法!
1 private BindingContext CreateBindingContext(NancyContext context, Type modelType, object instance, BindingConfig configuration, IEnumerable<string> blackList, Type genericType) 2 { 3 return new BindingContext 4 { 5 Configuration = configuration, 6 Context = context, 7 DestinationType = modelType, 8 Model = CreateModel(modelType, genericType, instance), 9 ValidModelBindingMembers = GetBindingMembers(modelType, genericType, blackList).ToList(),10 RequestData = this.GetDataFields(context),11 GenericType = genericType,12 TypeConverters = this.typeConverters.Concat(this.defaults.DefaultTypeConverters),13 };14 }
从中我们可以看到GetBindingMembers用到了blackList,再看看这个方法
1 private static IEnumerable<BindingMemberInfo> GetBindingMembers(Type modelType, Type genericType, IEnumerable<string> blackList)2 {3 var blackListHash = new HashSet<string>(blackList, StringComparer.Ordinal);4 5 return BindingMemberInfo.Collect(genericType ?? modelType)6 .Where(member => !blackListHash.Contains(member.Name));7 }
看到这个,行了,基本就懂了!
member => !blackListHash.Contains(member.Name)
这个表达式就是起到了真正的过滤作用啦!!!
ModelBinding就讲到这里了。
最后献上本次的代码示例:
https://github.com/hwqdt/Demos/tree/master/src/NancyDemoForModelBinding