模型绑定

  我们知道,一个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 });
            }
        }
    }

 

posted @ 2019-06-13 16:44  柠檬笔记  阅读(280)  评论(0编辑  收藏  举报