模型绑定的本质
 
   任何控制器方法的执行都受action invoker组件(下文用invoker代替)控制。对于每个Action方法的参数,这个invoker组件都会获取一个Model Binder Object(模型绑定器对象)。Model Binder的职责包括为Action方法参数寻找一个可能的值(从HTTP请求上下文)。每个参数都可以绑定到不同的Model Binder;但是大部分情况我们都使用的是默认模型绑定器-DefaultModelBinder(如果我们没有显式设置使用自定义的Model Binder的话)。
 
   每个Model Binder都使用它自己的特定算法来为Action方法参数设置值。默认模型绑定器对象大量使用反射机制。具体来说,对于每个Action方法参数,Model Binder都试图根据参数名去请求参数中寻找匹配的值。比如某个Action方法参数名为Text,那么ModelBinder会去请求上下文中寻找拥有相同名字的名值对(Entry)。如果找到,则Model Binder继续将Entry的值转换为Action方法参数类型。如果类型转换成功,转换后的值就被赋给那个Action方法参数,否则会抛出异常。托福答案
 
   注意只要遇到第一个不能成功转换类型或者在请求上下文中找不到匹配的参数(而且这个参数的类型为不可空类型),那么就会立刻抛异常。也就是说只有所有声明的参数都被Model Binder成功解析,Action方法才会被调用。并且注意Model Binder产生的异常无法在Action方法中捕获,我们必须在global.asax中设置一个全局错误处理器(global error handler)来捕获处理这些异常。还须注意只有当方法参数不能赋值为null才会抛异常。所以对于下面这种情况:
 
   public ActionResult TestAction(string name, Int32 age) {
 
   // ...
 
   return View();
 
   }
 
   如果请求上下文中不包含名为name的Entry不会有任何问题,name参数值被ModelBinder设为null。但是age参数就不同了,因为Int32类型是基本值类型,不能赋null值。如果我们需要允许不传age参数,那么我们只需要简单地修改代码为如下或者为age参数提供一个默认值:
 
   public ActionResult TestAction(string name, Nullable age) {
 
   // ...
 
   return View();
 
   }
 
   默认模型绑定器从HTTP请求上下文中查找参数值的顺序如下:
 
   请求数据来源说明
 
   Request.Form通过表单提交的参数
 
   RouteData.Values路由参数
 
   Request.QueryString查询参数,类似http://abc.com?name=jxq,这里的name=jxq即查询参数托福答案
 
   Request.Files随请求上传的文件
 
   当我们有多个要上传的参数时,我们最好不要为每个请求参数都创建一个Action方法参数,以避免方法参数列表过长。
 
   对于默认模型绑定器,我们可以将参数列表封装成一个参数类型,然后默认模型绑定器会按照与上面相同的按名(属性名和请求参数名进行匹配)匹配算法去设置这个参数对象的属性。
 
   手动调用模型绑定
 
   默认情况下模型绑定会自动调用,但是我们也可以手动进行模型绑定。比如下面的代码示例:
 
   [HttpPost]
 
   public ActionResult RegisterMember() {
 
   Person myPerson = new Person();
 
   UpdateModel(myPerson);
 
   return View(myPerson);
 
   }
 
   上面的UpdateModel(myPerson)即手动模型绑定。
 
   手动进行模型绑定的最大好处就是使得模型绑定过程更灵活。比如我们可以限制类型绑定只取表单提交参数,那么我们可以像下面这么做:
 
   [HttpPost]
 
   public ActionResult RegisterMember() {
 
   Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person));
 
   UpdateModel(myPerson, new FormValueProvider(ControllerContext));
 
   return View(myPerson);
 
   }
 
   FormValueProvider实现了IValueProvider接口,其他几种参数对应的IValueProvider实现如下:
 
   请求数据来源IValueProvider实现
 
   Request.FormFormValueProvider
 
   RouteData.ValuesRouteDataValueProvider
 
   Request.QueryStringQueryStringValueProvider
 
   Request.FilesHttpFileCollectionValueProvider
 
   除了上面的方法可以限制类型绑定的数据来源外,我们还可以利用直接利用FormCollection作为IValueProvider,如下:
 
   [HttpPost]
 
   public ActionResult RegisterMember(FormCollection formData) {
 
   Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person));
 
   UpdateModel(myPerson, formData);
 
   return View(myPerson);
 
   }
 
   手动进行模型绑定过程可能发生异常,可以用两种方法处理:第一种方法是直接捕获异常;第二种方法是利用TryUpdateModel方法。第一种方法如下:
 
   [HttpPost]
 
   public ActionResult RegisterMember(FormCollection formData) {
 
   Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person));
 
   try
 
   {
 
   UpdateModel(myPerson, formData);
 
   }
 
   catch(InvalidOperationException e)
 
   {
 
   // 处理异常
 
   }
 
   return View(myPerson);
 
   }
 
   第二种方法如下:
 
   [HttpPost]
 
   public ActionResult RegisterMember(FormCollection formData) {
 
   Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person));
 
   if(TryUpdateModel(myPerson, formData))
 
   {
 
   // 正常处理
 
   }
 
   else {
 
   // 处理异常
 
   }
 
   return View(myPerson);
 
   }
 
   第一节已经说过当使用默认模型绑定器时,我们无法在Action方法捕获模型绑定过程抛出的异常,可以在global.ascx中配置错误处理器来捕获处理。除此之外,我们还可以通过ModelState.IsValid来判断默认模型绑定是否成功。托福答案
 
   定制模型绑定器系统
 
   我们可以定制IValueProvider实现 ,IValueProvider接口定义如下:
 
   namespace System.Web.Mvc {
 
   using System;
 
   public interface IValueProvider {
 
   bool ContainsPrefix(string prefix);
 
   ValueProviderResult GetValue(string key);
 
   }
 
   }
 
   我们定制一个IValueProvider实现如下:
 
   using System;
 
   using System.Globalization;
 
   using System.Web.Mvc;
 
   namespace CustomeModelBinderDemo.Controllers.ValueProvider
 
   {
 
   public class MyValueProvider: IValueProvider
 
   {
 
   public bool ContainsPrefix(string prefix)
 
   {
 
   return System.String.Compare("curTime", prefix, StringComparison.OrdinalIgnoreCase) == 0;
 
   }
 
   public ValueProviderResult GetValue(string key)
 
   {
 
   return ContainsPrefix(key) ? new ValueProviderResult(DateTime.Now, null, CultureInfo.CurrentCulture) : null;
 
   }
 
   }
 
   }
 
   上面的GetValue(string key)方法即根据Action方法参数名从HTTP请求上下文获取匹配的值存到ValueProviderResult对象,ValueProviderResult类型包含一个ConvertTo(Type type)方法,用于将它封装的值转换成指定类型,这正好与第一节中讲的类型转换吻合(也说明XValueProvider对象负责模型绑定过程中的类型转换工作,因为模型绑定器会调用XValueProvider对象进行类型转换)。
 
   然后我们定义一个ValueProvider工厂类:
 
   public class CurrentTimeValueProviderFactory : ValueProviderFactory {
 
   public override IValueProvider GetValueProvider(ControllerContext controllerContext) {
 
   return new CurrentTimeValueProvider();
 
   }
 
   }
 
   然后我们在Global.asax的Application_Start方法中注册这个工厂类:
 
   protected void Application_Start() {
 
   AreaRegistration.RegisterAllAreas();
 
   ValueProviderFactories.Factories.Insert(0, new CurrentTimeValueProviderFactory()); // 注册ValueProvider工厂对象
 
   RegisterGlobalFilters(GlobalFilters.Filters);
 
   RegisterRoutes(RouteTable.Routes);
 
   }
 
   最后我们在Action中使用如下:
 
   public ActionResult Clock(DateTime curTime) {
 
   return Content("The time is " + curTime.ToLongTimeString());
 
   }
 
   我们也可以定制模型绑定器对象 ,有两种方法:第一种方法是继承DefaultModelBinder类,然后重写它的CreateModel方法,并且在Application_Start方法中设置ModelBinders.Binders.DefaultBinder为这个模型绑定器(表明现在的默认模型绑定器用的是我们自己定义的):
 
   public class DIModelBinder : DefaultModelBinder {
 
   protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) {
 
   return DependencyResolver.Current.GetService(modelType) ?? base.CreateModel(controllerContext, bindingContext, modelType);
 
   }
 
   }
 
   然后在global.asax的Application_Start方法中设置默认模型绑定器为DIModelBinder:
 
   protected void Application_Start() {
 
   // ...
 
   ModelBinders.Binders.DefaultBinder = new DIModelBinder();
 
   // ...
 
   }
 
   第二种方法继承IModelBinder接口,然后实现它的接口方法:
 
   public class PersonModelBinder : IModelBinder {
 
   public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
 
   // 检查是否有现成的model对象,如果没有创建一个(如果使用手动绑定则bindingContext.Model就不会为null)
 
   Person model = (Person) bindingContext.Model ?? (Person)DependencyResolver.Current.GetService(typeof(Person));
 
   // find out if the value provider has the required prefix
 
   bool hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName); // bindingContext.ModelName返回当前模型的名称
 
   string searchPrefix = (hasPrefix) ? bindingContext.ModelName + "." : "";
 
   // 填充model对象的字段
 
   model.PersonId = int.Parse(GetValue(bindingContext, searchPrefix, "PersonId"));
 
   model.FirstName = GetValue(bindingContext, searchPrefix, "FirstName");
 
   model.LastName = GetValue(bindingContext, searchPrefix, "LastName");
 
   model.BirthDate = DateTime.Parse(GetValue(bindingContext, searchPrefix, "BirthDate"));
 
   model.IsApproved = GetCheckedValue(bindingContext, searchPrefix, "IsApproved");
 
   model.Role = (Role)Enum.Parse(typeof(Role), GetValue(bindingContext, searchPrefix, "Role"));
 
   return model;
 
   }
 
   private string GetValue(ModelBindingContext context, string prefix, string key) {
 
   ValueProviderResult vpr = context.ValueProvider.GetValue(prefix + key);
 
   return vpr == null ? null : vpr.AttemptedValue;
 
   }
 
   private bool GetCheckedValue(ModelBindingContext context, string prefix, string key) {
 
   bool result = false;
 
   ValueProviderResult vpr = context.ValueProvider.GetValue(prefix + key);
 
   if (vpr != null) {
 
   result = (bool)vpr.ConvertTo(typeof(bool));
 
   }
 
   return result;
 
   }
 
   }
 
   然后同样地我们需要注册模型绑定器,可以全局注册,方法是在Application_Start方法中添加下面代码:
 
   protected void Application_Start() {
 
   // ...
 
   ModelBinders.Binders.Add(typeof(Person), new PersonModelBinder());
 
   // ...
 
   }
 
   也可以通过Attribute为某个Action参数设置模型绑定器,如下:
 
   public ActionResult Index(
 
   [ModelBinder(typeof(DateTimeModelBinder))] DateTime theDate)
 
   亦或者像下面这样进行模型绑定器与类型的绑定:
 
   [ModelBinder(typeof(PersonModelBinder))]
 
   public class Person {
 
   [HiddenInput(DisplayValue=false)]
 
   public int PersonId { get; set; }
 
   public string FirstName { get; set; }
 
   public string LastName { get; set; }
 
   }
 
   最后我们来看看如何定制ModelBinderProvider ,它主要用于有多个模型绑定器的情况下来选择用哪个某型绑定器,我们需要实现IModelProvider接口:
 
   using System;
 
   using System.Web.Mvc;
 
   using MvcApp.Models;
 
   namespace MvcApp.Infrastructure {
 
   public class CustomModelBinderProvider : IModelBinderProvider {
 
   public IModelBinder GetBinder(Type modelType) {
 
   return modelType == typeof(Person) ? new PersonModelBinder() : null;
 
   }
 
   }
 
   }
 
   然后又是老套的在Application_Start方法中注册:
 
   ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());

