MVC中的扩展点(八)模型绑定

    MVC可以将用户提交的数据绑定到Action参数,我们将这个过程称之为模型绑定,在模型绑定中有两个关键:一个是值提供器,用于确定数据来源,另一个称为模型绑定器,用于确定如何将值绑定到特性的数据模型。

MVC中默认的值提供器

    值提供器是一组实现了IValueProvider接口的类,MVC中的值提供其使用了标准的抽象工厂设计模式,其类图如下:

IValueProvider

    MVC提供了四种默认的值提供器:

FormValueProvider:表单数据,对应于ASP.NET的Request.Form集合

QueryStringValueProvider:查询字符串,对应于ASP.NET的Request.QueryString集合

HttpFileCollectionValueProvider:文件集合,数据来源于Request.Files集合

RouteDataValueProvider:路由信息,对应于RouteData.Values集合

    MVC为每一个值提供器提供了一个工厂:ValueProviderFactory,ValueProviderFactoryCollection是一个值提供器工厂集合,其中的GetValueProvider方法返回与当前控制器上下文匹配的值提供器。ValueProviderFactories是一个静态类,内部封装一个ValueProviderFactoryCollection集合(Factories属性),此集合默认包含MVC中4个默认的值提供器,所以,如果我们要使用自己的值绑定器,可以通过此类的Factories属性,使用InsertItem增加新的值提供器,使用SetItem方法将某个默认值提供器替换为我们的自定义值提供器。 需要注意的是,ValueProviderFactoryCollection的GetValueProvider方法实际返回的是一个ValueProviderCollection集合,但由于此集合类同样实现了IValueProvider接口(其GetValue方法会从集合中所有的值提供器中查找符合条件的项),所以GetValueProvider的返回类型仍然为IValueProvider。

MVC中默认的模型绑定器

    模型绑定器用于将值提供器提供的数据映射到特定的模型类型,正因为有模型绑定器的存在,才使我们可以直接使用带参数的控制器方法:绑定器可以从值提供器中获取数据,并根据控制器方法参数类型,自动实例化参数并填充数据。

    MVC中默认的模型绑定器类结构如下:

IModelBinder

    MVC中实现四种模型绑定器:

HttpPostedFileBaseModelBinder:用于处理HttpPostedFileBase类型

ByteArrayModelBinder:用于处理Byte[]类型

LinqBinaryModelBinder:用于处理Linq中的Binary类型

DefaultModelBinder:默认绑定器,如果某个类型没有特定的绑定器,则使用此绑定器。

    ModelBinderDictionary是一个IModelBinder字典,键为类型,值为类型所对应的绑定器。DefaultBinder属性指定如果集合中不存在指定类型的绑定器时,应返回的默认绑定器,默认为DefaultModelBinder。我们可以通过此属性来指定我们自己的默认绑定器。ModelBinders是一个静态类,内部封装了ModelBinderDictionary(Binders属性),包含默认的绑定器列表。

    CustomModelBinderAttribute是一个用于指定自定义绑定器的特性的抽象基类,方法GetBinder用于返回一个绑定器实例。ModelBinderAttribute是CustomModelBinderAttribute的一个实现,它根据指定的绑定器类型,返回其实例。MVC在查找适当的绑定器时,遵循以下顺序:

在参数上通过ModelBinderAttribute特性指定的绑定器

在ModelBinders.Binders中注册的绑定器

在类型上通过ModelBinderAttribute特性指定的绑定器

默认绑定器(通常为DefaultModelBinder)

    BindAttribute特性用于指定默认绑定器在绑定时的具体行为:Exlude属性指定绑定器在填充数据时,不处理的属性列表,Include属性指定绑定器在填充数据时,只需处理的属性列表,Prefix用于指定类型的前缀,默认情况下,前缀为控制器方法中参数的名称。IsPropertyAllowed方法用于判断特定的属性是否需要被处理。

自定义值提供器

    要实现自定义的值提供器,我们需要实现一个ValueProviderFactory和一个IValueProvider,之后将其添加到ValueProviderFactories.Factories集合中。下例实现一个CookieValueProvider,用于从Cookie中获取数据:

1、创建一个空MVC项目

2、实现CookieValueProviderFactory工厂及CookieValueProvider提供器

显示行号 复制代码 CookieValueProviderFactory
  1. public class CookieValueProviderFactory : ValueProviderFactory
    
  2.  {
    
  3.     public override IValueProvider GetValueProvider(ControllerContext controllerContext)
    
  4.     {
    
  5.         return new CookieValueProvider(controllerContext.HttpContext.Request.Cookies);
    
  6.     }
    
  7. 
    
  8.     private class CookieValueProvider : IValueProvider
    
  9.     {
    
  10.         private HttpCookieCollection Cookies;
    
  11. 
    
  12.         public CookieValueProvider(HttpCookieCollection cs)
    
  13.         {
    
  14.             Cookies = cs;
    
  15.         }
    
  16. 
    
  17.         public bool ContainsPrefix(string prefix)
    
  18.         {
    
  19.             return Cookies.AllKeys.Contains(prefix);
    
  20.         }
    
  21. 
    
  22.         public ValueProviderResult GetValue(string key)
    
  23.         {
    
  24.             if (!ContainsPrefix(key))
    
  25.                 return null;
    
  26.             string value = Cookies[key].Value;
    
  27.             return new ValueProviderResult(value,value, CultureInfo.CurrentCulture );
    
  28.         }
    
  29.     }
    
  30. }
    
  31. 
    
 

