ASP.NET MVC

Os recursos e problemas da associação de modelo do ASP.NET MVC

Jess Chadwick

A associação de modelo do ASP.NET MVC simplifica as ações do controlador com a introdução de uma camada de abstração que popula os parâmetros das ações do controlador, cuidando do mapeamento de propriedades triviais e do código de conversão de tipo normalmente envolvidos ao trabalhar com dados de solicitação do ASP.NET. Embora a associação de modelo pareça simples, na verdade, ela é uma estrutura relativamente complexa composta de várias partes que funcionam em conjunto para criar e popular os objetos necessários para suas ações de controlador.

Este artigo levará você até o coração do subsistema de associação de modelo ASP.NET MVC, mostrando cada camada da estrutura de associação de modelo e as várias maneiras como a lógica de associação de modelo pode ser estendida para atender às necessidades dos aplicativos. Nesse processo, você verá algumas técnicas de associação de modelo, que são negligenciadas com frequência, e como evitar alguns dos erros mais comuns da associação de modelo.

Conceitos básicos da associação de modelo

Para compreender o que é a associação de modelo, primeiro examine uma maneira típica de popular um objeto a partir dos valores da solicitação em um aplicativo ASP.NET, mostrado na Figura 1.

Figura 1 Recuperando valores diretamente da solicitação

public ActionResult Create()
{
  var product = new Product() {
    AvailabilityDate = DateTime.Parse(Request["availabilityDate"]),
    CategoryId = Int32.Parse(Request["categoryId"]),
    Description = Request["description"],
    Kind = (ProductKind)Enum.Parse(typeof(ProductKind), 
                                   Request["kind"]),
    Name = Request["name"],
    UnitPrice = Decimal.Parse(Request["unitPrice"]),
    UnitsInStock = Int32.Parse(Request["unitsInStock"]),
 };
 // ...
}

Em seguida, compare a ação da Figura 1 com a Figura 2, que utiliza a associação de modelo para produzir o mesmo resultado.

Figura 2 Associação de modelo a valores primitivos

public ActionResult Create(
  DateTime availabilityDate, int categoryId,
    string description, ProductKind kind, string name,
    decimal unitPrice, int unitsInStock
  )
{
  var product = new Product() {
    AvailabilityDate = availabilityDate,
    CategoryId = categoryId,
    Description = description,
    Kind = kind,
    Name = name,
    UnitPrice = unitPrice,
    UnitsInStock = unitsInStock,
 };
 
 // ...
}

Embora os dois exemplos realizem a mesma coisa, uma instância de Product populada, o código da Figura 2 depende do ASP.NET MVC para converter os valores da solicitação em valores fortemente tipados. Com a associação de modelo, as ações do controlador podem ser focalizadas no fornecimento de valor comercial e evitam a perda de tempo com o mapeamento e a análise triviais da solicitação.

Associação a objetos complexos

Embora até a associação de modelo a tipos simples e primitivos possa ter um impacto realmente grande, muitas ações do controlador dependem de muito mais do que simplesmente um par de parâmetros. Felizmente, o ASP.NET MVC trata tipos complexos tão bem quanto tipos primitivos.

O código a seguir dá mais um passo na ação Create, ignorando os valores primitivos e associando diretamente à classe Product:

public ActionResult Create(Product product)
{
  // ...
}

Mais uma vez, esse código produz o mesmo resultado que as ações da Figura 1 e da Figura 2, só que desta vez nenhum código foi envolvido, a associação de modelo complexa do ASP.NET MVC eliminou todo o código clichê necessário para criar e popular uma nova instância de Product. Esse código exemplifica o verdadeiro potencial da associação de modelo.

Decompondo a associação de modelo

Agora que vimos a associação de modelo em ação, vamos separar as partes que compõem a estrutura da associação de modelo.

A associação de modelo é dividida em duas etapas distintas: a coleta de valores da solicitação e a população dos modelos com esses valores. Essas etapas são realizadas pelos provedores de valor e pelos associadores de modelo, respectivamente.

Provedores de valor

