[转载]ASP.NET MVC中的扩展点(八)模型绑定

[转载]MVC中的扩展点(八)模型绑定 – xfrog – 博客园.

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.     private class CookieValueProvider : IValueProvider
    
  8.     {
    
  9.         private HttpCookieCollection Cookies;
    
  10.         public CookieValueProvider(HttpCookieCollection cs)
    
  11.         {
    
  12.             Cookies = cs;
    
  13.         }
    
  14.         public bool ContainsPrefix(string prefix)
    
  15.         {
    
  16.             return Cookies.AllKeys.Contains(prefix);
    
  17.         }
    
  18.         public ValueProviderResult GetValue(string key)
    
  19.         {
    
  20.             if (!ContainsPrefix(key))
    
  21.                 return null;
    
  22.             string value = Cookies[key].Value;
    
  23.             return new ValueProviderResult(value,value, CultureInfo.CurrentCulture );
    
  24.         }
    
  25.     }
    
  26. }
    

.src_container { background-color: rgb(231, 229, 220); width: 99%; overflow: hidden; margin: 12px 0pt ! important; padding: 0px 3px 3px 0px; }.src_container .titlebar { background-color: rgb(212, 223, 255); border-width: 1px 1px 0pt; border-style: solid solid none; border-color: rgb(79, 129, 189) rgb(79, 129, 189) -moz-use-text-color; padding: 3px 24px; margin: 0pt; width: auto; line-height: 120%; overflow: hidden; text-align: left; font-size: 12px; }.src_container .toolbar { display: inline; font-weight: normal; font-size: 100%; float: right; color: rgb(0, 0, 255); text-align: left; overflow: hidden; }.toolbar span.button { display: inline; font-weight: normal; font-size: 100%; color: rgb(0, 0, 255); text-align: left; overflow: hidden; cursor: pointer; }.src_container div.clientarea { background-color: white; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; height: auto; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,courier,monospace,serif; }.src_container ol.mainarea { padding: 0pt 0pt 0pt 52px; margin: 0pt; background-color: rgb(247, 247, 255) ! important; }.number_show { padding-left: 52px ! important; list-style: decimal outside none ! important; }.number_show li { list-style: decimal outside none ! important; border-left: 1px dotted rgb(79, 129, 189); }.number_hide { padding-left: 0px ! important; list-style-type: none ! important; }.number_hide li { list-style-type: none ! important; border-left: 0px none; }ol.mainarea li { display: list-item ! important; font-size: 12px ! important; margin: 0pt ! important; line-height: 18px ! important; padding: 0pt 0pt 0pt 0px ! important; background-color: rgb(247, 247, 255) ! important; color: rgb(79, 129, 189); }ol.mainarea li pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap ol.mainarea li pre { white-space: pre-wrap; word-wrap: break-word; }ol.mainarea li pre.alt { background-color: rgb(247, 247, 255) ! important; } 3、在Application_Start中注册自定义值提供器工厂

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

.codearea { color: black; background-color: white; line-height: 18px; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,”BitStream Vera Sans Mono”,courier,monospace,serif; }.codearea pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap pre { white-space: pre-wrap; word-wrap: break-word; }.codearea pre.alt { background-color: rgb(247, 247, 255) ! important; }.codearea .lnum { color: rgb(79, 129, 189); line-height: 18px; }

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. }
    

