Nancy之ModelBinding(模型綁定)

過年前的最後一篇博客,決定留給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_Using_Bind的綁定是一種錯誤的寫法,會出錯,這個的正確寫法,我在註釋給出了。
 
employee_Empty、employee_Using_BindWithTModel、employee_Using_BindingTo、employee_Using_BindingToWithTModel

這幾個最終都是同樣的效果!!這裏說最終,是由於咱們的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之靜態文件處理

否則會發現這個錯誤"$ is not defined"
 
而後在TestModule.cs中添加以下代碼
 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中也一致

咱們在mvc視圖中用的 @Html.EditorFor之類的強類型綁定,生成的頁面都是把name設置爲字段名稱!

下面看看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 是判斷是否能轉化爲目的類型,

Convert纔是真正的轉化!
 

Nancy默認的Converters包含了Collection、DateTime、Fallback和Numeric

固然,有了這個接口,咱們能夠實現更多的拓展,怎麼用着方便怎麼來!

 

固然不能忘了咱們自定義模型綁定用到的接口 IModelBinder

1     public interface IModelBinder : IBinder
2     {
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

相關文章
相關標籤/搜索