O ASP.NET MVC inclui implementações de provedores de valor que cobrem as origens mais comuns de valores de solicitação, como os parâmetros de querystring, campos de formulário e dados de rota. Em tempo de execução, o ASP.NET MVC usa os provedores de valor registrados na classe ValueProviderFactories para avaliar valores de solicitação que os associadores de modelo podem usar.

Por padrão, a coleção de provedores de valor avalia os valores de várias origens na ordem a seguir:

  1. Parâmetros da ação de associação anterior, quando a ação é uma ação filho
  2. Campos de formulário (Request.Form)
  3. Os valores da propriedade no corpo da solicitação JSON (Request.InputStream), mas apenas quando a solicitação é uma solicitação AJAX
  4. Dados da rota (RouteData.Values)
  5. Parâmetros de Querystring (Request.QueryString)
  6. Arquivos postados (Request.Files)

A coleção de provedores de valor, como o objeto Resquest, é realmente apenas um dicionário superestimado, uma camada da abstração de pares de chave/valor, que os associadores de modelo podem usar sem necessidade de saber qual a origem dos dados. No entanto, a estrutura do provedor de valor usa essa abstração um pouco além do dicionário de solicitação, fornecendo a você um controle completo sobre como e onde a estrutura da associação de modelo obtém os dados. É possível até criar seus próprios provedores de valor personalizados.

Provedores de valor personalizados

O requisito mínimo para criar um provedor de valor personalizado é bastante simples: crie uma nova classe que implemente a interface System.Web.Mvc.ValueProviderFactory.

Por exemplo, a Figura 3 demonstra um provedor de valor personalizado que recupera valores dos cookies dos usuários.

Figura 3 Fábrica de provedor de valor personalizado que inspeciona valores de cookies

public class CookieValueProviderFactory : ValueProviderFactory
{
  public override IValueProvider GetValueProvider
  (
    ControllerContext controllerContext
  )
  {
    var cookies = controllerContext.HttpContext.Request.Cookies;
 
    var cookieValues = new NameValueCollection();
    foreach (var key in cookies.AllKeys)
    {
      cookieValues.Add(key, cookies[key].Value);
    }
 
    return new NameValueCollectionValueProvider(
      cookieValues, CultureInfo.CurrentCulture);
  }
}

Observe como CookieValueProviderFactory é simples. Em vez de criar um provedor de valor completamente novo a partir do zero, a CookieValueProviderFactory simplesmente recupera os cookies dos usuários e utiliza o NameValueCollectionValueProvider para expor esses valores para a estrutura de associação de modelo.

Depois de criar um provedor de valor personalizado, será necessário adicioná-lo à lista de provedores de valor por meio da coleção de ValueProviderFactories.Factories:

var factory = new CookieValueProviderFactory();
ValueProviderFactories.Factories.Add(factory);

É muito fácil criar provedores de valor personalizados, mas tenha cuidado ao fazer isso. O conjunto de provedores de valor prontos para uso fornecidos com o ASP.NET MVC expõem a maior parte dos dados disponíveis na HttpRequest (talvez, com exceção dos cookies) muito bem e geralmente fornecem dados suficientes para satisfazer a maioria dos cenários.

Para determinar se a criação de um novo provedor de valor é a coisa certa a fazer para seu cenário específico, faça a pergunta a seguir: O conjunto de informações fornecido pelos provedores de valor existentes contém todos os dados de que preciso (embora, talvez, não no formato correto)?

Se a resposta for não, a adição de um provedor de valor personalizado provavelmente será a maneira certa de resolver o problema. No entanto, quando a resposta for sim, como na realidade será, considere como preencher as partes ausentes personalizando o comportamento da associação de modelo para acessar os dados que estão sendo fornecidos pelos provedores de valor. O restante deste artigo mostra como fazer exatamente isso.

O principal componente da estrutura de associação de modelo do ASP.NET MVC responsável por criar e popular modelos usando os valores fornecidos pelos provedores de valor é chamado de associador de modelo.

Associador de modelo padrão

