最近在研究 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());

  

ASP.NET MVC 的自定义模型属性别名绑定的更多相关文章

  1. ASP.NET MVC 下自定义模型绑定,去除字符串类型前后的空格

    直接贴代码了: SkyModelBinder.cs using System.ComponentModel; using System.Linq; using System.Web.Mvc; name ...

  2. Asp.net Mvc 中的模型绑定

    asp.net mvc中的模型绑定可以在提交http请求的时候,进行数据的映射. 1.没有模型绑定的时候 public ActionResult Example0() { ) { string id ...

  3. ASP.NET MVC学习之模型验证详解

    ASP.NET MVC学习之模型验证篇 2014-05-28 11:36 by y-z-f, 6722 阅读, 13 评论, 收藏, 编辑 一.学习前的一句话 在这里要先感谢那些能够点开我随笔的博友们 ...

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

    一.前言 下面我们将开始学习模型绑定,通过下面的知识我们将能够理解ASP.NET MVC模型的模型绑定器是如何将http请求中的数据转换成模型的,其中我们重点讲述的是表单数据. 二.正文 1.简单类型 ...

  5. [ASP.NET MVC]如何定制Numeric属性/字段验证消息

    原文:[ASP.NET MVC]如何定制Numeric属性/字段验证消息 对于一个Numeric属性/字段,ASP.NET MVC会自动进行数据类型的验证(客户端验证),以确保输入的是一个有效的数字, ...

  6. 转:【译】Asp.net MVC 利用自定义RouteHandler来防止图片盗链

    [译]Asp.net MVC 利用自定义RouteHandler来防止图片盗链   你曾经注意过在你服务器请求日志中多了很多对图片资源的请求吗?这可能是有人在他们的网站中盗链了你的图片所致,这会占用你 ...

  7. 【转】Asp.net MVC 通过自定义ControllerFactory实现构造器注入(重写DefaultControllerFactory)

    [转]Asp.net MVC 通过自定义ControllerFactory实现构造器注入 一.重写ControllerFactory的GetControllerInstance ControllerF ...

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

    一.学习前的一句话 在这里要先感谢那些能够点开我随笔的博友们.慢慢的已经在博客园中度过一年半了,伊始只是将博客园作为自己学习的记录本一样使用,也不敢将自己的随笔发表到博客园首页,生怕自己的技艺不高,反 ...

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

    3.手工调用模型绑定 很多情况下我们都是通过形参的方式接收来自http流中的数据,这看似是完美的,但是缺少了很多过程中的控制,所以我们就需要使用手工的方式进行绑定.下面我们通过一个例子来说明,首先打开 ...

随机推荐

  1. Dynamic CRM 2013学习笔记(三)快速创建实体 EntityCreater

    一.实体简介 实体用于在 Microsoft Dynamics CRM 中建立业务数据模型和管理业务数据.例如,可以使用客户.市场活动和事件(案例)等实体跟踪和支持销售.市场营销和服务活动.实体具有一 ...

  2. 实验五 含有控制信号的计数器VHDL设计

    一.实验目的 学习计数器的设计.仿真和硬件测试,进一步熟悉VHDL设计技术. 二.实验仪器与器材 计算机1台,GW48-PK2S实验箱1台,QuartusⅡ6.0 1套. 三.实验 1. 基本命题 在 ...

  3. memcache(一)概述

    概述 memcache是一种支持分布式的缓存系统,基于网络连接(当然它也可以使用localhost)方式完成服务,本身它是一个独立于应用的程序或守护进程(Daemon方式). 本地缓存 memcach ...

  4. 字符串匹配算法之SimHash算法

    SimHash算法 由于实验室和互联网基本没啥关系,也就从来没有关注过数据挖掘相关的东西.在实际工作中,第一次接触到匹配和聚类等工作,虽然用一些简单的匹配算法可以做小数据的聚类,但数据量达到一定的时候 ...

  5. AngularJs 基础(60分钟入门)

    AngularJS 是一个创建富客户端应用的JavaScript MVC框架.你仍然需要具有服务端后台,但大多数的用户交互逻辑将放到客户端上处理.它可以创建单页的应用程序,一个页面的应用仅仅需要HTM ...

  6. 自制一个能显示helloworld的最简单OS

    <自己动手写操作系统> org 07c00h mov ax,cs mov ds,ax mov es,ax call DispStr jmp $ DispStr: mov ax,BootMe ...

  7. ios 学习常用网站

    一 ,别的blog上的内容 iPhone 4 与iPad开发基础教程 一.邮件列表: 1.cocoa-dev http://lists.apple.com/mailman/listinfo/cocoa ...

  8. [读书笔记]C#学习笔记八:StringBuilder与String详解及参数传递问题剖析

    前言 上次在公司开会时有同事分享windebug的知识, 拿的是string字符串Concat拼接 然后用while(true){}死循环的Demo来讲解.其中有提及string操作大量字符串效率低下 ...

  9. Leetcode 19 Remove Nth Node From End of List 链表

    删除从后往前数的第n个节点 我的做法是将两个指针first,second 先将first指向第n-1个,然后让first和second一起指向他们的next,直到first->next-> ...

  10. Redis info参数总结(转)

    Redis官网对 info 已经讲解的比较清楚的,参考文档 . 可以看到,info的输出结果是分几块的,有Servers.Clients.Memory等等,通过info后面接这些参数,可以指定输出某一 ...