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

And*_*tan 49 c# asp.net-mvc asp.net-mvc-2

更新(2016 9月21日) - 感谢Digbyswift评论此解决方案仍然适用于MVC5.

更新(2012 4月30日) - 注意那些在搜索等问题上遇到这个问题的人 - 接受的答案不是我最终如何做到这一点 - 但我接受了它,因为它可能在某些情况下有效. 我自己的答案包含我使用的最终解决方案,它可以重复使用并适用于任何项目.

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

我有以下模型类型(类的名称及其属性已更改以保护其身份):

public class MyExampleModel
{
  public string[] LongPropertyName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

然后将此属性绑定到一堆(> 150)复选框,其中每个输入名称当然都是LongPropertyName.

表单使用HTTP GET提交到url,并说用户选择其中三个复选框 - url将具有查询字符串 ?LongPropertyName=a&LongPropertyName=b&LongPropertyName=c

那么很大的问题是,如果我选择所有(甚至只有一半!)复选框,我超过了IIS上请求过滤器强制执行的最大查询字符串长度!

我不想扩展它 - 所以我想要一种方法来减少这个查询字符串(我知道我可以切换到POST - 但即便如此,我仍然希望最小化客户端发送的数据中的绒毛数量) .

我想要做的是LongPropertyName绑定到简单的'L',因此查询字符串将成为?L=a&L=b&L=c不更改代码中的属性名称.

有问题的类型已经有一个自定义模型绑定器(从DefaultModelBinder派生),但它附加到它的基类 - 所以我不想在那里为派生类放置代码.所有属性绑定当前都由标准的DefaultModelBinder逻辑执行,我知道它使用System.ComponentModel中的TypeDescriptors和Property Descriptors等.

我有点希望可能有一个属性我可以申请该物业来完成这项工作 - 是吗?或者我应该考虑实施ICustomTypeDescriptor

And*_*tan 84

回应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; }
}
Run Code Online (Sandbox Code Playgroud)

然后我们得到这个课:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

这代理了一个'正确'的PropertyDescriptor,它通常由DefaultModelBinder它找到,但是它的名字是别名.

接下来我们有了新的模型binder类:

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());
  }
}
Run Code Online (Sandbox Code Playgroud)

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

一旦您选择了模式以了解粘合剂的启动方式,您只需将其应用于模型类型,如下所示:

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

我选择此代码的原因是因为我想要一些可以使用自定义类型描述符以及能够使用任何类型的东西.同样,我希望价值提供者系统仍然可以用于获取模型属性值.所以我改变了DefaultModelBinder开始绑定时看到的元数据.这是一个稍微冗长的方法 - 但从概念上讲,它正在元数据级别完全按照您的要求进行操作.

如果ValueProvider包含多个别名的值,或者包含别名和属性的名称,则会产生一个可能有趣且有点烦人的副作用.在这种情况下,将仅使用一个检索到的值.当你刚刚使用objects 时,很难想出一种以类型安全的方式将它们合并的方法.但是,这与在表单帖子和查询字符串中提供值类似 - 我不确定MVC在该场景中的确切做法 - 但我不认为这是推荐的做法.

当然,另一个问题是,您不能创建等于另一个别名的别名,或者实际上是实际属性的名称.

我喜欢使用类来应用我的模型绑定器CustomModelBinderAttribute.唯一的问题是,如果您需要从模型类型派生并更改其绑定行为 - 因为它CustomModelBinderAttribute是在MVC执行的属性搜索中继承的.

在我的情况下,这是可以的,我正在开发一个新的站点框架,并能够使用其他机制将新的可扩展性推入我的基本绑定器以满足这些新类型; 但每个人都不会这样.

  • 工作得很好,谢谢.这应该是框架中默认内容的一部分. (2认同)

Nat*_*lor 20

您可以使用BindAttribute来完成此任务.

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

}
Run Code Online (Sandbox Code Playgroud)

更新

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

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

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

另一个选择是实现一个自定义模型绑定器,它可以手动执行参数值赋值(如上所述),但这很可能是矫枉过正.如果你有兴趣,这是一个例子:Flags Enumeration Model Binder.


小智 5

这会是类似于你的 Andras 的解决方案吗?我希望你也能发表你的答案。

控制器方法

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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

属性

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

    public BindingNameAttribute()
    {

    }
}
Run Code Online (Sandbox Code Playgroud)

视图模型

public class EmployeeViewModel
{                    

    [BindingName(Name = "txtName")]
    public string TestProperty
    {
        get;
        set;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在控制器中使用Binder

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

txtName 表单值应设置为 TestProperty。