A estrutura do ASP.NET MVC inclui a implementação do associador de modelo padrão denominado DefaultModelBinder, que foi desenvolvido para associar de maneira efetiva a maioria dos tipos de modelo. Isso é feito usando lógica recursiva e relativamente simples para cada propriedade do modelo de destino:

  1. examinar os provedores de valor para verificar se a propriedade foi descoberta como um tipo simples ou complexo verificando se o nome da propriedade está registrado como um prefixo. Os prefixos são simplesmente a "notação de ponto" do nome do campo de formulário HTML usada para representar se um valor é uma propriedade de um objeto complexo. O padrão do prefixo é [ParentProperty].[Property]. Por exemplo, o campo de formulário com o nome UnitPrice.Amount contém o valor do campo Amount da propriedade UnitPrice.
  2. Obter o ValueProviderResult dos provedores de valor registrados para o nome da propriedade.
  3. Se o valor for um tipo simples, tentar convertê-lo no tipo de destino. A lógica de conversão padrão utiliza o TypeConverter da propriedade para converter do valor de origem to tipo cadeia de caracteres no tipo de destino.
  4. Caso contrário, a propriedade será de um tipo complexo, portanto, executar uma associação recursiva.

Associação recursiva de modelo

A associação de modelo recursiva inicia efetivamente todo o processo de associação de modelo novamente, mas usa o nome da propriedade de destino como o novo prefixo. Com essa abordagem, o DefaultModelBinder pode percorrer gráficos inteiros de objetos complexos e populares, até valores de propriedade profundamente aninhados.

Para ver a associação recursiva em ação, altere o Product.UnitPrice de um tipo decimal simples para o tipo personalizado Currency. A Figura 4 mostra as duas classes.

Figure 4 Classe Product com a propriedade Unitprice complexa

public class Product
{
  public DateTime AvailabilityDate { get; set; }
  public int CategoryId { get; set; }
  public string Description { get; set; }
  public ProductKind Kind { get; set; }
  public string Name { get; set; }
  public Currency UnitPrice { get; set; }
  public int UnitsInStock { get; set; }
}
 
public class Currency
{
  public float Amount { get; set; }
  public string Code { get; set; }
}

Com essa atualização estabelecida, o associador de modelo procurará os valores denominados UnitPrice.Amount e UnitPrice.Code para popular a propriedade Product.UnitPrice complexa.

A lógica da associação recursiva de DefaultModelBinder pode popular efetivamente até os gráficos de objetos mais complexos. Até agora, vimos um objeto complexo que residia apenas em um nível de profundidade na hierarquia do objeto, que o DefaultModelBinder tratava facilmente. Para demonstrar o verdadeiro potencial da associação recursiva de modelo, adicione uma nova propriedade denominada Child a Product com o mesmo tipo, Product:

public class Product {
  public Product Child { get; set; }
  // ...
}

Em seguida, adicione um novo campo ao formulário e, aplicando a notação de ponto para indicar cada nível, crie tantos níveis quantos desejar. Por exemplo:

    <input type="text" name="Child.Child.Child.Child.Child.Child.Name"/>

Esse campo de formulário resultará em seis níveis de Products. Para cada nível, o DefaultModelBinder criará obedientemente uma nova instância de Product e começará a associar seus valores imediatamente. Quando o associador concluir, ele terá criado um gráfico de objeto semelhante ao código da Figura 5.

Figura 5 Um gráfico de objeto criado na associação recursiva de modelo

new Product {
  Child = new Product { 
    Child = new Product {
      Child = new Product {
        Child = new Product {
          Child = new Product {
            Child = new Product {
              Name = "MADNESS!"
            }
          }
        }
      }
    }
  }
}

Embora esse exemplo imaginário defina o valor de apenas uma propriedade, ele é uma boa demonstração de como a funcionalidade de associação recursiva de modelo DefaultModelBinder permite que ele ofereça suporte a alguns gráficos de modelos prontos para uso. Com a associação recursiva de modelo, se você puder criar um nome de campo de formulário para representar o valor a ser populado, onde quer que o valor resida na hierarquia de objetos, o associador de objeto o localizará e o associará.

Onde a associação de modelo não funciona

