ASP.NET MVC 的自定义模型绑定

最近在研究 ASP.NET MVC 模型绑定,发现 DefaultModelBinder 有一个弊端,就是无法实现对浏览器请求参数的自定义,最初的想法是想为实体模型的属性设置特性(Attribute),然后通过取得设置的特性值对属性进行赋值,研究了好久 MVC 源码之后发现可以通过重写 DefaultModelBinder 的 BindProperty 方法可以达到预期的目的。

ASP.NET MVC 中有一个自定义模型绑定特性 CustomModelBinderAttribute,打算通过重写 CustomModelBinderAttribute 来对实体属性进行出来,实现如下:

[AttributeUsageAttribute(AttributeTargets.Class|AttributeTargets.Struct|AttributeTargets.Enum|AttributeTargets.Interface|AttributeTargets.Parameter, AllowMultiple = false,
	Inherited = false)]
public abstract class CustomModelBinderAttribute : Attribute

但是由于 CustomModelBinderAttribute 不支持对属性设置特性,所以只好继承 Attribute 类重新写一个特性,代码如下:

    /// <summary>
    /// 表示一个调用自定义模型联编程序的特性。
    /// </summary>
    [AttributeUsage(ValidTargets, AllowMultiple = false, Inherited = false)]
    public class PropertyModelBinderAttribute : Attribute
    {
        /// <summary>
        /// 指定此属性可以应用特性的应用程序元素。
        /// </summary>
        internal const AttributeTargets ValidTargets = AttributeTargets.Field | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Parameter;
        /// <summary>
        /// 声明属性名称。
        /// </summary>
        private string _propertyName = string.Empty;

        /// <summary>
        /// 获取或设置属性别名。
        /// </summary>
        public string PropertyName
        {
            get { return _propertyName; }
        }

        /// <summary>
        /// 使用指定的属性别名。
        /// </summary>
        /// <param name="propertyName">指定的属性别名。</param>
        public PropertyModelBinderAttribute(string propertyName)
        {
            _propertyName = propertyName;
        }

        /// <summary>
        /// 检索关联的模型联编程序。。
        /// </summary>
        /// <returns>对实现 System.Web.Mvc.IModelBinder 接口的对象的引用。</returns>
        public IModelBinder GetBinder()
        {
            return new PropertyModelBinder();
        }

这样就可以在实体模型的属性上设置别名了。

    /// <summary>
    /// 表示一个城市筛选实体对象模型。
    /// </summary>
    [ModelBinder(typeof(PropertyModelBinder))]
    public class CityFilteringModel : BaseEntityModel
    {

        /// <summary>
        /// 获取或设置城市英文名称。
        /// </summary>
        public string CityEnglishName { get; set; }
        /// <summary>
        /// 获取或设置城市编号。
        /// </summary>
        [PropertyModelBinder("cid")]
        public int CityId { get; set; }
        /// <summary>
        /// 获取或设置城市名称。
        /// </summary>
        [PropertyModelBinder("cname")]
        public string CityName { get; set; }
    }

最后听过重写 DefaultModelBinder 的 BindProperty 和 SetProperty 方法就可以对模型绑定的属性实现自定义了。

    /// <summary>
    /// 将浏览器请求映射到数据对象。
    /// </summary>
    public class PropertyModelBinder : DefaultModelBinder
    {

        /// <summary>
        /// 初始化 <see cref="PropertyModelBinder"/> 类的新实例。
        /// </summary>
        public PropertyModelBinder()
        {
        }

        /// <summary>
        /// 使用指定的控制器上下文和绑定上下文来绑定模型。
        /// </summary>
        /// <param name="controllerContext">运行控制器的上下文。</param>
        /// <param name="bindingContext">绑定模型的上下文。</param>
        /// <returns>已绑定的对象。</returns>
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var model = base.BindModel(controllerContext, bindingContext);
            if (model is BaseEntiryModel) ((BaseEntiryModel)model).BindModel(controllerContext, bindingContext);
            return model;
        }

        /// <summary>
        ///  使用指定的控制器上下文、绑定上下文、属性描述符和属性联编程序来返回属性值。
        /// </summary>
        /// <param name="controllerContext">运行控制器的上下文。</param>
        /// <param name="bindingContext">绑定模型的上下文。</param>
        /// <param name="propertyDescriptor">要访问的属性的描述符。</param>
        /// <param name="propertyBinder">一个对象,提供用于绑定属性的方式。</param>
        /// <returns>一个对象,表示属性值。</returns>
        protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
        {
            var value = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);

            return value;
        }

        /// <summary>
        /// 使用指定的控制器上下文、绑定上下文和指定的属性描述符来绑定指定的属性。
        /// </summary>
        /// <param name="controllerContext">运行控制器的上下文。</param>
        /// <param name="bindingContext">绑定模型的上下文。</param>
        /// <param name="propertyDescriptor">描述要绑定的属性。</param>
        protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
        {
            string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
            object propertyValue = null;

            if (propertyDescriptor.Attributes[typeof(PropertyModelBinderAttribute)] != null)
            {
                var attribute = (PropertyModelBinderAttribute)propertyDescriptor.Attributes[typeof(PropertyModelBinderAttribute)];
                string propertyName = attribute.PropertyName;
                var valueResult = bindingContext.ValueProvider.GetValue(propertyName);

                if (valueResult != null)
                    propertyValue = valueResult.AttemptedValue;
            }
            else
            {
                if (!bindingContext.ValueProvider.ContainsPrefix(fullPropertyKey))
                {
                    return;
                }
            }

            // call into the property‘s model binder
            IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
            object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);
            ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
            propertyMetadata.Model = originalPropertyValue;
            ModelBindingContext innerBindingContext = new ModelBindingContext()
            {
                ModelMetadata = propertyMetadata,
                ModelName = fullPropertyKey,
                ModelState = bindingContext.ModelState,
                ValueProvider = bindingContext.ValueProvider
            };
            object newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder);
            if (newPropertyValue == null)
            {
                newPropertyValue = propertyValue;
            }

            propertyMetadata.Model = newPropertyValue;
            // validation
            ModelState modelState = bindingContext.ModelState[fullPropertyKey];

            if (modelState == null || modelState.Errors.Count == 0)
            {
                if (OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, newPropertyValue))
                {
                    SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
                    OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
                }
            }
            else
            {
                SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);

                // Convert FormatExceptions (type conversion failures) into InvalidValue messages
                foreach (ModelError error in modelState.Errors.Where(err => String.IsNullOrEmpty(err.ErrorMessage) && err.Exception != null).ToList())
                {
                    for (Exception exception = error.Exception; exception != null; exception = exception.InnerException)
                    {
                        // We only consider "known" type of exception and do not make too aggressive changes here
                        if (exception is FormatException || exception is OverflowException)
                        {
                            string displayName = propertyMetadata.GetDisplayName();
                            string errorMessageTemplate = GetValueInvalidResource(controllerContext);
                            string errorMessage = String.Format(CultureInfo.CurrentCulture, errorMessageTemplate, modelState.Value.AttemptedValue, displayName);
                            modelState.Errors.Remove(error);
                            modelState.Errors.Add(errorMessage);
                            break;
                        }
                    }
                }
            }
            //base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
        }

        /// <summary>
        /// 使用指定的控制器上下文、绑定上下文和属性值来设置指定的属性。
        /// </summary>
        /// <param name="controllerContext">运行控制器的上下文。</param>
        /// <param name="bindingContext">绑定模型的上下文。</param>
        /// <param name="propertyDescriptor">描述要绑定的属性。</param>
        /// <param name="value">为属性设置的值。</param>
        protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
        {
            ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
            propertyMetadata.Model = value;
            string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyMetadata.PropertyName);

            if (value == null && bindingContext.ModelState.IsValidField(modelStateKey))
            {
                ModelValidator requiredValidator = ModelValidatorProviders.Providers.GetValidators(propertyMetadata, controllerContext).Where(v => v.IsRequired).FirstOrDefault();
                if (requiredValidator != null)
                {
                    foreach (ModelValidationResult validationResult in requiredValidator.Validate(bindingContext.Model))
                    {
                        bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
                    }
                }
            }

            bool isNullValueOnNonNullableType = value == null && !TypeAllowsNullValue(propertyDescriptor.PropertyType);

            if (!propertyDescriptor.IsReadOnly && !isNullValueOnNonNullableType)
            {
                try
                {
                    var typeValue = Convert.ChangeType(value, propertyDescriptor.PropertyType, CultureInfo.InvariantCulture);
                    propertyDescriptor.SetValue(bindingContext.Model, typeValue);
                }
                catch (Exception ex)
                {
                    if (bindingContext.ModelState.IsValidField(modelStateKey))
                    {
                        bindingContext.ModelState.AddModelError(modelStateKey, ex);
                    }
                }
            }

            if (isNullValueOnNonNullableType && bindingContext.ModelState.IsValidField(modelStateKey))
            {
                bindingContext.ModelState.AddModelError(modelStateKey, GetValueRequiredResource(controllerContext));
            }
        }

        /// <summary>
        /// 使用指定的控制器上下文和绑定上下文来返回模型的属性。
        /// </summary>
        /// <param name="controllerContext">运行控制器的上下文。</param>
        /// <param name="bindingContext">绑定模型的上下文。</param>
        /// <returns>属性描述符的集合。</returns>
        protected override PropertyDescriptorCollection GetModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            bindingContext.PropertyFilter = new Predicate<string>(pred);
            var values = base.GetModelProperties(controllerContext, bindingContext);
            return values;
        }

        /// <summary>
        /// 获取属性筛选器的判定对象。
        /// </summary>
        /// <param name="target">属性筛选器的属性。</param>
        /// <returns>一个布尔值。</returns>
        protected bool pred(string target)
        {
            return true;
        }

        #region Private ...

        /// <summary>
        /// 类型允许空值。
        /// </summary>
        /// <param name="type">指定的类型。</param>
        /// <returns>若类型值为空,则返回 true,否则返回 false。</returns>
        private static bool TypeAllowsNullValue(Type type)
        {
            return (!type.IsValueType || IsNullableValueType(type));
        }

        /// <summary>
        /// 是可为空值类型。
        /// </summary>
        /// <param name="type">指定的类型。</param>
        /// <returns>若类型值为空,则返回 true,否则返回 false。</returns>
        private static bool IsNullableValueType(Type type)
        {
            return Nullable.GetUnderlyingType(type) != null;
        }

        /// <summary>
        /// 获取价值无效的资源。
        /// </summary>
        /// <param name="controllerContext"></param>
        /// <returns></returns>
        private static string GetValueInvalidResource(ControllerContext controllerContext)
        {
            return GetUserResourceString(controllerContext, "PropertyValueInvalid") ?? "The value ‘{0}‘ is not valid for {1}.";
        }

        /// <summary>
        /// 获取价值所需的资源。
        /// </summary>
        /// <param name="controllerContext"></param>
        /// <returns></returns>
        private static string GetValueRequiredResource(ControllerContext controllerContext)
        {
            return GetUserResourceString(controllerContext, "PropertyValueRequired") ?? "A value is required.";
        }

        private static string GetUserResourceString(ControllerContext controllerContext, string resourceName)
        {
            string result = null;

            if (!String.IsNullOrEmpty(ResourceClassKey) && (controllerContext != null) && (controllerContext.HttpContext != null))
            {
                result = controllerContext.HttpContext.GetGlobalResourceObject(ResourceClassKey, resourceName, CultureInfo.CurrentUICulture) as string;
            }

            return result;
        }

        #endregion

    }

