Pergunta

Eu tenho a seguinte ação do controlador:

[HttpPost]
public ViewResult DoSomething(MyModel model)
{
    // do something
    return View();
}

Onde MyModel se parece com isso:

public class MyModel
{
    public string PropertyA {get; set;}
    public IList<int> PropertyB {get; set;}
}

Portanto, o DefaultModelBinder deve vincular isso sem um problema. A única coisa é que quero usar o fichário especial/personalizado para encadernação PropertyB E eu também quero reutilizar esse fichário. Por isso, pensei que a solução seria colocar um atributo Modelbinder antes da propriedadeB, o que, obviamente, não funciona (o atributo do ModelBinder não é permitido em uma propriedade). Eu vejo duas soluções:

  1. Para usar os parâmetros de ação em todas as propriedades, em vez de todo o modelo (que eu não preferiria, pois o modelo tem muitas propriedades) como esta:

    public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB)
    
  2. Para criar um novo tipo, digamos MyCustomType: List<int> e registrar o fichário do modelo para este tipo (esta é uma opção)

  3. Talvez para criar um fichário para mymodel, substitua BindProperty E se a propriedade for "PropertyB" Ligue a propriedade com meu fichário personalizado. Isso é possível?

existe alguma outra solução?

Foi útil?

Solução

substituir o bindProperty e se a propriedade for "PropertyB" vincular a propriedade com meu fichário personalizado

Essa é uma boa solução, embora, em vez de verificar "IS PropertyB", é melhor você verificar seus próprios atributos personalizados que definem ligantes em nível de propriedade, como

[PropertyBinder(typeof(PropertyBBinder))]
public IList<int> PropertyB {get; set;}

Você pode ver um exemplo de substituição de bindProperty aqui.

Outras dicas

Na verdade, eu gosto da sua terceira solução, apenas eu faria uma solução genérica para todos os Modelbinders, colocando -a em um fichário personalizado que herda DefaultModelBinder e está configurado para ser o fichário padrão para o seu aplicativo MVC.

Então você faria este novo DefaultModelBinder vincular automaticamente qualquer propriedade decorada com um PropertyBinder atributo, usando o tipo fornecido no parâmetro.

Eu tive a ideia deste excelente artigo: http://aboutcode.net/2011/03/12/mvc-property-binder.html.

Também vou mostrar minha opinião sobre a solução:

Meu DefaultModelBinder:

namespace MyApp.Web.Mvc
{
    public class DefaultModelBinder : System.Web.Mvc.DefaultModelBinder
    {
        protected override void BindProperty(
            ControllerContext controllerContext, 
            ModelBindingContext bindingContext, 
            PropertyDescriptor propertyDescriptor)
        {
            var propertyBinderAttribute = TryFindPropertyBinderAttribute(propertyDescriptor);
            if (propertyBinderAttribute != null)
            {
                var binder = CreateBinder(propertyBinderAttribute);
                var value = binder.BindModel(controllerContext, bindingContext, propertyDescriptor);
                propertyDescriptor.SetValue(bindingContext.Model, value);
            }
            else // revert to the default behavior.
            {
                base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
            }
        }

        IPropertyBinder CreateBinder(PropertyBinderAttribute propertyBinderAttribute)
        {
            return (IPropertyBinder)DependencyResolver.Current.GetService(propertyBinderAttribute.BinderType);
        }

        PropertyBinderAttribute TryFindPropertyBinderAttribute(PropertyDescriptor propertyDescriptor)
        {
            return propertyDescriptor.Attributes
              .OfType<PropertyBinderAttribute>()
              .FirstOrDefault();
        }
    }
}

Meu IPropertyBinder interface:

namespace MyApp.Web.Mvc
{
    interface IPropertyBinder
    {
        object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext, MemberDescriptor memberDescriptor);
    }
}

Meu PropertyBinderAttribute:

namespace MyApp.Web.Mvc
{
    public class PropertyBinderAttribute : Attribute
    {
        public PropertyBinderAttribute(Type binderType)
        {
            BinderType = binderType;
        }

        public Type BinderType { get; private set; }
    }
}

Um exemplo de fichário de propriedade:

namespace MyApp.Web.Mvc.PropertyBinders
{
    public class TimeSpanBinder : IPropertyBinder
    {
        readonly HttpContextBase _httpContext;

        public TimeSpanBinder(HttpContextBase httpContext)
        {
            _httpContext = httpContext;
        }

        public object BindModel(
            ControllerContext controllerContext,
            ModelBindingContext bindingContext,
            MemberDescriptor memberDescriptor)
        {
            var timeString = _httpContext.Request.Form[memberDescriptor.Name].ToLower();
            var timeParts = timeString.Replace("am", "").Replace("pm", "").Trim().Split(':');
            return
                new TimeSpan(
                    int.Parse(timeParts[0]) + (timeString.Contains("pm") ? 12 : 0),
                    int.Parse(timeParts[1]),
                    0);
        }
    }
}

Exemplo do fichário da propriedade acima que está sendo usado:

namespace MyApp.Web.Models
{
    public class MyModel
    {
        [PropertyBinder(typeof(TimeSpanBinder))]
        public TimeSpan InspectionDate { get; set; }
    }
}

@A resposta de Jonathanconway é ótima, mas eu gostaria de adicionar um detalhe menor.

Provavelmente é melhor substituir o GetPropertyValue método em vez de BindProperty para dar o mecanismo de validação do DefaultBinder uma chance de trabalhar.

protected override object GetPropertyValue(
    ControllerContext controllerContext,
    ModelBindingContext bindingContext,
    PropertyDescriptor propertyDescriptor,
    IModelBinder propertyBinder)
{
    PropertyBinderAttribute propertyBinderAttribute =
        TryFindPropertyBinderAttribute(propertyDescriptor);
    if (propertyBinderAttribute != null)
    {
        propertyBinder = CreateBinder(propertyBinderAttribute);
    }

    return base.GetPropertyValue(
        controllerContext,
        bindingContext,
        propertyDescriptor,
        propertyBinder);
}

Faz 6 anos desde que essa pergunta foi feita, prefiro tomar esse espaço para resumir a atualização, em vez de fornecer uma nova solução. No momento da redação deste artigo, o MVC 5 já existe há um bom tempo, e o ASP.NET Core acabou de sair.

Eu segui a abordagem examinada no post escrito por Vijaya Anand (btw, graças a Vijaya): http://www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes. E uma coisa que vale a pena notar é que a lógica de ligação de dados é colocada na classe de atributo personalizada, que é o método BindProperty da classe StringArrayPropertyBindattribute no exemplo de Vijaya Anand.

No entanto, em todos os outros artigos sobre este tópico que li (incluindo a solução da @Jonathanconway), a classe de atributos personalizada é apenas uma pedra de etapa que lidera a estrutura para descobrir o fichário de modelo personalizado correto para aplicar; e a lógica de ligação é colocada nesse fichário de modelo personalizado, que geralmente é um objeto imodelbinder.

A 1ª abordagem é mais simples para mim. Pode haver algumas deficiências da 1ª abordagem, que ainda não conheci, porque sou muito novo na estrutura do MVC no momento.

Além disso, descobri que a classe ExtendedModelBinder no exemplo de Vijaya Anand é desnecessária no MVC 5. Parece que a classe DefaultModelBinder que vem com o MVC 5 é inteligente o suficiente para cooperar com atributos de ligação de modelos personalizados.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top