响应 michaelalm 的回答和要求 - 这就是我最终所做的。由于 Nathan 建议的解决方案之一会奏效,因此我主要出于礼貌而将原始答案打勾。
它的输出是 DefaultModelBinder 类的替代品,您可以全局注册(从而允许所有模型类型利用别名)或选择性地继承自定义模型绑定器。
一切都始于:
/// <summary>
/// Allows you to create aliases that can be used for model properties at
/// model binding time (i.e. when data comes in from a request).
///
/// The type needs to be using the DefaultModelBinderEx model binder in
/// order for this to work.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class BindAliasAttribute : Attribute
{
public BindAliasAttribute(string alias)
{
//ommitted: parameter checking
Alias = alias;
}
public string Alias { get; private set; }
}
然后我们得到这个类:
internal sealed class AliasedPropertyDescriptor : PropertyDescriptor
{
public PropertyDescriptor Inner { get; private set; }
public AliasedPropertyDescriptor(string alias, PropertyDescriptor inner)
: base(alias, null)
{
Inner = inner;
}
public override bool CanResetValue(object component)
{
return Inner.CanResetValue(component);
}
public override Type ComponentType
{
get { return Inner.ComponentType; }
}
public override object GetValue(object component)
{
return Inner.GetValue(component);
}
public override bool IsReadOnly
{
get { return Inner.IsReadOnly; }
}
public override Type PropertyType
{
get { return Inner.PropertyType; }
}
public override void ResetValue(object component)
{
Inner.ResetValue(component);
}
public override void SetValue(object component, object value)
{
Inner.SetValue(component, value);
}
public override bool ShouldSerializeValue(object component)
{
return Inner.ShouldSerializeValue(component);
}
}
这代理了通常由DefaultModelBinder 找到的“正确”PropertyDescriptor,但将其名称显示为别名。
接下来我们有了新的模型绑定器类:
已更新 @jsabrooke 的建议 below
public class DefaultModelBinderEx : DefaultModelBinder
{
protected override System.ComponentModel.PropertyDescriptorCollection
GetModelProperties(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var toReturn = base.GetModelProperties(controllerContext, bindingContext);
List<PropertyDescriptor> additional = new List<PropertyDescriptor>();
//now look for any aliasable properties in here
foreach (var p in
this.GetTypeDescriptor(controllerContext, bindingContext)
.GetProperties().Cast<PropertyDescriptor>())
{
foreach (var attr in p.Attributes.OfType<BindAliasAttribute>())
{
additional.Add(new AliasedPropertyDescriptor(attr.Alias, p));
if (bindingContext.PropertyMetadata.ContainsKey(p.Name)
&& !string.Equals(p.Name, attr.Alias, StringComparison.OrdinalIgnoreCase)))
{
bindingContext.PropertyMetadata.Add(
attr.Alias,
bindingContext.PropertyMetadata[p.Name]);
}
}
}
return new PropertyDescriptorCollection
(toReturn.Cast<PropertyDescriptor>().Concat(additional).ToArray());
}
}
然后,从技术上讲,这就是它的全部内容。您现在可以使用在此 SO 中作为答案发布的解决方案将此 DefaultModelBinderEx 类注册为默认值:Change the default model binder in asp.net MVC,或者您可以将其用作您自己的模型绑定器的基础。
一旦您选择了您希望活页夹如何发挥作用的模式,您只需将其应用于模型类型,如下所示:
public class TestModelType
{
[BindAlias("LPN")]
//and you can add multiple aliases
[BindAlias("L")]
//.. ad infinitum
public string LongPropertyName { get; set; }
}
我选择此代码的原因是因为我想要一些可以与自定义类型描述符一起使用并且能够与任何类型一起使用的东西。同样,我希望在获取模型属性值时仍然使用价值提供者系统。所以我更改了DefaultModelBinder 在开始绑定时看到的元数据。这是一种稍微冗长的方法 - 但从概念上讲,它在元数据级别执行您希望它执行的操作。
如果ValueProvider 包含多个别名的值,或者一个别名及其名称的属性,则可能会产生一个有趣且有点烦人的副作用。在这种情况下,将只使用检索到的值之一。但是,当您只使用objects 时,很难想出一种以类型安全的方式将它们全部合并的方法。不过,这类似于在表单帖子和查询字符串中提供值——我不确定 MVC 在那种情况下究竟做了什么——但我不认为这是推荐的做法。
当然,另一个问题是,您不能创建一个等于另一个别名的别名,或者实际上是一个实际属性的名称。
一般来说,我喜欢使用CustomModelBinderAttribute 类来应用我的模型绑定器。唯一的问题是,如果您需要从模型类型派生并更改其绑定行为 - 因为 CustomModelBinderAttribute 是在 MVC 执行的属性搜索中继承的。
在我的情况下,这没关系,我正在开发一个新的站点框架,并且能够使用其他机制将新的可扩展性推送到我的基本绑定器中以满足这些新类型;但并非所有人都这样。