需要注意的是要在实体模型的类上设置 [ModelBinder(typeof(PropertyModelBinder))] 并且在 Global 中注册。

        ModelBinders.Binders.Clear();
        ModelBinders.Binders.Add(typeof(PropertySoukeModel), new PropertyModelBinder());

  

时间: 2024-08-07 06:03:18

ASP.NET MVC 的自定义模型绑定的相关文章

ASP.NET MVC学习之模型绑定(1)

一.前言 下面我们将开始学习模型绑定,通过下面的知识我们将能够理解ASP.NET MVC模型的模型绑定器是如何将http请求中的数据转换成模型的,其中我们重点讲述的是表单数据. 二.正文 1.简单类型绑定 学过一定ASP.NET MVC都会为这个特点所骄傲,就是能够将表单中与同名的参数映射,这相比操作ASP.NET控件来获取值轻便了许多,但是正如上面所说的那样要同名(大小写不区分),下面我们会讲述如何自己去指定. 首先我们在HomeController(如果不存在则创建)中获取表单中的值并显示:

ASP.NET MVC 4 (九) 模型绑定

模型绑定指的是MVC从浏览器发送的HTTP请求中为我们创建.NET对象,在HTTP请求和C#间起着桥梁的作用.模型绑定的一个最简单的例子是带参数的控制器action方法,比如我们注册这样的路径映射: routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Inde

