模型绑定的本质
 
   任何控制器方法的执行都受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. a great tool for automatically formating your code!

    1. make your own format file at your project's root or file's folder. (The clang-format will automat ...

  2. bzoj2427

    一开始读错题导致各种不会做,无奈其实是一道水题,缩点反向建图树形dp即可 type link=^point; point=record po:longint; next:link; end; ..] ...

  3. 【转】Android JNI编程—JNI基础

    原文网址:http://www.jianshu.com/p/aba734d5b5cd 最近看到了很多关于热补的开源项目——Depoxed(阿里).AnFix(阿里).DynamicAPK(携程)等,它 ...

  4. 图论(网络流):SCOI 2007 修车

    同一时刻有N位车主带着他们的爱车来到了汽车维修中心.维修中心共有M位技术人员,不同的技术人员对不同的车进行维修所用的时间是不同的.现在需要安排这M位技术人员所维修的车及顺序,使得顾客平均等待的时间最小 ...

  5. select的使用(二)

    多表查询 /*基本连接*/ select a.Name,b.Name from T_Employee a,T_Department b where a.DepartmentId=b.Id /*内连接, ...

  6. Delphi生成GUID

    Delphi生成GUID Uses ComObj; Var aGUID: string; aGUID := GetClassId; // 取得GUID

  7. 数学概念——F 概率(经典问题)birthday paradox

    F - 概率(经典问题) Time Limit:2000MS     Memory Limit:32768KB     64bit IO Format:%lld & %llu Submit S ...

  8. sublime text3安装SublimeREPL--解决不能运行input()的问题

    原文地址:http://blog.chinaunix.net/uid-12014716-id-4269991.html 一.安装包管理器(如果已经安装可以忽略) 1.简单的安装方法:使用Ctrl+`快 ...

  9. android webview无法加载网页

    主要原因是没有在AndroidManifest.xml里面设置如下: <user-permission android:name="android.permission.INTERNE ...

  10. python操作RabbiMQ

    RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统.他遵循Mozilla Public License开源协议. MQ全称为Message Queue, 消息队列(MQ)是一种应用程序 ...