ASP.NET MVC 的自定义模型属性别名绑定
最近在研究 ASP.NET MVC 模型绑定,发现 DefaultModelBinder 有一个弊端,就是无法实现对浏览器请求参数的自定义,最初的想法是想为实体模型的属性设置特性(Attribute),然后通过取得设置的特性值对属性进行赋值,研究了好久 MVC 源码之后发现可以通过重写 DefaultModelBinder 的 BindProperty 方法可以达到预期的目的。
ASP.NET MVC 中有一个自定义模型绑定特性 CustomModelBinderAttribute,打算通过重写 CustomModelBinderAttribute 来对实体属性进行出来,实现如下:
1 2 3 | [AttributeUsageAttribute(AttributeTargets.Class|AttributeTargets.Struct|AttributeTargets.Enum|AttributeTargets.Interface|AttributeTargets.Parameter, AllowMultiple = false , Inherited = false )] public abstract class CustomModelBinderAttribute : Attribute |
但是由于 CustomModelBinderAttribute 不支持对属性设置特性,所以只好继承 Attribute 类重新写一个特性,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | /// <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(); } |
这样就可以在实体模型的属性上设置别名了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /// <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 方法就可以对模型绑定的属性实现自定义了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | /// <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 中注册。
1 2 | ModelBinders.Binders.Clear(); ModelBinders.Binders.Add( typeof (PropertyModelBinder), new PropertyModelBinder()); |
分类:
DotNet
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库