ASP.NET MVC 的自定义模型属性别名绑定
最近在研究 ASP.NET MVC 模型綁定,發(fā)現(xiàn) DefaultModelBinder 有一個(gè)弊端,就是無法實(shí)現(xiàn)對瀏覽器請求參數(shù)的自定義,最初的想法是想為實(shí)體模型的屬性設(shè)置特性(Attribute),然后通過取得設(shè)置的特性值對屬性進(jìn)行賦值,研究了好久 MVC 源碼之后發(fā)現(xiàn)可以通過重寫 DefaultModelBinder 的 BindProperty 方法可以達(dá)到預(yù)期的目的。
ASP.NET MVC 中有一個(gè)自定義模型綁定特性 CustomModelBinderAttribute,打算通過重寫 CustomModelBinderAttribute 來對實(shí)體屬性進(jìn)行出來,實(shí)現(xiàn)如下:
[AttributeUsageAttribute(AttributeTargets.Class|AttributeTargets.Struct|AttributeTargets.Enum|AttributeTargets.Interface|AttributeTargets.Parameter, AllowMultiple = false,
Inherited = false)]
public abstract class CustomModelBinderAttribute : Attribute
但是由于 CustomModelBinderAttribute 不支持對屬性設(shè)置特性,所以只好繼承 Attribute 類重新寫一個(gè)特性,代碼如下:
/// <summary>
/// 表示一個(gè)調(diào)用自定義模型聯(lián)編程序的特性。
/// </summary>
[AttributeUsage(ValidTargets, AllowMultiple = false, Inherited = false)]
public class PropertyModelBinderAttribute : Attribute
{
/// <summary>
/// 指定此屬性可以應(yīng)用特性的應(yīng)用程序元素。
/// </summary>
internal const AttributeTargets ValidTargets = AttributeTargets.Field | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Parameter;
/// <summary>
/// 聲明屬性名稱。
/// </summary>
private string _propertyName = string.Empty; /// <summary>
/// 獲取或設(shè)置屬性別名。
/// </summary>
public string PropertyName
{
get { return _propertyName; }
} /// <summary>
/// 使用指定的屬性別名。
/// </summary>
/// <param name="propertyName">指定的屬性別名。</param>
public PropertyModelBinderAttribute(string propertyName)
{
_propertyName = propertyName;
} /// <summary>
/// 檢索關(guān)聯(lián)的模型聯(lián)編程序。。
/// </summary>
/// <returns>對實(shí)現(xiàn) System.Web.Mvc.IModelBinder 接口的對象的引用。</returns>
public IModelBinder GetBinder()
{
return new PropertyModelBinder();
}
這樣就可以在實(shí)體模型的屬性上設(shè)置別名了。
/// <summary>
/// 表示一個(gè)城市篩選實(shí)體對象模型。
/// </summary>
[ModelBinder(typeof(PropertyModelBinder))]
public class CityFilteringModel : BaseEntityModel
{ /// <summary>
/// 獲取或設(shè)置城市英文名稱。
/// </summary>
public string CityEnglishName { get; set; }
/// <summary>
/// 獲取或設(shè)置城市編號。
/// </summary>
[PropertyModelBinder("cid")]
public int CityId { get; set; }
/// <summary>
/// 獲取或設(shè)置城市名稱。
/// </summary>
[PropertyModelBinder("cname")]
public string CityName { get; set; }
}
最后聽過重寫 DefaultModelBinder 的 BindProperty 和 SetProperty 方法就可以對模型綁定的屬性實(shí)現(xiàn)自定義了。
/// <summary>
/// 將瀏覽器請求映射到數(shù)據(jù)對象。
/// </summary>
public class PropertyModelBinder : DefaultModelBinder
{ /// <summary>
/// 初始化 <see cref="PropertyModelBinder"/> 類的新實(shí)例。
/// </summary>
public PropertyModelBinder()
{
} /// <summary>
/// 使用指定的控制器上下文和綁定上下文來綁定模型。
/// </summary>
/// <param name="controllerContext">運(yùn)行控制器的上下文。</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>
/// 使用指定的控制器上下文、綁定上下文、屬性描述符和屬性聯(lián)編程序來返回屬性值。
/// </summary>
/// <param name="controllerContext">運(yùn)行控制器的上下文。</param>
/// <param name="bindingContext">綁定模型的上下文。</param>
/// <param name="propertyDescriptor">要訪問的屬性的描述符。</param>
/// <param name="propertyBinder">一個(gè)對象,提供用于綁定屬性的方式。</param>
/// <returns>一個(gè)對象,表示屬性值。</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">運(yùn)行控制器的上下文。</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>
/// 使用指定的控制器上下文、綁定上下文和屬性值來設(shè)置指定的屬性。
/// </summary>
/// <param name="controllerContext">運(yùn)行控制器的上下文。</param>
/// <param name="bindingContext">綁定模型的上下文。</param>
/// <param name="propertyDescriptor">描述要綁定的屬性。</param>
/// <param name="value">為屬性設(shè)置的值。</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">運(yùn)行控制器的上下文。</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>一個(gè)布爾值。</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>
/// 獲取價(jià)值無效的資源。
/// </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>
/// 獲取價(jià)值所需的資源。
/// </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 }
需要注意的是要在實(shí)體模型的類上設(shè)置 [ModelBinder(typeof(PropertyModelBinder))] 并且在 Global 中注冊。
ModelBinders.Binders.Clear();
ModelBinders.Binders.Add(typeof(PropertyModelBinder), new PropertyModelBinder());
總結(jié)
以上是生活随笔為你收集整理的ASP.NET MVC 的自定义模型属性别名绑定的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: bppm与AD域集成
- 下一篇: asp.net ajax控件工具集 Au