É verdade: há alguns modelos que o DefaultModelBinder simplesmente não conseguirá associar. No entanto, também existem alguns cenários nos quais a lógica de associação de modelo padrão pode parecer não funcionar, mas nos quais funcionará muito bem desde que você a use de maneira adequada.

Os seguintes são alguns dos cenários mais comuns que os desenvolvedores assumem que o DefaultModelBinder não funcionará, e como é possível implementá-los usando apenas o DefaultModelBinder.

Coleções complexas Os provedores de valor do ASP.NET MVC prontos para uso tratam todos os nomes de campos de solicitação como se fossem valores de postagem de formulário. Por exemplo, uma coleção de valores primitivos em uma postagem de formulário, na qual cada valor requer seu próprio índice exclusivo (espaço em branco adicionado para legibilidade:

MyCollection[0]=one &
MyCollection[1]=two &
MyCollection[2]=three

a mesma abordagem também pode ser aplicada a coleções de objetos complexos. Para demonstrar isso, atualize a classe Product para dar suporte a várias moedas alterando a propriedade UnitPrice para uma coleção de objetos Currency:

public class Product : IProduct
{
  public IEnumerable<Currency> UnitPrice { get; set; }
 
  // ...
}

Com essa alteração, os seguintes parâmetros de solicitação serão necessários para popular a propriedade UnitPrice atualizada:

UnitPrice[0].Code=USD &
UnitPrice[0].Amount=100.00 &

UnitPrice[1].Code=EUR &
UnitPrice[1].Amount=73.64

Preste muita atenção à sintaxe da nomenclatura dos parâmetros de solicitação necessários para associar coleções de objetos complexos. Observe os indexadores usados para identificar cada item exclusivo na área, e que cada propriedade para cada instância deve conter a referência indexada completa a essa instância. Lembre-se de que o associador de modelo espera que os nomes de propriedades sigam a sintaxe de nomenclatura da postagem do formulário, independentemente da solicitação ser um GET ou um POST.

Embora isso seja um tanto não intuitivo, as solicitações JSON têm os mesmos requisitos, elas também devem obedecer à sintaxe de nomenclatura da postagem do formulário. Veja, por exemplo, a carga de JSON para a coleção de UnitPrice anterior. A sintaxe pura da matriz JSON desses dados seria representada como:

[ 
  { "Code": "USD", "Amount": 100.00 },
  { "Code": "EUR", "Amount": 73.64 }
]

No entanto, os provedores de valores padrão e os associadores de modelo requerem que os dados sejam representados como uma postagem de formulário JSON:

{
  "UnitPrice[0].Code": "USD",
  "UnitPrice[0].Amount": 100.00,

  "UnitPrice[1].Code": "EUR",
  "UnitPrice[1].Amount": 73.64
}

O cenário da coleção de objetos complexos é, talvez, um dos cenários mais problemáticos que os desenvolvedores encontram, porque a sintaxe não é necessariamente evidente para todos os desenvolvedores. No entanto, depois de aprender a sintaxe relativamente simples para postar coleções complexas, esses cenários se tornam mais fáceis de lidar.

Associadores de modelo genéricos personalizados Embora o DefaultModelBinder tenha potencial suficiente para tratar quase qualquer coisa que você jogue para ele, há ocasiões em que ele não faz o que você precisa. Quando ocorrem esses cenários, muitos desenvolvedores tentam tirar partido do modelo de extensibilidade da estrutura de associação de modelo e criam seu próprio associador de modelo personalizado.

Por exemplo, embora o Microsoft .NET Framework forneça excelente suporte a princípios orientados a objetos, o DefaultModelBinder não oferece nenhum suporte à associação para abstrair interfaces e classes básicas. Para demonstrar essa deficiência, refatore a classe Product para que seja derivada de uma interface denominada IProduct, que consiste em propriedades somente leitura. Da mesma maneira, atualize a ação Create do controlador para que aceite a nova interface IProduct em vez da implementação concreta de Product, conforme mostrado na Figura 6.

Figura 6 Associação a uma interface

public interface IProduct
{
  DateTime AvailabilityDate { get; }
  int CategoryId { get; }
  string Description { get; }
  ProductKind Kind { get; }
  string Name { get; }
  decimal UnitPrice { get; }
  int UnitsInStock { get; }
}
 
public ActionResult Create(IProduct product)
{
  // ...
}

A ação Create atualizada mostrada na Figura 6, embora seja código C# perfeitamente legítimo, faz com que o DefaultModelBinder gere a exceção: “Não é possível criar uma instância de uma interface”. É bastante compreensível que o associador de modelo gere essa exceção, uma vez que o DefaultModelBinder não tem como saber qual tipo concreto de IProduct criar.

A maneira mais simples de solucionar esse problema é criar um associador de modelo personalizado que implemente a interface IModelBinder. A Figura 7 mostra o ProductModelBinder, um associador de modelo personalizado que sabe como criar e associar uma instância da interface IProduct.

Figura 7 ProductModelBinder — um associador de modelo personalizado rigidamente acoplado

public class ProductModelBinder : IModelBinder
{
  public object BindModel
    (
      ControllerContext controllerContext,
      ModelBindingContext bindingContext
    )
  {
    var product = new Product() {
      Description = GetValue(bindingContext, "Description"),
      Name = GetValue(bindingContext, "Name"),
  }; 
 
    string availabilityDateValue = 
      GetValue(bindingContext, "AvailabilityDate");

    if(availabilityDateValue != null)
    {
      DateTime date;
      if (DateTime.TryParse(availabilityDateValue, out date))
      product.AvailabilityDate = date;
    }
 
    string categoryIdValue = 
      GetValue(bindingContext, "CategoryId");

    if (categoryIdValue != null)
    {
      int categoryId;
      if (Int32.TryParse(categoryIdValue, out categoryId))
      product.CategoryId = categoryId;
    }
 
    // Repeat custom binding code for every property
    // ...
 
    return product;
  }
 
  private string GetValue(
    ModelBindingContext bindingContext, string key)
  {
    var result = bindingContext.ValueProvider.GetValue(key);
    return (result == null) ? null : result.AttemptedValue;
  }
}

A desvantagem de criar associadores de modelo personalizados que implementem a interface IModelBinder diretamente é que eles normalmente duplicam muito do DefaultModelBinder apenas para modificar algumas áreas da lógica. Também é comum que esses associadores personalizados focalizem classes de modelo específicas, criando um acoplamento rígido entre a estrutura e a camada de negócios, e limitando a reutilização para oferecer suporte a outros tipos de modelo.

Para evitar todos esses problemas em seus associadores de modelo personalizados, considere derivar do DefaultModelBinder e substituir comportamentos específicos para que se adéquem a suas necessidades. Frequentemente, essa abordagem fornece o melhor dos dois mundos.

Associador de modelo abstrato O único problema ao tentar aplicar a associação de modelo a uma interface com o DefaultModelBinder é que ele não sabe como determinar o tipo de modelo concreto. Considere a meta de nível superior: a habilidade de desenvolver ações do controlador em um tipo não concreto e determinar dinamicamente o tipo concreto de cada solicitação.

Derivando do DefaultModelBinder e substituindo apenas a lógica que determina o tipo de modelo de destino, é possível não apenas resolver o cenário específico de IProduct, mas realmente criar um associador de modelo genérico que também pode tratar a maior parte das outras hierarquias da interface. A Figura 8 mostra um exemplo de um associador de modelo abstrato genérico.

Figura 8 Um associador de modelo abstrato genérico

public class AbstractModelBinder : DefaultModelBinder
{
  private readonly string _typeNameKey;

  public AbstractModelBinder(string typeNameKey = null)
  {
    _typeNameKey = typeNameKey ?? "__type__";
  }

  public override object BindModel
  (
    ControllerContext controllerContext,
    ModelBindingContext bindingContext
  )
  {
    var providerResult =
    bindingContext.ValueProvider.GetValue(_typeNameKey);

    if (providerResult != null)
    {
      var modelTypeName = providerResult.AttemptedValue;

      var modelType =
        BuildManager.GetReferencedAssemblies()
          .Cast<Assembly>()
          .SelectMany(x => x.GetExportedTypes())
          .Where(type => !type.IsInterface)
          .Where(type => !type.IsAbstract)
          .Where(bindingContext.ModelType.IsAssignableFrom)
          .FirstOrDefault(type =>
            string.Equals(type.Name, modelTypeName,
              StringComparison.OrdinalIgnoreCase));

      if (modelType != null)
      {
        var metaData =
        ModelMetadataProviders.Current
        .GetMetadataForType(null, modelType);

        bindingContext.ModelMetadata = metaData;
      }
    }

    // Fall back to default model binding behavior
    return base.BindModel(controllerContext, bindingContext);
  }
}

Para dar suporte à associação de modelo a uma interface, o associador de modelo deve primeiro converter a interface em um tipo concreto. Para conseguir isso, o AbstractModelBinder solicita a chave “__type__” dos provedores de valor da solicitação. A utilização de provedores de valor para esse tipo de dados fornece flexibilidade até onde o valor de “__type__” esteja definido. Por exemplo, a chave pode ser definida como parte da rota (nos dados da rota), especificada como um parâmetro querystring ou até representada como um campo nos dados da postagem do formulário.

Em seguida, o AbstractModelBinder usa o nome do tipo concreto para gerar um novo conjunto de metadados que descrevem os detalhes da classe concreta. O AbstractModelBinder usa esses novos metadados para substituir a propriedade ModelMetadata existente que descreveu o tipo do modelo abstrato inicial, fazendo efetivamente com que o associador de modelo esqueça que foi associado a um tipo não concreto para começar.

Depois que o AbstractModelBinder substitui os metadados do modelo por todas as informações necessárias para associação ao modelo adequado, ele simplesmente libera o controle de volta para a lógica básica do DefaultModelBinder para permitir que ela manipule o restante do trabalho.

O AbstractModelBinder é um excelente exemplo que mostra como é possível estender a lógica de associação padrão com sua própria lógica personalizada sem reinventar a roda, derivando diretamente da interface IModelBinder.

Seleção de associador de modelo

A criação de associadores de modelo personalizados é apenas a primeira etapa. Para aplicar a lógica de associação de modelo personalizada em seu aplicativo, você também deve registrar os associadores de modelo personalizados. A maioria dos tutoriais mostra duas maneiras de registrar associadores de modelo personalizados.

A coleção global de ModelBinders A maneira geralmente recomendada de substituir o associador de modelo para tipos específicos é registrar um mapeamento de tipo para associador ao dicionário ModelBinders.Binders.

O trecho de código a seguir informa a estrutura para usar o AbstractModelBinder para associar modelos de Currency:

ModelBinders.Binders.Add(typeof(Currency), new AbstractModelBinder());

Substituindo o associador de modelo padrão Como alternativa, para substituir o manipulador padrão global, você pode atribuir um associador de modelo à propriedade ModelBinders.Binders.DefaultBinder. Por exemplo:

ModelBinders.Binders.DefaultBinder = new AbstractModelBinder();

Embora essas duas abordagens funcionem bem para a maioria dos cenários, há mais duas maneiras em que o ASP.NET MVC permite registrar um associador de modelo para um tipo: atributos e provedores.

Adicionando adornos a modelos com atributos personalizados

Além de adicionar um mapeamento de tipo ao dicionário ModelBinders, a estrutura do ASP.NET MVC também oferece o System.Web.Mvc.CustomModelBinderAttribute abstrato, um atributo que permite criar dinamicamente um associador de modelo para cada classe ou propriedade à qual o atributo é aplicado. A Figura 9 mostra uma implementação de CustomModelBinderAttribute que cria um AbstractModelBinder.

Figura 9 Uma implementação de CustomModelBinderAttribute

[AttributeUsage(
  AttributeTargets.Class | AttributeTargets.Enum |
  AttributeTargets.Interface | AttributeTargets.Parameter |
  AttributeTargets.Struct | AttributeTargets.Property,
  AllowMultiple = false, Inherited = false
)]
public class AbstractModelBinderAttribute : CustomModelBinderAttribute
{
  public override IModelBinder GetBinder()
  {
    return new AbstractModelBinder();
  }
}

Em seguida, você pode aplicar o AbstractModelBinderAttribute a qualquer classe ou propriedade de modelo, desta maneira:

public class Product
{
  [AbstractModelBinder]
  public IEnumerable<CurrencyRequest> UnitPrice { get; set; }
  // ...
}

Agora, quando o associador de modelo tentar localizar o localizador apropriado para Product.UnitPrice, ele descobrirá o AbstractModelBinderAttribute e usará o AbstractModelBinder para associar a propriedade Product.UnitPrice.

A utilização de atributos de associador de modelo personalizado é uma boa maneira de realizar uma abordagem mais declarativa à configuração de associadores de modelo mantendo a coleção global de associadores de modelo simples. Além disso, como os atributos de associador de modelo personalizado podem ser aplicados a classes inteiras e a propriedades individuais, você tem um controle refinado sobre o processo de associação de modelo.

Pergunte aos associadores!

Os provedores de associador de modelo oferecem a capacidade de executar código arbitrário em tempo real para determinar o melhor associador de modelo possível para um determinado tipo. Portanto, eles fornecem um meio-termo excelente entre registro explícito de associador de modelo para tipos de modelo individuais, registro baseado em atributo estático e um conjunto de associadores de modelo padrão para todos os tipos.

O código a seguir mostra como criar um IModelBinderProvider que fornece um AbstractModelBinder para todas as interfaces e tipos abstratos:

public class AbstractModelBinderProvider : IModelBinderProvider
{
  public IModelBinder GetBinder(Type modelType)
  {
    if (modelType.IsAbstract || modelType.IsInterface)
      return new AbstractModelBinder();
 
    return null;
  }
}

A lógica que controla se o AbstractModelBinder se aplica a um determinado tipo de modelo é relativamente simples: O tipo é não concreto? Se for, o AbstractModelBinder será o associador de modelo adequado para o tipo, portanto, o associador de modelo será instanciado e retornado. Se o tipo for um tipo concreto, o AbstractModelBinder não será adequado. Um valor nulo será retornado para indicar que o associador de modelo não é uma correspondência desse tipo.

Ao implementar a lógica do .GetBinder, é importante lembrar que a lógica será executada para cada propriedade que seja uma candidata à associação de modelo, portanto, mantenha-a leve para evitar introduzir problemas de desempenho no aplicativo.

Para começar a usar um provedor de associador de modelo, adicione-o à lista de provedores mantidos na coleção de ModelBinderProviders.BinderProviders. Por exemplo, registre o AbstractModelBinder da seguinte maneira:

var provider = new AbstractModelBinderProvider();
ModelBinderProviders.BinderProviders.Add(provider);

E dessa maneira fácil, você adicionou suporte à associação de modelo para tipos não concretos em todo o seu aplicativo.

A abordagem de associação de modelo torna a seleção de associação de modelo muito mais dinâmica removendo a carga de determinação do associador de modelo apropriado para fora da estrutura e colocando-a no local mais apropriado: os próprios associadores de modelo.

Pontos principais de extensibilidade

Como qualquer outro método, a associação de modelo ASP.NET MVC permite que as ações do controlador aceitem tipos de objetos complexos como parâmetros. A associação de modelo também incentiva uma separação melhor das preocupações com a lógica de população de objetos da lógica que usa objetos populados.

Explorei alguns pontos principais de extensibilidade na estrutura de associação de modelo que podem ajudar você a otimizá-la ao máximo. Compreender a associação de modelo do ASP.NET MVC e como usá-la adequadamente pode ter um grande impacto, até no mais simples dos aplicativos.

Jess Chadwick é um consultor de software independente com especialização em tecnologias Web. Ele tem mais de uma década de experiência em desenvolvimento, de dispositivos incorporados em inicializações a Web farms de escala empresarial em empresas da Fortune 500. Jess é ASPInsider, Microsoft MVP em ASP.NET e autor de livros e revistas. Está ativamente envolvido na comunidade de desenvolvimento, falando regularmente em grupos de usuários e conferências e liderando o grupo de usuários de .Net, o NJDOTNET Central, de New Jersey.

Agradecemos ao seguinte especialista técnico por rever este artigo: Phil Haack