Nancy之ModelBinding(模型綁定)


過年前的最后一篇博客,決定留給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_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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM