zoukankan      html  css  js  c++  java
  • 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(PropertyModelBinder), new PropertyModelBinder());
    

      

  • 相关阅读:
    VIO(4)—— 基于滑动窗口算法的 VIO 系统:可观性和 一致性
    矩阵零空间的含义和物理意义
    Spring Boot 2.5.1 发布!我真跟不上了。。。
    Redis 是并发安全的吗?你确定?
    Spring Boot 集成 Apollo 配置中心,真香、真强大!
    Spring Boot 接入支付宝,实战来了!
    微服务必须具备的 3 个基本功能!
    如何加速 Nginx 的服务响应?
    golang在日志中打印堆栈信息
    Maven
  • 原文地址:https://www.cnblogs.com/weisenz/p/4610273.html
Copyright © 2011-2022 走看看