Asp.Net MVC 2 – 将模型的属性绑定到不同的命名值

更新(2016 9月21日) – 感谢Digbyswift评论说,这个解决scheme在MVC5中仍然有效。

更新(2012 4月30日) – 注意到人们在search等问题上陷入困境 – 接受的答案并不是我如何做到这一点 – 但是由于它可能在某些情况下有效,我放弃了它。 我自己的答案包含我使用的最终解决scheme ,这是可重用的,将适用于任何项目。

它也被证实在MVC框架的v3和v4中工作。

我有以下的模型types(类和它的属性的名称已经改变,以保护他们的身份):

public class MyExampleModel { public string[] LongPropertyName { get; set; } } 

这个属性然后绑定到一堆(> 150)的checkbox,其中每个input的名字当然是LongPropertyName

表单提交到HTTP GET,并说用户select其中三个checkbox – url将有查询string?LongPropertyName=a&LongPropertyName=b&LongPropertyName=c

然后,大问题是,如果我选中所有(或者甚至超过一半!)checkbox,则超出了IIS上请求filter强制执行的最大查询string长度!

我不想扩展 – 所以我想要一个方法来裁减这个查询string(我知道我可以切换到一个POST – 但即使如此,我仍然想要最大限度地减less客户端发送的数据中的绒毛数量) 。

我想要做的就是将LongPropertyName绑定到“L”,这样查询string就变成了?L=a&L=b&L=c但是不改变代码中的属性名称

有问题的types已经有一个自定义的模型联编程序(从DefaultModelBinder派生),但它附加到它的基类 – 所以我不想把代码放在那里的派生类。 所有的属性绑定目前都是由标准的DefaultModelBinder逻辑来执行的,我知道它使用System.ComponentModel中的TypeDescriptors和Property Descriptors等。

我有点希望可以有一个属性,我可以申请财产,使这项工作 – 在那里? 或者我应该看在实现ICustomTypeDescriptor

你可以使用BindAttribute来完成这个。

 public ActionResult Submit([Bind(Prefix = "L")] string[] longPropertyName) { } 

更新

由于'longPropertyName'参数是模型对象的一部分,而不是控制器动作的独立参数,因此您有其他几个select。

您可以将模型和属性保留为您的操作的独立参数,然后在操作方法中手动合并数据。

 public ActionResult Submit(MyModel myModel, [Bind(Prefix = "L")] string[] longPropertyName) { if(myModel != null) { myModel.LongPropertyName = longPropertyName; } } 

另一个select是实现一个自定义的模型绑定器,手动执行参数值分配(如上所述),但这很可能是矫枉过正的。 下面是一个例子,如果你感兴趣: Flags Enumeration Model Binder 。

回应michaelalm的回答和要求 – 这是我最终做的。 由于Nathan提出的解决scheme之一会起作用,所以我留下的答案主要是出于礼貌。

这个输出是DefaultModelBinder类的替代品,您可以全局注册(从而允许所有模型types利用别名)或有select地inheritance自定义模型绑定器。

这一切都开始,可以预见:

 /// <summary> /// Allows you to create aliases that can be used for model properties at /// model binding time (ie 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); } } 

这代理了一个通常由DefaultModelBinderfind的“适当的”PropertyDescriptor,但是将其名称作为别名。

接下来,我们有新的模型联编程序类:

 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)) bindingContext.PropertyMetadata.Add(attr.Alias, bindingContext.PropertyMetadata[p.Name]); } } return new PropertyDescriptorCollection (toReturn.Cast<PropertyDescriptor>().Concat(additional).ToArray()); } } 

而从技术上讲,这就是它的全部。 你现在可以注册这个DefaultModelBinderEx类作为默认使用的解决scheme作为答案发布在这个SO: 改变默认的模型绑定在asp.net MVC中 ,或者你可以使用它作为你自己的模型绑定的基础。

一旦你select了你想要的活页夹的模式,你只需要把它应用到模型types如下:

 public class TestModelType { [BindAlias("LPN")] //and you can add multiple aliases [BindAlias("L")] //.. ad infinitum public string LongPropertyName { get; set; } } 

我select这个代码的原因是因为我想要的东西,可以使用自定义types描述符,以及能够使用任何types。 同样,我希望价值提供者系统仍然用于采购模型属性值。 所以我改变了DefaultModelBinder开始绑定时看到的元数据。 这是一个稍微冗长的方法 – 但从概念上讲,它正在元数据层面上做你想做的事情。

如果ValueProvider包含多个别名的值,或者别名和该名称的属性,则可能会引起一些潜在的有趣且稍微令人讨厌的副作用。 在这种情况下,将只使用其中一个检索值。 当你仅仅使用object的时候,很难想象一种以types安全的方式来合并它们的方法。 这与提供表单post和查询string中的值相似,我不确定MVC在这种情况下究竟做了什么 – 但我不认为这是推荐的做法。

另一个问题是,当然,你不能创build一个等于另一个别名的别名,或者实际上是一个实际属性的名字。

我喜欢应用我的模型粘合剂,一般来说,使用CustomModelBinderAttribute类。 唯一的问题是,如果您需要从模型types派生并更改其绑定行为 – 因为CustomModelBinderAttribute在由MVC执行的属性search中被inheritance。

在我的情况下,这是好的,我正在开发一个新的网站框架,并能够推动新的可扩展性到我的基地活页夹使用其他机制,以满足这些新的types; 但对于每个人来说都不是这样。

这是一个类似于你的Andras的解决scheme吗? 我希望你可以发表你的答案。

控制器方法

 public class MyPropertyBinder : DefaultModelBinder { protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor) { base.BindProperty(controllerContext, bindingContext, propertyDescriptor); for (int i = 0; i < propertyDescriptor.Attributes.Count; i++) { if (propertyDescriptor.Attributes[i].GetType() == typeof(BindingNameAttribute)) { // set property value. propertyDescriptor.SetValue(bindingContext.Model, controllerContext.HttpContext.Request.Form[(propertyDescriptor.Attributes[i] as BindingNameAttribute).Name]); break; } } } } 

属性

 public class BindingNameAttribute : Attribute { public string Name { get; set; } public BindingNameAttribute() { } } 

视图模型

 public class EmployeeViewModel { [BindingName(Name = "txtName")] public string TestProperty { get; set; } } 

然后在控制器中使用活页夹

 [HttpPost] public ActionResult SaveEmployee(int Id, [ModelBinder(typeof(MyPropertyBinder)] EmployeeViewModel viewModel) { // do stuff here } 

txtName表单值应该被设置为TestProperty。