ASP.NET MVC中的模型绑定
模型绑定的本质
任何控制器方法的执行都受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中的模型绑定的更多相关文章
- Asp.net Mvc 中的模型绑定
asp.net mvc中的模型绑定可以在提交http请求的时候,进行数据的映射. 1.没有模型绑定的时候 public ActionResult Example0() { ) { string id ...
- ASP.NET MVC学习之模型绑定(1)
一.前言 下面我们将开始学习模型绑定,通过下面的知识我们将能够理解ASP.NET MVC模型的模型绑定器是如何将http请求中的数据转换成模型的,其中我们重点讲述的是表单数据. 二.正文 1.简单类型 ...
- ASP.NET MVC中的模型装配 封装方法 非常好用
下面说一下 我们知道在asp.net mvc中 视图可以绑定一个实体模型 然后我们三层架构中也有一个model模型 但是这两个很多时候却是不一样的对象来的 就拿微软的官方mvc例子来说明 微软的视图实 ...
- ASP.NET Core 中的模型绑定
微软官方文档:ASP.NET Core 中的模型绑定 Route 是通过MVC Route URL取值. 如:http://localhost:5000/Home/Index/2,id取出的值就会是2 ...
- ASP.NET MVC学习之模型绑定(2)
3.手工调用模型绑定 很多情况下我们都是通过形参的方式接收来自http流中的数据,这看似是完美的,但是缺少了很多过程中的控制,所以我们就需要使用手工的方式进行绑定.下面我们通过一个例子来说明,首先打开 ...
- [转]ASP.NET MVC 4 (九) 模型绑定
本文转自:http://www.cnblogs.com/duanshuiliu/p/3706701.html 模型绑定指的是MVC从浏览器发送的HTTP请求中为我们创建.NET对象,在HTTP请求和C ...
- ASP.NET MVC 4 (九) 模型绑定
模型绑定指的是MVC从浏览器发送的HTTP请求中为我们创建.NET对象,在HTTP请求和C#间起着桥梁的作用.模型绑定的一个最简单的例子是带参数的控制器action方法,比如我们注册这样的路径映射: ...
- ASP.NET MVC 下自定义模型绑定,去除字符串类型前后的空格
直接贴代码了: SkyModelBinder.cs using System.ComponentModel; using System.Linq; using System.Web.Mvc; name ...
- ASP.NET MVC中默认Model Binder绑定Action参数为List、Dictionary等集合的实例
在实际的ASP.NET mvc项目开发中,有时会遇到一个参数是一个List.Dictionary等集合类型的情况,默认的情况ASP.NET MVC框架是怎么为我们绑定ASP.NET MVC的Actio ...
随机推荐
- For循环复杂练习
for是循环当中经常用到的一个结构,练熟了才可以. 练习-需求描述: 在控制台打印以下形式的字符: * * * * * * * * * * * * * * * 思路,首先分析需求的规律 1.首先分析需 ...
- BZOJ 1071 [SCOI2007]组队
1071: [SCOI2007]组队 Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 1330 Solved: 417[Submit][Status][ ...
- 【转】 linux iio子系统
原文网址:http://blog.csdn.net/tsy20100200/article/details/47101661 最近由于工作的需要,接触了Linux iio子系统,对于这个目录其实以前是 ...
- (转载)PHP使用empty检查函数返回结果时报Fatal error: Can't use function return value in write context的问题
(转载)http://be-evil.org/post-153.html PHP开发时,当你使用empty检查一个函数返回的结果时会报错:Fatal error: Can't use function ...
- GeoPandas官方中文文档--译著
译自GeoPandas 0.1.0 文档(原版译著,有错误欢迎交流,转载请注明) GeoPandas是一个开源项目,它的目的是使得在Python下更方便的处理地理空间数据.GeoPandas扩展了pa ...
- C++引用变量学习
版权所有,转载请注明来源 (1)reference variable(rv) 主要用处是作为方程的形式参数,使用rv 可以直接对原数据进行操作而不是该数据的拷贝,节省了时间和空间,尤其是对于结构体以及 ...
- Jenkins 四: 启动关闭以及重启jenkins
启动 1. 在桌面新建一个jenkins.bat文件.内容如下: cd /d %JENKINS_HOME% java -jar %JENKINS_HOME%\jenkins.war --httpPor ...
- F - Prime Path
题目大意: 素数路径 估计看数据就明白这道题什么意思了......给两个素数,都是四位数的素数,并且没有前导0,现在需要经过一种变换把一个素数转换成另一个,当然这种转换是有规则的,规则就是每次只能改变 ...
- the identity used to sign the executable is no longer valid.解决方法
the identity used to sign the executable is no longer valid.解决方法 一.重新下载Provisioning Profile 1.到devel ...
- Windows7下32位IE异常不能打开解决方法
今天更新了Update及安装了一些软件,重启电脑后发现32位IE不能正常打开,而64位IE正常. 错误信息如下: 问题签名: 问题事件名称: BEX 应用程序名: iexplore.exe 应用 ...