模型绑定的本质
 
   任何控制器方法的执行都受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. 如何解决设置Session保存在StateServer后引起WebService/WebMethod无法异步获取Session

    项目中有一个文件上传功能,需要显示文件上传进度.于是使用PageMethods 调用WebService/WebMethod . 在demo中测试一切正常.但是转移到项目中之后无法异步刷新文件上传进度 ...

  2. 【最短路】NEERC15 F Froggy Ford(2015-2016 ACM-ICPC)(Codeforces GYM 100851)

    题目链接: http://codeforces.com/gym/100851 题目大意: 一只青蛙跳过宽为W的河,河中游N个石头,坐标xi,yi,现在往河中间添加一个石头,使得每次跳跃的最大的距离最小 ...

  3. uri编解码

    相关函数如下:(都是全局函数) encodeURI(URIString):将文本字符串编码为有效的统一资源标示符URI decodeURI(URIString) encodeURIComponent( ...

  4. 最小生成树 10.1.5.253 1505 poj 1258 http://poj.org/problem?id=1258

    #include <iostream>// poj 1258 10.1.5.253 1505 using namespace std; #define N 105 // 顶点的最大个数 ( ...

  5. [OSX] 取消开机启动

    以Pulse Secure为例 参考:https://kb.pulsesecure.net/articles/Pulse_Secure_Article/KB26679 输入指令: launchctl ...

  6. Codeforces Round #326 (Div. 1) - C. Duff in the Army 树上倍增算法

    题意:一个n个点的数, m个人住在其中的某些点上, 每个人的标号1-m, 询问u-v 路径上标号前a个人,并输出标号,a < 10. 作法, 利用倍增, ID[j][i] 表示i到i的第2^j个 ...

  7. Servlet线程安全

    public class servletDemo1 extends HttpServlet { int i=0; public void doGet(HttpServletRequest reques ...

  8. Spring 3.x企业应用开发实战(9-1)----依赖注入

    Spring中的依赖注入方式:属性注入.构造函数注入和工厂方式注入. 1.属性注入 属性注入即通过setXxx()方法注入Bean的属性值或依赖对象. 属性注入要求Bean提供一个默认的构造函数,在J ...

  9. Greenplum 数据库架构分析

    Greenplum 数据库是最先进的分布式开源数据库技术,主要用来处理大规模的数据分析任务,包括数据仓库.商务智能(OLAP)和数据挖掘等.自2015年10月正式开源以来,受到国内外业内人士的广泛关注 ...

  10. Linux散列表(一)——操作函数

    散列表(又名哈希表)仅仅需要一个包含单一指针的链表头.它是双向链表的变体.它不同于双链表——表头和结点使用相同的结构体——散列表对表头和结点有不同的定义.如下: struct hlist_head { ...