最近在研究 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 類從新寫一個特性,代碼以下:ide
/// <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(); }
這樣就能夠在實體模型的屬性上設置別名了。ui
/// <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 方法就能夠對模型綁定的屬性實現自定義了。orm
/// <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());