[转]ASP.NET MVC 4 (九) 模型绑定

本文转自:http://www.cnblogs.com/duanshuiliu/p/3706701.html 模型绑定指的是MVC从浏览器发送的HTTP请求中为我们创建.NET对象,在HTTP请求和C#间起着桥梁的作用.模型绑定的一个最简单的例子是带参数的控制器action方法,比如我们注册这样的路径映射: routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: n

ASP.NET MVC学习之模型绑定(2)

3.手工调用模型绑定 很多情况下我们都是通过形参的方式接收来自http流中的数据,这看似是完美的,但是缺少了很多过程中的控制,所以我们就需要使用手工的方式进行绑定.下面我们通过一个例子来说明,首先打开Views/Home/Index.cshtml页面,并输入如下代码: 1 @{ 2 ViewBag.Title = "Index"; 3 } 4 5 @if (TempData.ContainsKey("msg")) 6 { 7 <h1> 8 @TempDa

asp.net mvc 中的模型绑定

在asp.net MVC 应用程序, 表单提交的数据通过模型绑定将数据从View传递到控制器中.使用模型绑定显然比Request.Form["Price"] ,Decimal.Parse(Request.Form["Price"] )还需要手动类型转换要要方便很多. 模型绑定有两种,隐式绑定和显式绑定. 1.隐式模型绑定.先将表单值转化为CLR 类型,然后以表单字段name名称,value值参数传递到Action中.为了防止过度提交,使用Bind(Include)属

ASP.NET MVC学习之模型验证篇

一.学习前的一句话 在这里要先感谢那些能够点开我随笔的博友们.慢慢的已经在博客园中度过一年半了,伊始只是将博客园作为自己学习的记录本一样使用,也不敢将自己的随笔发表到博客园首页,生怕自己的技艺不高,反倒成了笑话.但是随着时间的推移,再也按捺不住这种想法,于是就写了一篇随笔发表到博客园首页.让我意想不到的是有许多人都看了,而且也留下了评论.这让我鼓起勇气写了第二.三.四篇.到现在的连载,这里我希望那些从未发表过随笔的人可以尝试去发表,在这里他人不会嘲讽你,而是会给你更好的建议.说了这么多下面我们继

自定义模型绑定系统

一.创建自定义的值提供器 1.通过创建自定义的值提供器,我们可以把自己的数据源添加到模型绑定过程.而值提供器需要实现IValueProvider接口.下面是此接口的定义 namespace System.Web.Mvc { /// <summary> /// Defines the methods that are required for a value provider in ASP.NET MVC. /// </summary> public interface IValue

ASP.NET MVC中的模型装配 封装方法 非常好用

我们知道在asp.net mvc中 视图可以绑定一个实体模型 然后我们三层架构中也有一个model模型 但是这两个很多时候却是不一样的对象来的 就拿微软的官方mvc例子来说明 微软的视图实体中 有loginmodel 有registermodel 等等 这些视图模型 都只是占用户实体的某几个字段而已, 那么这个时候 我们可以用下面两个方法来转换 自动赋值两个对象的 protected T AssembleInfo<T, T2>(T2 model)        {            Pro

原来自定义模型绑定器还可以这么玩

我有个功能,需要绑定List<User>.我做了个测试:试图: @using (Html.BeginForm("save", "user")) { <div> 用户名:<input type="text" name="UserName" /><br /> 用户名:<input type="text" name="UserName" /