ASP.NET MVC中的模型绑定的更多相关文章

  1. Asp.net Mvc 中的模型绑定

    asp.net mvc中的模型绑定可以在提交http请求的时候,进行数据的映射. 1.没有模型绑定的时候 public ActionResult Example0() { ) { string id ...

  2. ASP.NET MVC学习之模型绑定(1)

    一.前言 下面我们将开始学习模型绑定,通过下面的知识我们将能够理解ASP.NET MVC模型的模型绑定器是如何将http请求中的数据转换成模型的,其中我们重点讲述的是表单数据. 二.正文 1.简单类型 ...

  3. ASP.NET MVC中的模型装配 封装方法 非常好用

    下面说一下 我们知道在asp.net mvc中 视图可以绑定一个实体模型 然后我们三层架构中也有一个model模型 但是这两个很多时候却是不一样的对象来的 就拿微软的官方mvc例子来说明 微软的视图实 ...

  4. ASP.NET Core 中的模型绑定

    微软官方文档:ASP.NET Core 中的模型绑定 Route 是通过MVC Route URL取值. 如:http://localhost:5000/Home/Index/2,id取出的值就会是2 ...

  5. ASP.NET MVC学习之模型绑定(2)

    3.手工调用模型绑定 很多情况下我们都是通过形参的方式接收来自http流中的数据,这看似是完美的,但是缺少了很多过程中的控制,所以我们就需要使用手工的方式进行绑定.下面我们通过一个例子来说明,首先打开 ...

  6. [转]ASP.NET MVC 4 (九) 模型绑定

    本文转自:http://www.cnblogs.com/duanshuiliu/p/3706701.html 模型绑定指的是MVC从浏览器发送的HTTP请求中为我们创建.NET对象,在HTTP请求和C ...

  7. ASP.NET MVC 4 (九) 模型绑定

    模型绑定指的是MVC从浏览器发送的HTTP请求中为我们创建.NET对象,在HTTP请求和C#间起着桥梁的作用.模型绑定的一个最简单的例子是带参数的控制器action方法,比如我们注册这样的路径映射: ...

  8. ASP.NET MVC 下自定义模型绑定,去除字符串类型前后的空格

    直接贴代码了: SkyModelBinder.cs using System.ComponentModel; using System.Linq; using System.Web.Mvc; name ...

  9. ASP.NET MVC中默认Model Binder绑定Action参数为List、Dictionary等集合的实例

    在实际的ASP.NET mvc项目开发中,有时会遇到一个参数是一个List.Dictionary等集合类型的情况,默认的情况ASP.NET MVC框架是怎么为我们绑定ASP.NET MVC的Actio ...

随机推荐

  1. 五种I/O 模式——阻塞(默认IO模式),非阻塞(常用语管道),I/O多路复用(IO多路复用的应用场景),信号I/O,异步I/O

    五种I/O 模式——阻塞(默认IO模式),非阻塞(常用语管道),I/O多路复用(IO多路复用的应用场景),信号I/O,异步I/O 五种I/O 模式:[1]        阻塞 I/O          ...

  2. TCP/IP学习(四)TCP缓冲区大小及限制(转)

    链接来自:http://blog.csdn.net/ysu108/article/details/7764461 这个问题在前面有的部分已经涉及,这里在重新总结下.主要参考UNIX网络编程. (1)数 ...

  3. bzoj1251 序列终结者(Splay Tree+懒惰标记)

    Description 网上有许多题,就是给定一个序列,要你支持几种操作:A.B.C.D.一看另一道题,又是一个序列 要支持几种操作:D.C.B.A.尤其是我们这里的某人,出模拟试题,居然还出了一道这 ...

  4. [Sequence Alignment Methods] Smith–Waterman algorithm

    Smith–Waterman algorithm 首先需要澄清一个事实,Smith–Waterman algorithm是求两个序列的最佳subsequence匹配,与之对应的算法但是求两个序列整体匹 ...

  5. POJ 1456 Supermarket

    题意:商场卖东西,每种商品有两个属性,一种是价格pi,另一种是保质期di,每种商品只能在天数<=di的时候卖出.每天只能卖一种商品,问最多能卖出价格之和为多少的商品.(n <= 10^4, ...

  6. L - Subway - POJ 2502

    题意:在一个城市里,分布着若干条地铁线路,每条地铁线路有若干个站点,所有地铁的速度均为40km/h.现在你知道了出发地和终点的坐标,以及这些地铁 线路每个站点的坐标,你的步行速度为10km/h,且你到 ...

  7. poj2393

    题目大意: 奶酪工厂 奶牛买了一个奶酪工厂制作全世界有名的Yucky酸奶,在接下来的N周(1<=N<=10000),牛奶的价格和工作将会受到波动例如他将花费C_i (1 <= C_i ...

  8. Oracle 数据库 Database Express Edition 11g Release 2 (11.2) 错误解决集锦(使用语法)

    ORA-14552: 在查询或 DML 中无法执行 DDL, 提交或回退 PL/SQL“ORA-14551:无法在查询中执行DML操作 解决:在声明函数时加上: PRAGMA AUTONOMOUS_T ...

  9. git分享:Git_DataPro

    基于TCP/UDP网络协议,在Java NIO特性下实现Socket通信,实现终端设备数据的实时接收解析存储.主要技术点:多线程.缓存.C3P0连接池等. <项目执行:打包下载全部文件导入Ecl ...

  10. 细说Lucene源码(一):索引文件锁机制

    大家都知道,在多线程或多进程的环境中,对统一资源的访问需要特别小心,特别是在写资源时,如果不加锁,将会导致很多严重的后果,Lucene的索引也是如此,lucene对索引的读写分为IndexReader ...