過年前的最後一篇博客,決定留給Nancy中的ModelBindingjavascript
仍是一樣的,咱們與MVC結合起來,方便理解和對照html
先來看看MVC中簡單的ModelBinding吧java
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 }
上面的代碼是我用下面類型的控制器生成的一個添加方法,裏面就用到了ModelBindingjquery
像這樣比較簡單的模型綁定,你們應該是很熟悉了吧!git
或許已經爛熟於心了。github
MVC中關於Model Binding的詳細解讀能夠參見下面的,真的超詳細,我就再也不展開了ajax
[ASP.NET MVC 小牛之路]15 - Model Bindingjson
ModelBinder——ASP.NET MVC Model綁定的核心數組
下面就來看看Nancy中的model binding吧。mvc
先來看個具體的例子,咱們順着這個例子來說解這一塊的內容
這個例子咱們要用到的引用有Nancy,Nancy.Hosting.Aspnet
咱們先來看看它默認的綁定
先創建一個模型Employee
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 }
咱們在這個模型中,給部分字段設置了默認值。
創建一個視圖default.html,用於測試Nancy中默認的ModelBinding
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>
而後咱們創建一個TestModule.cs,在裏面演示了各類不一樣方式下的binding
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 }
下面來看看運行的結果
咱們在表單填下了這些內容,如今咱們監視上面的各個值的變化
這幾個最終都是同樣的效果!!這裏說最終,是由於咱們的employee_Empty剛實例化時,應該是咱們設置的默認值。
employee_Using_BindAndBlacklistStyle1和employee_Using_BindAndBlacklistStyle2是在Bind後面帶了參數的,
這些參數就是所謂的黑名單,就是綁定的時候忽略掉。而後結果就是咱們設置的默認值Catcher8和18。
至於它爲何這樣就能綁定上,咱們看了自定義的綁定以後可能會清晰很多。
接下來就是使用自定義的綁定方法:
模型咱們仍是用剛纔的Employee.cs
此處新添加一個視圖custom.html,基本和前面的default.html一致,換了個action
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>
相當重要的一步!!!編寫咱們的ModelBinder,這個ModelBinder要實現IModelBinder這個接口!
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 }
而後在咱們的TestModule.cs中添加以下代碼
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 };
下面看看運行效果
咱們仍是在表單輸入這些內容,同時對employee1和employee2添加監視
清楚的看到,咱們自定義的binder生效了,年齡就是咱們設定的24!
Nancy中,還有比較方便的是json和xml也一樣能綁定。因爲這兩個很類似,因此這裏就只介紹json。
一樣的,例子說話!
添加一個json.html視圖
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>
在這裏,偷懶了(節省點時間),我是直接寫死了兩個值,而後打印出這個employee的相關屬性。
還有一點要注意的是。引用的js文件,不想寫convention配置就把js放到content文件夾,具體的可參見我前面的bolg Nancy之靜態文件處理。
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 };
運行看看效果
再來看看咱們監視的狀況!!
很nice,正是咱們想要的結果,編號沒有賦值,自動取了默認值!
跟往常同樣,簡單分析一下這一塊的源碼。
ModelBinding在Nancy這個項目下面,裏面的內容以下:
很明顯,咱們應該先看看DefaultBinder.cs,由於全部的默認實現,Nancy都會帶Default的字樣
DefaultBinder實現了IBinder這個接口,這個接口裏面就一個東西Bind
1 /// <summary> 2 /// Binds incoming request data to a model type 3 /// </summary> 4 public interface IBinder 5 { 6 /// <summary> 7 /// Bind to the given model type 8 /// </summary> 9 /// <param name="context">Current context</param> 10 /// <param name="modelType">Model type to bind to</param> 11 /// <param name="configuration">The <see cref="BindingConfig"/> that should be applied during binding.</param> 12 /// <param name="blackList">Blacklisted property names</param> 13 /// <param name="instance">Existing instance of the object</param> 14 /// <returns>Bound model</returns> 15 object Bind(NancyContext context, Type modelType, object instance, BindingConfig configuration, params string[] blackList); 16 }
這就是咱們ModelBinding的關鍵所在!
DefaultBinder裏面的實現就是
先判斷綁定的類型是否是數組集合,是的話,一種處理策略,不是的話,另外一種處理策略,
在裏面的判斷中還有一個重要的概念是Binding Configuration。由於這個Configuration能夠修改咱們綁定的行爲
這裏我直接截了官網文檔的圖來展現
BodyOnly設置爲true的時候,一旦主體被綁定,binder就會馬上中止。
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中也一致
下面看看ITypeConverter這個接口
1 public interface ITypeConverter 2 { 3 bool CanConvertTo(Type destinationType, BindingContext context); 4 5 object Convert(string input, Type destinationType, BindingContext context); 6 }
這個接口提供了一種轉換類型的方法
CanConvertTo 是判斷是否能轉化爲目的類型,
Nancy默認的Converters包含了Collection、DateTime、Fallback和Numeric
固然,有了這個接口,咱們能夠實現更多的拓展,怎麼用着方便怎麼來!
固然不能忘了咱們自定義模型綁定用到的接口 IModelBinder
1 public interface IModelBinder : IBinder 2 { 3 bool CanBind(Type modelType); 4 }
IModerBinder這個接口除了本身定義的CanBind方法外,還繼承了IBinder這個接口,因此咱們自定義ModelBinder的時候只須要
實現這個接口就能夠了。做用就是綁定數據到相應的模型中。
最後就講講「黑名單」的內容!
「黑名單」的實現,還用到了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