過年前的最后一篇博客,決定留給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中關於Model Binding的詳細解讀可以參見下面的,真的超詳細,我就不再展開了
[ASP.NET MVC 小牛之路]15 - Model Binding
ModelBinder——ASP.NET MVC Model綁定的核心
下面就來看看Nancy中的model binding吧。
先來看個具體的例子,我們順着這個例子來講解這一塊的內容
這個例子我們要用到的引用有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