.src_container { background-color: rgb(231, 229, 220); width: 99%; overflow: hidden; margin: 12px 0pt ! important; padding: 0px 3px 3px 0px; }.src_container .titlebar { background-color: rgb(212, 223, 255); border-width: 1px 1px 0pt; border-style: solid solid none; border-color: rgb(79, 129, 189) rgb(79, 129, 189) -moz-use-text-color; padding: 3px 24px; margin: 0pt; width: auto; line-height: 120%; overflow: hidden; text-align: left; font-size: 12px; }.src_container .toolbar { display: inline; font-weight: normal; font-size: 100%; float: right; color: rgb(0, 0, 255); text-align: left; overflow: hidden; }.toolbar span.button { display: inline; font-weight: normal; font-size: 100%; color: rgb(0, 0, 255); text-align: left; overflow: hidden; cursor: pointer; }.src_container div.clientarea { background-color: white; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; height: auto; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,courier,monospace,serif; }.src_container ol.mainarea { padding: 0pt 0pt 0pt 52px; margin: 0pt; background-color: rgb(247, 247, 255) ! important; }.number_show { padding-left: 52px ! important; list-style: decimal outside none ! important; }.number_show li { list-style: decimal outside none ! important; border-left: 1px dotted rgb(79, 129, 189); }.number_hide { padding-left: 0px ! important; list-style-type: none ! important; }.number_hide li { list-style-type: none ! important; border-left: 0px none; }ol.mainarea li { display: list-item ! important; font-size: 12px ! important; margin: 0pt ! important; line-height: 18px ! important; padding: 0pt 0pt 0pt 0px ! important; background-color: rgb(247, 247, 255) ! important; color: rgb(79, 129, 189); }ol.mainarea li pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap ol.mainarea li pre { white-space: pre-wrap; word-wrap: break-word; }ol.mainarea li pre.alt { background-color: rgb(247, 247, 255) ! important; } 注意,参数类型为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.             XDocument existingModel = bindingContext.Model as XDocument;
    
  25.             if (existingModel != null)
    
  26.             {
    
  27.                 if (existingModel.Root != null)
    
  28.                 {
    
  29.                     existingModel.Root.ReplaceWith(xml.Root);
    
  30.                 }
    
  31.                 else
    
  32.                 {
    
  33.                     existingModel.Add(xml.Root);
    
  34.                 }
    
  35.                 return existingModel;
    
  36.             }
    
  37.             else
    
  38.             {
    
  39.                 return xml;
    
  40.             }
    
  41.         }
    
  42.         return null;
    
  43.     }
    
  44. }
    

.src_container { background-color: rgb(231, 229, 220); width: 99%; overflow: hidden; margin: 12px 0pt ! important; padding: 0px 3px 3px 0px; }.src_container .titlebar { background-color: rgb(212, 223, 255); border-width: 1px 1px 0pt; border-style: solid solid none; border-color: rgb(79, 129, 189) rgb(79, 129, 189) -moz-use-text-color; padding: 3px 24px; margin: 0pt; width: auto; line-height: 120%; overflow: hidden; text-align: left; font-size: 12px; }.src_container .toolbar { display: inline; font-weight: normal; font-size: 100%; float: right; color: rgb(0, 0, 255); text-align: left; overflow: hidden; }.toolbar span.button { display: inline; font-weight: normal; font-size: 100%; color: rgb(0, 0, 255); text-align: left; overflow: hidden; cursor: pointer; }.src_container div.clientarea { background-color: white; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; height: auto; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,courier,monospace,serif; }.src_container ol.mainarea { padding: 0pt 0pt 0pt 52px; margin: 0pt; background-color: rgb(247, 247, 255) ! important; }.number_show { padding-left: 52px ! important; list-style: decimal outside none ! important; }.number_show li { list-style: decimal outside none ! important; border-left: 1px dotted rgb(79, 129, 189); }.number_hide { padding-left: 0px ! important; list-style-type: none ! important; }.number_hide li { list-style-type: none ! important; border-left: 0px none; }ol.mainarea li { display: list-item ! important; font-size: 12px ! important; margin: 0pt ! important; line-height: 18px ! important; padding: 0pt 0pt 0pt 0px ! important; background-color: rgb(247, 247, 255) ! important; color: rgb(79, 129, 189); }ol.mainarea li pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap ol.mainarea li pre { white-space: pre-wrap; word-wrap: break-word; }ol.mainarea li pre.alt { background-color: rgb(247, 247, 255) ! important; }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. }
    

