Asp.Net MVC 2 – Vincule a propriedade de um modelo a um valor nomeado diferente

Atualização (21 de setembro de 2016) – Obrigado ao Digbyswift por comentar que esta solução ainda funciona no MVC5 também.

Update (30 de abril de 2012) – Note para as pessoas tropeçando em toda esta questão de pesquisas etc – a resposta aceita não é como acabei fazendo isso – mas deixei aceito porque poderia ter funcionado em alguns casos. Minha própria resposta contém a solução final que usei , que é reutilizável e será aplicada a qualquer projeto.

Também está confirmado para trabalhar em v3 e v4 do framework MVC.

Eu tenho o seguinte tipo de modelo (os nomes da class e suas propriedades foram alterados para proteger suas identidades):

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

Esta propriedade é então ligada a um grupo (> 150) de checkboxs de seleção, onde o nome de input de cada um é, naturalmente, LongPropertyName .

O formulário envia para url com um HTTP GET e diz que o usuário seleciona três dessas checkboxs de seleção – o URL terá a string de consulta ?LongPropertyName=a&LongPropertyName=b&LongPropertyName=c

O grande problema, então, é que, se eu selecionar todas (ou mesmo pouco mais da metade!) As checkboxs de seleção, excederei o tamanho máximo da string de consulta imposta pelo filtro de solicitação no IIS!

Eu não quero estender isso – então eu quero uma maneira de reduzir essa seqüência de consulta (eu sei que posso apenas mudar para um POST – mas mesmo assim eu ainda quero minimizar a quantidade de cotão nos dados enviados pelo cliente) .

O que eu quero fazer é ter o LongPropertyName ligado a simplesmente ‘L’ para que a string de consulta se torne ?L=a&L=b&L=c mas sem alterar o nome da propriedade no código .

O tipo em questão já tem um fichário de modelo personalizado (derivado de DefaultModelBinder), mas está anexado à sua class base – portanto, não quero colocar o código lá para uma class derivada. Toda a vinculação de propriedade é executada atualmente pela lógica padrão DefaultModelBinder, que eu sei usa TypeDescriptors e Property Descriptors etc de System.ComponentModel.

Eu estava meio que esperando que pudesse haver um atributo que eu pudesse aplicar à propriedade para fazer esse trabalho – está lá? Ou eu deveria estar olhando para implementar ICustomTypeDescriptor ?

Você pode usar o BindAttribute para fazer isso.

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

Atualizar

Como o parâmetro ‘longPropertyName’ é parte do object de modelo e não um parâmetro independente da ação do controlador, você tem algumas outras opções.

Você poderia manter o modelo e a propriedade como parâmetros independentes para sua ação e mesclar manualmente os dados juntos no método de ação.

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

Outra opção seria implementar um Model Binder personalizado que executa a atribuição de valor de parâmetro (como acima) manualmente, mas isso é provavelmente um exagero. Aqui está um exemplo de um, se você estiver interessado: Binder Enumeration Model Binder .

Em resposta à resposta e pedido do michaelalm – eis o que acabei fazendo. Deixei a resposta original assinalada principalmente por cortesia, já que uma das soluções sugeridas por Nathan teria funcionado.

A saída deste é um substituto para a class DefaultModelBinder que você pode registrar globalmente (permitindo assim que todos os tipos de modelo aproveitem o aliasing) ou herdam seletivamente para os classificadores de modelo customizados.

Tudo começa previsivelmente com:

 ///  /// 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. ///  [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; } } 

E então nós temos essa class:

 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); } } 

Isso faz com que um PropertyDescriptor ‘adequado’ seja encontrado normalmente pelo DefaultModelBinder mas apresenta seu nome como o alias.

Em seguida, temos a nova class de ligantes do modelo:

 public class DefaultModelBinderEx : DefaultModelBinder { protected override System.ComponentModel.PropertyDescriptorCollection GetModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) { var toReturn = base.GetModelProperties(controllerContext, bindingContext); List additional = new List(); //now look for any aliasable properties in here foreach (var p in this.GetTypeDescriptor(controllerContext, bindingContext) .GetProperties().Cast()) { foreach (var attr in p.Attributes.OfType()) { 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().Concat(additional).ToArray()); } } 

E, então, tecnicamente, é tudo o que existe para isso. Agora você pode registrar essa class DefaultModelBinderEx como padrão usando a solução lançada como a resposta neste SO: Altere o fichário de modelo padrão no asp.net MVC ou use-o como base para seu próprio fichário de modelo.

Uma vez que você selecionou seu padrão para como você deseja que o fichário seja ativado, você simplesmente o aplica a um tipo de modelo da seguinte maneira:

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

A razão pela qual escolhi esse código foi porque queria algo que funcionasse com descritores de tipos personalizados, além de poder trabalhar com qualquer tipo. Da mesma forma, eu queria que o sistema do provedor de valor fosse usado ainda na origem dos valores da propriedade do modelo. Então eu mudei os metadados que o DefaultModelBinder vê quando começa a binding. É uma abordagem um pouco mais longa – mas conceitualmente, ela está fazendo no nível de metadados exatamente o que você quer fazer.

Um efeito colateral potencialmente interessante e um pouco irritante será se o ValueProvider contiver valores para mais de um alias ou um alias e a propriedade por seu nome. Nesse caso, apenas um dos valores recuperados será usado. Difícil pensar em uma maneira de mesclá-los todos de uma maneira segura quando você está apenas trabalhando com object . Isso é semelhante, no entanto, ao fornecer um valor em um post de formulário e uma string de consulta – e não sei exatamente o que o MVC faz nesse cenário – mas não acho que seja uma prática recomendada.

Outro problema é, obviamente, que você não deve criar um alias que seja igual a outro alias, ou mesmo o nome de uma propriedade real.

Eu gosto de aplicar meus fichários de modelo, em geral, usando a class CustomModelBinderAttribute . O único problema com isso pode ser se você precisar derivar do tipo de modelo e alterar seu comportamento de binding – já que o CustomModelBinderAttribute é herdado na pesquisa de atributos executada pelo MVC.

No meu caso, tudo bem, estou desenvolvendo uma nova estrutura de site e estou conseguindo implementar uma nova extensibilidade em meus fichários de base usando outros mecanismos para satisfazer esses novos tipos; mas esse não será o caso para todos.

isso seria uma solução parecida com a sua Andras? Eu espero que você possa postar sua resposta também.

método controlador

 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; } } } } 

Atributo

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

ViewModel

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

então usar o Binder no controlador

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

o valor do formulário txtName deve ser definido como o TestProperty.