模型绑定
Posted jesen1315
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模型绑定相关的知识,希望对你有一定的参考价值。
我们知道,一个Asp.Net MVC应用的请求总是指向定义在目标Controller类中的某个Action,当Controller被激活之后,这个Action方法会被执行。而大部分的Action方法都有参数,所以MVC在调用目标Action之前必须从请求中提取相应的数据并以此来生成参数,这个过程就是“模型绑定”。
MVC利用一个名为ValueProvider的对象来为Model绑定原始数据,该类实现了IValueProvider接口。
namespace System.Web.Mvc // // 摘要: // 定义 ASP.NET MVC 中的值提供程序所需的方法。 public interface IValueProvider // // 摘要: // 确定集合是否包含指定的前缀。 // // 参数: // prefix: // 要搜索的前缀。 // // 返回结果: // 如果集合包含指定的前缀,则为 true;否则为 false。 bool ContainsPrefix(string prefix); // // 摘要: // 使用指定的键来检索值对象。 // // 参数: // key: // 要检索的值对象的键。 // // 返回结果: // 指定键所对应的值对象;如果找不到该键,则为 null。 ValueProviderResult GetValue(string key);
namespace System.Web.Mvc // // 摘要: // 表示将一个值(如窗体发布或查询字符串中的值)绑定到操作方法参数属性或绑定到该参数本身的结果。 public class ValueProviderResult // // 摘要: // 使用指定的原始值、尝试的值和区域性信息初始化 System.Web.Mvc.ValueProviderResult 类的新实例。 // // 参数: // rawValue: // 原始值。 // // attemptedValue: // 尝试的值。 // // culture: // 区域性。 public ValueProviderResult(object rawValue, string attemptedValue, CultureInfo culture); // // 摘要: // 初始化 System.Web.Mvc.ValueProviderResult 类的新实例。 protected ValueProviderResult(); // // 摘要: // 获取或设置要转换为字符串,以便显示的原始值。 // // 返回结果: // 原始值。 public string AttemptedValue get; protected set; // // 摘要: // 获取或设置区域性。 // // 返回结果: // 区域性。 public CultureInfo Culture get; protected set; // // 摘要: // 获取或设置值提供程序所提供的原始值。 // // 返回结果: // 原始值。 public object RawValue get; protected set; // // 摘要: // 将此结果封装的值转换为指定的类型。 // // 参数: // type: // 目标类型。 // // 返回结果: // 转换后的值。 // // 异常: // T:System.ArgumentNullException: // type 参数为 null。 public object ConvertTo(Type type); // // 摘要: // 使用指定的区域性信息将此结果封装的值转换为指定的类型。 // // 参数: // type: // 目标类型。 // // culture: // 要在转换中使用的区域性。 // // 返回结果: // 转换后的值。 // // 异常: // T:System.ArgumentNullException: // type 参数为 null。 public virtual object ConvertTo(Type type, CultureInfo culture);
同时MVC提供了一系列的ValueProvider来处理相应的绑定,当请求过来的时候MVC会通过ValueProviderFactories注册ValueProviderFactory,再创建ValueProvider。
虽然MVC提供的ValueProviderFactory基本上满足大部分Model绑定需求,但是在一些特殊场景下我们可以自定义ValueProviderFactory。
using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.Linq; using System.Net.Http.Headers; using System.Web; using System.Web.Http.Controllers; using System.Web.Mvc; namespace Jesen.Web.Mvc /// <summary> /// 自定义http请求头的ValueProviderFactory来绑定Header的值 /// </summary> public class HttpHeaderValueProviderFactory : ValueProviderFactory public override IValueProvider GetValueProvider(ControllerContext controllerContext) NameValueCollection requestData = new NameValueCollection(); NameValueCollection headers = controllerContext.RequestContext.HttpContext.Request.Headers; foreach (string key in headers.Keys) requestData.Add(key.Replace("-", ""), headers[key]); return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture); public class CustomHttpHeaders public string Connection get; set; public string Accept get; set; public string AcceptCharset get; set; public string AcceptLanguage get; set; public string Host get; set; public string UserAgent get; set;
public class CustomController : Controller // GET: Custom public ActionResult Index(CustomHttpHeaders headers) return View(headers);
然后需要在 Application_Start 把我们自定义的ValueProvider添加到ValueProviderFactories中
ValueProviderFactories.Factories.Add(new HttpHeaderValueProviderFactory());
访问该Action得到结果
ValueProvider为构建参数提供了原始数据,而真正的模型绑定工作则是由ModelBinder来完成的。ModelBinder是Model绑定系统最为核心的一个对象。它实现了IModelBinder接口,该接口提供了一个BindModel方法,绑定的数据对象就是通过执行这个方法获得的。
MVC提供了ByteArrayModelBinder、LinqBinaryModelBinder、HttpPostedFileBaseModelBinder、CancellationTokenModelBinder四种针对模型绑定的特殊类型,同时也提供了一个默认的DefaultModelBinder,当根据目标数据类型无法找到一个匹配的ModelBinder时,最终会选择DefaultModelBinder。
和ValueProvider一样,MVC也提供了ModelBinderFactories来注册ModelBinderFactory,然后创建ModelBinder,我们也可以自定义我们自己的ModelBinder。
using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.Linq; using System.Net.Http.Headers; using System.Web; using System.Web.Http.Controllers; using System.Web.Mvc; namespace Jesen.Web.Mvc public class Person public int Id get; set; public string Name get; set; /// <summary> /// 自定义ModelBinder /// </summary> public class PersonModelBinder : IModelBinder public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) return new Person(); /// <summary> /// 自定义ModelBinderProvider来控制采用的ModelBinder /// </summary> public class PersonModelBinderProvider : IModelBinderProvider public IModelBinder GetBinder(Type modelType) if (typeof(Person).IsAssignableFrom(modelType)) return new PersonModelBinder(); return null;
public class CustomController : Controller // GET: Custom public ActionResult Index(Person p) ControllerDescriptor controllerDescriptor = new ReflectedControllerDescriptor(typeof(CustomController)); ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(ControllerContext, "Index"); Dictionary<ParameterDescriptor, IModelBinder> binders = new Dictionary<ParameterDescriptor, IModelBinder>(); foreach(ParameterDescriptor parameterDescriptor in actionDescriptor.GetParameters()) binders.Add(parameterDescriptor, this.GetModelBinder(parameterDescriptor)); return View(binders); /// <summary> /// 调用ActionInvoker的GetModelBinder方法得到针对指定参数的ModelBinder对象 /// </summary> /// <param name="parameterDescriptor"></param> /// <returns></returns> private IModelBinder GetModelBinder(ParameterDescriptor parameterDescriptor) MethodInfo getModelBinder = typeof(ControllerActionInvoker).GetMethod("GetModelBinder", BindingFlags.Instance | BindingFlags.NonPublic); return (IModelBinder)getModelBinder.Invoke(this.ActionInvoker, new object[] parameterDescriptor );
@model Dictionary<ParameterDescriptor, IModelBinder> @ Layout = null; <html> <body> @foreach(var item in Model) <p>@item.Key.ParameterName</p> <p>@item.Value.GetType().Name</p> </body> </html>
同样需要在Application_Start中添加自定义的ModelBinderFactory
ModelBinderProviders.BinderProviders.Add(new PersonModelBinderProvider());
运行代码可以看到模型绑定器变成了我们定义的PersonModelBinder了
除了上述方法自定义ModelBinderProvider来为某种具体的数据类型提供对应的ModelBinder外,还可以使用ModelBinders静态类型直接注册数据类型与对应ModelBinder之间的匹配关系,如果同时指定了这两种方式,那么ModelBinderProvider的方式优先级更高。
ModelBinderProviders.BinderProviders.Add(new PersonModelBinderProvider()); ModelBinders.Binders.Add(typeof(Person), new PersonModelBinder());
MVC提供的第三种定义匹配ModelBinder的方式可以通过ModelBinderAttribute来实现
[ModelBinder(typeof(PersonModelBinder))] public class Person public int Id get; set; public string Name get; set;
public ActionResult Index([ModelBinder(typeof(PersonModelBinder))] Person p) ControllerDescriptor controllerDescriptor = new ReflectedControllerDescriptor(typeof(CustomController)); ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(ControllerContext, "Index"); Dictionary<ParameterDescriptor, IModelBinder> binders = new Dictionary<ParameterDescriptor, IModelBinder>(); foreach(ParameterDescriptor parameterDescriptor in actionDescriptor.GetParameters()) binders.Add(parameterDescriptor, this.GetModelBinder(parameterDescriptor)); return View(binders);
那么如果同时采用了三种方式,它们的优先级是: 参数中使用特性 --> ModelProviderFactory --> 静态ModelBinders --> 类上的特性。
最后,笔者在工作中开发App接口时,采用RSA方式对参数进行加密,自定义了自己的ModelBinder。其代码如下:
public class EncryptedModelBinder : DefaultModelBinder public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) if (controllerContext.RouteData.DataTokens["area"] != null) if (String.Equals("Examination", controllerContext.RouteData.DataTokens["area"].ToString(), StringComparison.OrdinalIgnoreCase)) return base.BindModel(controllerContext, bindingContext); RuntimeHelpers.EnsureSufficientExecutionStack(); if (bindingContext == null) throw new ArgumentNullException("bindingContext"); if (string.IsNullOrEmpty(bindingContext.ModelName) || bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); object rawValue= (valueProviderResult.RawValue as Array).GetValue(0); string decryptedString = null; if ((String.Equals("Product", controllerContext.RouteData.Values["controller"].ToString(), StringComparison.OrdinalIgnoreCase) && string.Equals("Validate", controllerContext.RouteData.Values["action"].ToString(), StringComparison.OrdinalIgnoreCase)) || (String.Equals("logistics", controllerContext.RouteData.Values["controller"].ToString(), StringComparison.OrdinalIgnoreCase) && string.Equals("enquiryvalidate", controllerContext.RouteData.Values["action"].ToString(), StringComparison.OrdinalIgnoreCase)) || (String.Equals("logistics", controllerContext.RouteData.Values["controller"].ToString(), StringComparison.OrdinalIgnoreCase) && string.Equals("enquiry", controllerContext.RouteData.Values["action"].ToString(), StringComparison.OrdinalIgnoreCase))) decryptedString = rawValue.ToString(); else if ((String.Equals("RealName", controllerContext.RouteData.Values["controller"].ToString(), StringComparison.OrdinalIgnoreCase) && string.Equals("Input", controllerContext.RouteData.Values["action"].ToString(), StringComparison.OrdinalIgnoreCase)) || (String.Equals("Home", controllerContext.RouteData.Values["controller"].ToString(), StringComparison.OrdinalIgnoreCase) && string.Equals("ReplyLeaveMessage", controllerContext.RouteData.Values["action"].ToString(), StringComparison.OrdinalIgnoreCase))) if (String.Equals("PhotoSelf", bindingContext.ModelName, StringComparison.OrdinalIgnoreCase) || String.Equals("PhotoIdFront", bindingContext.ModelName, StringComparison.OrdinalIgnoreCase) || String.Equals("PhotoIdBack", bindingContext.ModelName, StringComparison.OrdinalIgnoreCase) || String.Equals("File", bindingContext.ModelName, StringComparison.OrdinalIgnoreCase)) decryptedString = rawValue.ToString(); else decryptedString = RSACryption.RSADecrypt(RSACryption.strPrivateKey, rawValue.ToString(), "utf-8");//rawValue + ""; //解密,update by hujs 2016-12-01 //else if (String.Equals("Examination", controllerContext.RouteData.DataTokens["area"].ToString(), StringComparison.OrdinalIgnoreCase)) // // decryptedString = rawValue.ToString(); // else if (String.Equals("Home", controllerContext.RouteData.Values["controller"].ToString(), StringComparison.OrdinalIgnoreCase) && string.Equals("InfoModify", controllerContext.RouteData.Values["action"].ToString(), StringComparison.OrdinalIgnoreCase) && bindingContext.ModelName == "Signature") decryptedString = rawValue.ToString(); else decryptedString = RSACryption.RSADecrypt(RSACryption.strPrivateKey, rawValue.ToString(), "utf-8");//rawValue + ""; //解密,update by hujs 2016-12-01 //string decryptedString = RSACryption.RSADecrypt(RSACryption.strPrivateKey, rawValue.ToString(), "utf-8");//rawValue + ""; //解密,update by hujs 2016-12-01 valueProviderResult = new ValueProviderResult(decryptedString, decryptedString, valueProviderResult.Culture); if (valueProviderResult != null) return BindSimpleModel(controllerContext, bindingContext, valueProviderResult); return base.BindModel(controllerContext, bindingContext); internal object BindSimpleModel(ControllerContext controllerContext, ModelBindingContext bindingContext, ValueProviderResult valueProviderResult) bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); if (bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue)) return valueProviderResult.RawValue; if (bindingContext.ModelType != typeof(string)) if (bindingContext.ModelType.IsArray) return ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType); Type type =ExtractGenericInterface(bindingContext.ModelType, typeof(IEnumerable<>)); if (type != null) object o = this.CreateModel(controllerContext, bindingContext, bindingContext.ModelType); Type collectionType = type.GetGenericArguments()[0]; Type destinationType = collectionType.MakeArrayType(); object newContents = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, destinationType); if (typeof(ICollection<>).MakeGenericType(new Type[] collectionType ).IsInstanceOfType(o)) CollectionHelpers.ReplaceCollection(collectionType, o, newContents); return o; return ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType); private static object ConvertProviderResult(ModelStateDictionary modelState, string modelStateKey, ValueProviderResult valueProviderResult, Type destinationType) try return valueProviderResult.ConvertTo(destinationType); catch (Exception exception) modelState.AddModelError(modelStateKey, exception); return null; public static Type ExtractGenericInterface(Type queryType, Type interfaceType) if (MatchesGenericType(queryType, interfaceType)) return queryType; return MatchGenericTypeFirstOrDefault(queryType.GetInterfaces(), interfaceType); private static bool MatchesGenericType(Type type, Type matchType) return (type.IsGenericType && (type.GetGenericTypeDefinition() == matchType)); private static Type MatchGenericTypeFirstOrDefault(Type[] types, Type matchType) for (int i = 0; i < types.Length; i++) Type type = types[i]; if (MatchesGenericType(type, matchType)) return type; return null; private static class CollectionHelpers private static readonly MethodInfo _replaceCollectionMethod = typeof(EncryptedModelBinder.CollectionHelpers).GetMethod("ReplaceCollectionImpl", BindingFlags.NonPublic | BindingFlags.Static); private static readonly MethodInfo _replaceDictionaryMethod = typeof(EncryptedModelBinder.CollectionHelpers).GetMethod("ReplaceDictionaryImpl", BindingFlags.NonPublic | BindingFlags.Static); [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] public static void ReplaceCollection(Type collectionType, object collection, object newContents) _replaceCollectionMethod.MakeGenericMethod(new Type[] collectionType ).Invoke(null, new object[] collection, newContents );
以上是关于模型绑定的主要内容,如果未能解决你的问题,请参考以下文章
如何将 View 类中的代码片段移动到 OnAppearing() 方法?