zoukankan      html  css  js  c++  java
  • 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:
    " + 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

  • 相关阅读:
    2017 年终总结 —— 在路上
    尝试造了个工具类库,名为 Diana
    走近 Python (类比 JS)
    Node.js 异步异闻录
    使用 Node.js 搭建一个 API 网关
    不就是语法和长难句吗—笔记总结Day4
    不就是语法和长难句吗—笔记总结Day3
    不就是语法和长难句吗—笔记总结Day2
    不就是语法和长难句吗—笔记总结Day1
    Kali Day1
  • 原文地址:https://www.cnblogs.com/catcher1994/p/5181663.html
Copyright © 2011-2022 走看看