.src_container { background-color: rgb(231, 229, 220); width: 99%; overflow: hidden; margin: 12px 0pt ! important; padding: 0px 3px 3px 0px; }.src_container .titlebar { background-color: rgb(212, 223, 255); border-width: 1px 1px 0pt; border-style: solid solid none; border-color: rgb(79, 129, 189) rgb(79, 129, 189) -moz-use-text-color; padding: 3px 24px; margin: 0pt; width: auto; line-height: 120%; overflow: hidden; text-align: left; font-size: 12px; }.src_container .toolbar { display: inline; font-weight: normal; font-size: 100%; float: right; color: rgb(0, 0, 255); text-align: left; overflow: hidden; }.toolbar span.button { display: inline; font-weight: normal; font-size: 100%; color: rgb(0, 0, 255); text-align: left; overflow: hidden; cursor: pointer; }.src_container div.clientarea { background-color: white; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; height: auto; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,courier,monospace,serif; }.src_container ol.mainarea { padding: 0pt 0pt 0pt 52px; margin: 0pt; background-color: rgb(247, 247, 255) ! important; }.number_show { padding-left: 52px ! important; list-style: decimal outside none ! important; }.number_show li { list-style: decimal outside none ! important; border-left: 1px dotted rgb(79, 129, 189); }.number_hide { padding-left: 0px ! important; list-style-type: none ! important; }.number_hide li { list-style-type: none ! important; border-left: 0px none; }ol.mainarea li { display: list-item ! important; font-size: 12px ! important; margin: 0pt ! important; line-height: 18px ! important; padding: 0pt 0pt 0pt 0px ! important; background-color: rgb(247, 247, 255) ! important; color: rgb(79, 129, 189); }ol.mainarea li pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap ol.mainarea li pre { white-space: pre-wrap; word-wrap: break-word; }ol.mainarea li pre.alt { background-color: rgb(247, 247, 255) ! important; }

显示行号 复制代码 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. 
    

.src_container { background-color: rgb(231, 229, 220); width: 99%; overflow: hidden; margin: 12px 0pt ! important; padding: 0px 3px 3px 0px; }.src_container .titlebar { background-color: rgb(212, 223, 255); border-width: 1px 1px 0pt; border-style: solid solid none; border-color: rgb(79, 129, 189) rgb(79, 129, 189) -moz-use-text-color; padding: 3px 24px; margin: 0pt; width: auto; line-height: 120%; overflow: hidden; text-align: left; font-size: 12px; }.src_container .toolbar { display: inline; font-weight: normal; font-size: 100%; float: right; color: rgb(0, 0, 255); text-align: left; overflow: hidden; }.toolbar span.button { display: inline; font-weight: normal; font-size: 100%; color: rgb(0, 0, 255); text-align: left; overflow: hidden; cursor: pointer; }.src_container div.clientarea { background-color: white; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; height: auto; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,courier,monospace,serif; }.src_container ol.mainarea { padding: 0pt 0pt 0pt 52px; margin: 0pt; background-color: rgb(247, 247, 255) ! important; }.number_show { padding-left: 52px ! important; list-style: decimal outside none ! important; }.number_show li { list-style: decimal outside none ! important; border-left: 1px dotted rgb(79, 129, 189); }.number_hide { padding-left: 0px ! important; list-style-type: none ! important; }.number_hide li { list-style-type: none ! important; border-left: 0px none; }ol.mainarea li { display: list-item ! important; font-size: 12px ! important; margin: 0pt ! important; line-height: 18px ! important; padding: 0pt 0pt 0pt 0px ! important; background-color: rgb(247, 247, 255) ! important; color: rgb(79, 129, 189); }ol.mainarea li pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap ol.mainarea li pre { white-space: pre-wrap; word-wrap: break-word; }ol.mainarea li pre.alt { background-color: rgb(247, 247, 255) ! important; }

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

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

默认绑定器扩展

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

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

源代码下载

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