Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Por Kirk Larkin
A associação de modelo permite que as ações do controlador funcionem diretamente com tipos de modelo (passados como argumentos de método), em vez de solicitações HTTP. O mapeamento entre os dados de solicitação de entrada e os modelos de aplicativo é tratado por associadores de modelo. Os desenvolvedores podem estender a funcionalidade de vinculação de modelos incorporada implementando vinculadores de modelo personalizados, embora normalmente você não precise desenvolver seu próprio provedor.
Exibir ou baixar código de exemplo (como baixar)
Limitações padrão do associador de modelo
Os associadores de modelo padrão dão suporte à maioria dos tipos de dados comuns do .NET Core e devem atender às necessidades da maioria dos desenvolvedores. Eles esperam associar a entrada baseada em texto da solicitação diretamente aos tipos de modelo. Talvez seja necessário transformar a entrada antes de vinculá-la. Por exemplo, quando você tem uma chave que pode ser usada para pesquisar dados de modelo. Você pode usar um associador de modelo personalizado para buscar dados com base na chave.
Model binding de tipos simples e complexos
O model binding usa definições específicas para os tipos nos quais opera. Um tipo simples é convertido usando TypeConverter ou um método TryParse a partir de uma única cadeia de caracteres. Um tipo complexo é convertido de vários valores de entrada. A estrutura determina a diferença de acordo com a existência de um TypeConverter ou TryParse. É recomendável criar um conversor de tipo ou usar TryParse para uma conversão de string em SomeType que não exija recursos externos ou várias entradas.
Consulte tipos simples para obter uma lista de tipos que o associador de modelo pode converter de cadeias de caracteres.
Antes de criar seu próprio associador de modelo personalizado, vale a pena examinar como os associadores de modelo existentes são implementados. Considere o ByteArrayModelBinder que pode ser usado para converter cadeias de caracteres codificadas em base64 em matrizes de bytes. As matrizes de bytes geralmente são armazenadas como arquivos ou campos BLOB de banco de dados.
Trabalhando com o ByteArrayModelBinder
Cadeias de caracteres codificadas em Base64 podem ser usadas para representar dados binários. Por exemplo, uma imagem pode ser codificada como uma cadeia de caracteres. O exemplo inclui uma imagem como uma cadeia de caracteres codificada em base64 em Base64String.txt.
ASP.NET Core MVC pode pegar uma cadeia de caracteres codificada em base64 e usar uma ByteArrayModelBinder para convertê-la em uma matriz de bytes. Os ByteArrayModelBinderProvider mapeia os argumentos byte[] para ByteArrayModelBinder:
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(byte[]))
{
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
return new ByteArrayModelBinder(loggerFactory);
}
return null;
}
Ao criar seu próprio associador de modelo personalizado, você pode implementar um tipo próprio de IModelBinderProvider, ou usar o ModelBinderAttribute.
O exemplo a seguir mostra como usar ByteArrayModelBinder para converter uma cadeia de caracteres codificada em base64 em um byte[] e salvar o resultado em um arquivo:
[HttpPost]
public void Post([FromForm] byte[] file, string filename)
{
// Don't trust the file name sent by the client. Use
// Path.GetRandomFileName to generate a safe random
// file name. _targetFilePath receives a value
// from configuration (the appsettings.json file in
// the sample app).
var trustedFileName = Path.GetRandomFileName();
var filePath = Path.Combine(_targetFilePath, trustedFileName);
if (System.IO.File.Exists(filePath))
{
return;
}
System.IO.File.WriteAllBytes(filePath, file);
}
Se você quiser ver comentários de código traduzidos para idiomas diferentes do inglês, informe-nos neste problema de discussão do GitHub.
Você pode POSTAR uma cadeia de caracteres codificada em base64 para o método de API anterior usando uma ferramenta como curl.
Desde que o associador possa associar dados de solicitação a propriedades ou argumentos nomeados adequadamente, a associação de modelo terá êxito. O exemplo a seguir mostra como usar ByteArrayModelBinder com um modelo de exibição:
[HttpPost("Profile")]
public void SaveProfile([FromForm] ProfileViewModel model)
{
// Don't trust the file name sent by the client. Use
// Path.GetRandomFileName to generate a safe random
// file name. _targetFilePath receives a value
// from configuration (the appsettings.json file in
// the sample app).
var trustedFileName = Path.GetRandomFileName();
var filePath = Path.Combine(_targetFilePath, trustedFileName);
if (System.IO.File.Exists(filePath))
{
return;
}
System.IO.File.WriteAllBytes(filePath, model.File);
}
public class ProfileViewModel
{
public byte[] File { get; set; }
public string FileName { get; set; }
}
Exemplo de associador de modelo personalizado
Nesta seção, implementaremos um vinculador de modelo personalizado que:
- Converte dados de solicitação de entrada em argumentos de chave fortemente tipados.
- Usa o Entity Framework Core para buscar a entidade associada.
- Passa a entidade associada como um argumento para o método de ação.
O exemplo a seguir usa o ModelBinder atributo no Author modelo:
using CustomModelBindingSample.Binders;
using Microsoft.AspNetCore.Mvc;
namespace CustomModelBindingSample.Data
{
[ModelBinder(BinderType = typeof(AuthorEntityBinder))]
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public string GitHub { get; set; }
public string Twitter { get; set; }
public string BlogUrl { get; set; }
}
}
No código anterior, o atributo ModelBinder especifica o tipo de IModelBinder que deve ser usado para vincular os parâmetros de ação Author.
A classe a seguir AuthorEntityBinder associa um Author parâmetro ao buscar a entidade de uma fonte de dados usando o Entity Framework Core e um authorId:
public class AuthorEntityBinder : IModelBinder
{
private readonly AuthorContext _context;
public AuthorEntityBinder(AuthorContext context)
{
_context = context;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var modelName = bindingContext.ModelName;
// Try to fetch the value of the argument by name
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
if (!int.TryParse(value, out var id))
{
// Non-integer arguments result in model state errors
bindingContext.ModelState.TryAddModelError(
modelName, "Author Id must be an integer.");
return Task.CompletedTask;
}
// Model will be null if not found, including for
// out of range id values (0, -3, etc.)
var model = _context.Authors.Find(id);
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Note
A classe anterior AuthorEntityBinder destina-se a ilustrar um vinculador de modelo personalizado. A classe não se destina a ilustrar as práticas recomendadas para um cenário de pesquisa. Para pesquisa, associe o authorId e faça uma consulta no banco de dados em um método de ação. Essa abordagem separa as falhas de associação de modelo dos casos de NotFound.
O código a seguir mostra como usar o AuthorEntityBinder em um método de ação:
[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
O ModelBinder atributo pode ser usado para aplicar os AuthorEntityBinder parâmetros que não usam convenções padrão:
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
Neste exemplo, como o nome do argumento não é o padrão authorId, ele é especificado no parâmetro usando o ModelBinder atributo. O controlador e o método de ação são simplificados quando comparados à tarefa de buscar a entidade diretamente no método de ação. A lógica para buscar o autor usando o Entity Framework Core foi movida para o model binder. Isso pode ser uma simplificação considerável quando você tem vários métodos que se associam ao Author modelo.
Você pode aplicar o ModelBinder atributo a propriedades de modelo individuais (como em um viewmodel) ou a parâmetros de método de ação para especificar um determinado associador de modelo ou nome de modelo apenas para esse tipo ou ação.
Implementando um provedor de ModelBinder
Em vez de aplicar um atributo, você pode implementar IModelBinderProvider. É assim que os associadores de estrutura internos são implementados. Quando você especifica o tipo sobre o qual seu associador opera, você está especificando o tipo de argumento que ele produz, não a entrada aceita pelo associador. O provedor de vinculador a seguir funciona com o AuthorEntityBinder. Quando adicionado à coleção de provedores do MVC, não é necessário usar o atributo ModelBinder em parâmetros do tipo Author ou Author.
using CustomModelBindingSample.Data;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;
namespace CustomModelBindingSample.Binders
{
public class AuthorEntityBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(Author))
{
return new BinderTypeModelBinder(typeof(AuthorEntityBinder));
}
return null;
}
}
}
Observação: o código anterior retorna um
BinderTypeModelBinder.BinderTypeModelBinderatua como uma fábrica para vinculadores de modelo e fornece DI (injeção de dependência). Para acessar oAuthorEntityBinder, o EF Core requer DI. UseBinderTypeModelBinderse o associador de modelo exigir serviços de DI.
Para usar um provedor de associador de modelo personalizado, adicione-o em ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AuthorContext>(options => options.UseInMemoryDatabase("Authors"));
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
});
}
Ao avaliar vinculadores de modelo, a coleção de provedores é examinada sequencialmente. O primeiro provedor que retorna um vinculador que corresponde ao modelo fornecido é utilizado. Adicionar seu provedor ao final da coleção pode, portanto, resultar em um associador de modelo interno sendo chamado antes que seu associador personalizado tenha uma chance. Neste exemplo, o provedor personalizado é adicionado ao início da coleção para garantir que ele seja sempre usado para Author argumentos de ação.
Associação de modelo polimórfico
A associação a diferentes modelos de tipos derivados é conhecida como associação de modelo polimórfico. A associação de modelo personalizado polimórfico é necessária quando o valor da solicitação deve ser associado ao tipo de modelo derivado específico. Associação de modelo polimórfico:
- Não é típico de uma API REST projetada para interoperar com todos os idiomas.
- Torna difícil compreender os modelos vinculados.
No entanto, se um aplicativo exigir associação de modelo polimórfico, uma implementação poderá ser semelhante ao seguinte código:
public abstract class Device
{
public string Kind { get; set; }
}
public class Laptop : Device
{
public string CPUIndex { get; set; }
}
public class SmartPhone : Device
{
public string ScreenSize { get; set; }
}
public class DeviceModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(Device))
{
return null;
}
var subclasses = new[] { typeof(Laptop), typeof(SmartPhone), };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in subclasses)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new DeviceModelBinder(binders);
}
}
public class DeviceModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Device.Kind));
var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;
IModelBinder modelBinder;
ModelMetadata modelMetadata;
if (modelTypeValue == "Laptop")
{
(modelMetadata, modelBinder) = binders[typeof(Laptop)];
}
else if (modelTypeValue == "SmartPhone")
{
(modelMetadata, modelBinder) = binders[typeof(SmartPhone)];
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
if (newBindingContext.Result.IsModelSet)
{
// Setting the ValidationState ensures properties on derived types are correctly
bindingContext.ValidationState[newBindingContext.Result.Model] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
}
Recomendações e práticas recomendadas
Associadores de modelos personalizados:
- Não deve tentar definir códigos de status ou retornar resultados (por exemplo, 404 Não Encontrado). Se a associação de modelo falhar, um filtro de ação ou lógica dentro do próprio método de ação deverá lidar com a falha.
- São mais úteis para eliminar o código repetitivo e as preocupações transversais dos métodos de ação.
- Normalmente não deve ser usado para converter uma cadeia de caracteres em um tipo personalizado, uma TypeConverter geralmente é uma opção melhor.
Por Steve Smith
A associação de modelo permite que as ações do controlador funcionem diretamente com tipos de modelo (passados como argumentos de método), em vez de solicitações HTTP. O mapeamento entre os dados de solicitação de entrada e os modelos de aplicativo é tratado por associadores de modelo. Os desenvolvedores podem estender a funcionalidade de vinculação de modelos incorporada implementando vinculadores de modelo personalizados, embora normalmente você não precise desenvolver seu próprio provedor.
Exibir ou baixar código de exemplo (como baixar)
Limitações padrão do associador de modelo
Os associadores de modelo padrão dão suporte à maioria dos tipos de dados comuns do .NET Core e devem atender às necessidades da maioria dos desenvolvedores. Eles esperam associar a entrada baseada em texto da solicitação diretamente aos tipos de modelo. Talvez seja necessário transformar a entrada antes de vinculá-la. Por exemplo, quando você tem uma chave que pode ser usada para pesquisar dados de modelo. Você pode usar um associador de modelo personalizado para buscar dados com base na chave.
Revisão de associação de modelo
O model binding usa definições específicas para os tipos nos quais opera. Um tipo simples é convertido a partir de uma única string na entrada. Um tipo complexo é convertido de vários valores de entrada. A estrutura determina a diferença com base na existência de um TypeConverter. Recomendamos que você crie um conversor de tipo se tiver um mapeamento simples string>SomeType que não exija recursos externos.
Antes de criar seu próprio associador de modelo personalizado, vale a pena examinar como os associadores de modelo existentes são implementados. Considere o ByteArrayModelBinder que pode ser usado para converter cadeias de caracteres codificadas em base64 em matrizes de bytes. As matrizes de bytes geralmente são armazenadas como arquivos ou campos BLOB de banco de dados.
Trabalhando com o ByteArrayModelBinder
Cadeias de caracteres codificadas em Base64 podem ser usadas para representar dados binários. Por exemplo, uma imagem pode ser codificada como uma cadeia de caracteres. O exemplo inclui uma imagem como uma cadeia de caracteres codificada em base64 em Base64String.txt.
ASP.NET Core MVC pode pegar uma cadeia de caracteres codificada em base64 e usar uma ByteArrayModelBinder para convertê-la em uma matriz de bytes. Os ByteArrayModelBinderProvider mapeia os argumentos byte[] para ByteArrayModelBinder:
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(byte[]))
{
return new ByteArrayModelBinder();
}
return null;
}
Ao criar seu próprio associador de modelo personalizado, você pode implementar um tipo próprio de IModelBinderProvider, ou usar o ModelBinderAttribute.
O exemplo a seguir mostra como usar ByteArrayModelBinder para converter uma cadeia de caracteres codificada em base64 em um byte[] e salvar o resultado em um arquivo:
[HttpPost]
public void Post([FromForm] byte[] file, string filename)
{
// Don't trust the file name sent by the client. Use
// Path.GetRandomFileName to generate a safe random
// file name. _targetFilePath receives a value
// from configuration (the appsettings.json file in
// the sample app).
var trustedFileName = Path.GetRandomFileName();
var filePath = Path.Combine(_targetFilePath, trustedFileName);
if (System.IO.File.Exists(filePath))
{
return;
}
System.IO.File.WriteAllBytes(filePath, file);
}
Você pode POSTAR uma cadeia de caracteres codificada em base64 para o método de API anterior usando uma ferramenta como curl.
Desde que o associador possa associar dados de solicitação a propriedades ou argumentos nomeados adequadamente, a associação de modelo terá êxito. O exemplo a seguir mostra como usar ByteArrayModelBinder com um modelo de exibição:
[HttpPost("Profile")]
public void SaveProfile([FromForm] ProfileViewModel model)
{
// Don't trust the file name sent by the client. Use
// Path.GetRandomFileName to generate a safe random
// file name. _targetFilePath receives a value
// from configuration (the appsettings.json file in
// the sample app).
var trustedFileName = Path.GetRandomFileName();
var filePath = Path.Combine(_targetFilePath, trustedFileName);
if (System.IO.File.Exists(filePath))
{
return;
}
System.IO.File.WriteAllBytes(filePath, model.File);
}
public class ProfileViewModel
{
public byte[] File { get; set; }
public string FileName { get; set; }
}
Exemplo de associador de modelo personalizado
Nesta seção, implementaremos um vinculador de modelo personalizado que:
- Converte dados de solicitação de entrada em argumentos de chave fortemente tipados.
- Usa o Entity Framework Core para buscar a entidade associada.
- Passa a entidade associada como um argumento para o método de ação.
O exemplo a seguir usa o ModelBinder atributo no Author modelo:
using CustomModelBindingSample.Binders;
using Microsoft.AspNetCore.Mvc;
namespace CustomModelBindingSample.Data
{
[ModelBinder(BinderType = typeof(AuthorEntityBinder))]
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public string GitHub { get; set; }
public string Twitter { get; set; }
public string BlogUrl { get; set; }
}
}
No código anterior, o atributo ModelBinder especifica o tipo de IModelBinder que deve ser usado para vincular os parâmetros de ação Author.
A classe a seguir AuthorEntityBinder associa um Author parâmetro ao buscar a entidade de uma fonte de dados usando o Entity Framework Core e um authorId:
public class AuthorEntityBinder : IModelBinder
{
private readonly AppDbContext _db;
public AuthorEntityBinder(AppDbContext db)
{
_db = db;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var modelName = bindingContext.ModelName;
// Try to fetch the value of the argument by name
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
if (!int.TryParse(value, out var id))
{
// Non-integer arguments result in model state errors
bindingContext.ModelState.TryAddModelError(
modelName, "Author Id must be an integer.");
return Task.CompletedTask;
}
// Model will be null if not found, including for
// out of range id values (0, -3, etc.)
var model = _db.Authors.Find(id);
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Note
A classe anterior AuthorEntityBinder destina-se a ilustrar um vinculador de modelo personalizado. A classe não se destina a ilustrar as práticas recomendadas para um cenário de pesquisa. Para pesquisa, associe o authorId e faça uma consulta no banco de dados em um método de ação. Essa abordagem separa as falhas de associação de modelo dos casos de NotFound.
O código a seguir mostra como usar o AuthorEntityBinder em um método de ação:
[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
O ModelBinder atributo pode ser usado para aplicar os AuthorEntityBinder parâmetros que não usam convenções padrão:
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
Neste exemplo, como o nome do argumento não é o padrão authorId, ele é especificado no parâmetro usando o ModelBinder atributo. O controlador e o método de ação são simplificados quando comparados à tarefa de buscar a entidade diretamente no método de ação. A lógica para buscar o autor usando o Entity Framework Core foi movida para o model binder. Isso pode ser uma simplificação considerável quando você tem vários métodos que se associam ao Author modelo.
Você pode aplicar o ModelBinder atributo a propriedades de modelo individuais (como em um viewmodel) ou a parâmetros de método de ação para especificar um determinado associador de modelo ou nome de modelo apenas para esse tipo ou ação.
Implementando um provedor de ModelBinder
Em vez de aplicar um atributo, você pode implementar IModelBinderProvider. É assim que os associadores de estrutura internos são implementados. Quando você especifica o tipo sobre o qual seu associador opera, você está especificando o tipo de argumento que ele produz, não a entrada aceita pelo associador. O provedor de vinculador a seguir funciona com o AuthorEntityBinder. Quando adicionado à coleção de provedores do MVC, não é necessário usar o atributo ModelBinder em parâmetros do tipo Author ou Author.
using CustomModelBindingSample.Data;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;
namespace CustomModelBindingSample.Binders
{
public class AuthorEntityBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(Author))
{
return new BinderTypeModelBinder(typeof(AuthorEntityBinder));
}
return null;
}
}
}
Observação: o código anterior retorna um
BinderTypeModelBinder.BinderTypeModelBinderatua como uma fábrica para vinculadores de modelo e fornece DI (injeção de dependência). Para acessar oAuthorEntityBinder, o EF Core requer DI. UseBinderTypeModelBinderse o associador de modelo exigir serviços de DI.
Para usar um provedor de associador de modelo personalizado, adicione-o em ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options => options.UseInMemoryDatabase("App"));
services.AddMvc(options =>
{
// add custom binder to beginning of collection
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
Ao avaliar vinculadores de modelo, a coleção de provedores é examinada sequencialmente. O primeiro provedor que retornar um vinculador será usado. Adicionar seu provedor ao final da coleção pode resultar em um associador de modelo integrado ser chamado antes que seu associador personalizado tenha a chance de ser executado. Neste exemplo, o provedor personalizado é adicionado ao início da coleção para garantir seu uso nos argumentos de ação Author.
Associação de modelo polimórfico
A associação a diferentes modelos de tipos derivados é conhecida como associação de modelo polimórfico. A associação de modelo personalizado polimórfico é necessária quando o valor da solicitação deve ser associado ao tipo de modelo derivado específico. Associação de modelo polimórfico:
- Não é típico de uma API REST projetada para interoperar com todos os idiomas.
- Torna difícil compreender os modelos vinculados.
No entanto, se um aplicativo exigir associação de modelo polimórfico, uma implementação poderá ser semelhante ao seguinte código:
public abstract class Device
{
public string Kind { get; set; }
}
public class Laptop : Device
{
public string CPUIndex { get; set; }
}
public class SmartPhone : Device
{
public string ScreenSize { get; set; }
}
public class DeviceModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(Device))
{
return null;
}
var subclasses = new[] { typeof(Laptop), typeof(SmartPhone), };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in subclasses)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new DeviceModelBinder(binders);
}
}
public class DeviceModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Device.Kind));
var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;
IModelBinder modelBinder;
ModelMetadata modelMetadata;
if (modelTypeValue == "Laptop")
{
(modelMetadata, modelBinder) = binders[typeof(Laptop)];
}
else if (modelTypeValue == "SmartPhone")
{
(modelMetadata, modelBinder) = binders[typeof(SmartPhone)];
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
if (newBindingContext.Result.IsModelSet)
{
// Setting the ValidationState ensures properties on derived types are correctly
bindingContext.ValidationState[newBindingContext.Result.Model] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
}
Recomendações e práticas recomendadas
Associadores de modelos personalizados:
- Não deve tentar definir códigos de status ou retornar resultados (por exemplo, 404 Não Encontrado). Se a associação de modelo falhar, um filtro de ação ou lógica dentro do próprio método de ação deverá lidar com a falha.
- São mais úteis para eliminar o código repetitivo e as preocupações transversais dos métodos de ação.
- Normalmente não deve ser usado para converter uma cadeia de caracteres em um tipo personalizado, uma TypeConverter geralmente é uma opção melhor.