3、在Application_Start中注册自定义值提供器工厂

ValueProviderFactories.Factories.Insert(0, new CookieValueProviderFactory());

 

4、实现一个测试控制器,HomeController:

显示行号 复制代码 HomeController
  1. public class HomeController : Controller
    
  2. {
    
  3.     public ActionResult Index(DateTime? lastTime)
    
  4.     {
    
  5.         if (!Request.Cookies.AllKeys.Contains("lastTime"))
    
  6.         {
    
  7.             Response.Cookies.Add(new HttpCookie("lastTime", DateTime.Now.ToString()));
    
  8.         }
    
  9.         return Content(lastTime == null ? String.Empty : lastTime.ToString());
    
  10.     }
    
  11. }
    
  12. 
    

    注意,参数类型为DateTime?可空类型,这样可防止默认绑定器在没有找到对应值时报错。测试时,首次运行,由于Cookie中没有lastTime项,所以页面为空,刷新一次页面,此时页面将显示上一次访问的时间。

自定义模型绑定器

    通过实现IModelBinder接口可实现自定义模型绑定器,下例将实现一个XDocument类型的绑定器:

1、创建一个空MVC项目

2、实现XDocumentBinder

显示行号 复制代码 XDocumentBinder
  1. public class XDocumentBinder : IModelBinder
    
  2. {
    
  3.     public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    
  4.     {
    
  5.         string key = bindingContext.ModelName;
    
  6.         ValueProviderResult vpr = bindingContext.ValueProvider.GetValue(key);
    
  7.         if (vpr !=null && vpr.RawValue != null && !String.IsNullOrEmpty(vpr.AttemptedValue))
    
  8.         {
    
  9.             //在模型状态中保存尝试值
    
  10.             bindingContext.ModelState.SetModelValue(key, vpr);
    
  11.             string tempString = ((String[])vpr.RawValue)[0];
    
  12.             XDocument xml = null;
    
  13.             try
    
  14.             {
    
  15.                 xml = XDocument.Parse(tempString);
    
  16.             }
    
  17.             catch (XmlException)
    
  18.             {
    
  19.                 //无法解析XML文本,则设置模型错误状态
    
  20.                 bindingContext.ModelState.AddModelError(key, "Not valid XML");
    
  21.                 return null;
    
  22.             }
    
  23. 
    
  24.             //如果模型已经存在,则替换
    
  25.             XDocument existingModel = bindingContext.Model as XDocument;
    
  26.             if (existingModel != null)
    
  27.             {
    
  28.                 if (existingModel.Root != null)
    
  29.                 {
    
  30.                     existingModel.Root.ReplaceWith(xml.Root);
    
  31.                 }
    
  32.                 else
    
  33.                 {
    
  34.                     existingModel.Add(xml.Root);
    
  35.                 }
    
  36.                 return existingModel;
    
  37.             }
    
  38.             else
    
  39.             {
    
  40.                 return xml;
    
  41.             }
    
  42.         }
    
  43.         return null;
    
  44.     }
    
  45. }
    
  46. 
    

3、在Application_Start中注册XDocumentBinder

ModelBinders.Binders.Add(typeof(XDocument), new XDocumentBinder());

 

4、创建用于测试的HomeController及其View

显示行号 复制代码 HomeController
  1. public class HomeController : Controller
    
  2. {
    
  3.     [ValidateInput(false)]
    
  4.     public ActionResult Index(XDocument xml)
    
  5.     {
    
  6.            
    
  7.         if (xml == null)
    
  8.         {
    
  9.             return View();
    
  10.         }
    
  11.         else
    
  12.         {
    
  13.             Response.Clear();
    
  14.             return Content(xml.ToString(), "application/xml");
    
  15.         }
    
  16.     }
    
  17. }
    
  18. 
    
显示行号 复制代码 Index.aspx
  1.     <div>
    
  2.     <%using (Html.BeginForm())
    
  3.       {%>
    
  4.       <%= Html.ValidationSummary() %>
    
  5.        <%=Html.TextArea("xml", new { Rows=20, Cols=50 })%> 
    
  6.        <input type="submit" value ="submit" />
    
  7.      <% } %>
    
  8.     </div>
    
  9. 
    

    关于注册XDocumentBinder,我们可以通过ModelBinders.Binders将某个类型映射到绑定器,也可以通过在参数上、类型上通过ModelBinderAttribute特性来指定。

    本例中Index方法上,我们指定了[ValidateInput(false)]特性,用于跳过输入验证,这是因为默认设置下,MVC不允许提交XML类型的数据。

默认绑定器扩展

    DefaultModelBinder默认绑定器通过Activator.CreateInstance来实例化模型类型,所以我们的模型必须要用无参构造器。要解除此限制,我们可以使用DI技术对默认绑定器进行扩展:从DefaultModelBinder继承一个新类,并重写CreateModel方法,使用DI来实例化模型。最后通过 ModelBinders.Binders.DefaultBinder属性指定我们的自定义默认绑定器。

    关于DI,在MVC中的扩展点(三)控制器工厂中有所涉及,有兴趣的朋友可参照该示例来实现自定义默认绑定器。

源代码下载

posted @ 2011-01-16 15:21  xfrog  阅读(5337)  评论(6编辑  收藏  举报