Compartir a través de


Enlace de modelos personalizado en ASP.NET Core

Por Kirk Larkin

El enlace de modelos permite que las acciones del controlador funcionen directamente con los tipos de modelo (pasados como argumentos de método), en lugar de las solicitudes HTTP. La asignación entre los datos de solicitudes entrantes y los modelos de aplicaciones se controla por medio de enlazadores de modelos. Los desarrolladores pueden ampliar la funcionalidad integrada de enlace de modelos implementando enlazadores de modelos personalizados (si bien, por lo general, no es necesario escribir un proveedor propio).

Vea o descargue el código de ejemplo (cómo descargarlo)

Limitaciones de los enlazadores de modelos predeterminados

Los enlazadores de modelos predeterminados admiten la mayoría de los tipos de datos comunes de .NET Core y deben satisfacer la mayoría de las necesidades de los desarrolladores. Esperan enlazar la entrada basada en texto desde la solicitud directamente a los tipos de modelo. Es posible que tenga que transformar la entrada antes de enlazarla. Por ejemplo, cuando tiene una clave que se puede usar para buscar datos del modelo. Puede usar un enlazador de modelos personalizado para obtener datos en función de la clave.

Tipos simples y complejos de enlace de modelos

El enlace de modelos usa definiciones específicas para los tipos en los que opera. Un tipo simple se convierte a partir de una sola cadena mediante un TypeConverter o un TryParse método. Un tipo complejo se convierte a partir de varios valores de entrada. El marco determina la diferencia en función de la existencia de TypeConverter o TryParse. Se recomienda crear un convertidor de tipos o usar TryParse para una stringSomeType conversión que no requiera recursos externos ni varias entradas.

Consulte Tipos simples para obtener una lista de tipos que el enlazador de modelos puede convertir de cadenas.

Antes de crear su propio enlazador de modelos personalizado, merece la pena revisar cómo se implementan los enlazadores de modelos existentes. Tenga en cuenta el ByteArrayModelBinder que se puede usar para convertir cadenas codificadas en base64 en matrices de bytes. Las matrices de bytes a menudo se almacenan como archivos o campos BLOB de base de datos.

Trabajar con ByteArrayModelBinder

Las cadenas codificadas en Base64 se pueden usar para representar datos binarios. Por ejemplo, una imagen se puede codificar como una cadena. El ejemplo incluye una imagen como una cadena codificada en base64 en Base64String.txt.

ASP.NET Core MVC puede tomar una cadena codificada en base64 y usar un ByteArrayModelBinder para convertirlo en una matriz de bytes. ByteArrayModelBinderProvider mapea argumentos byte[] a 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;
}

Al crear su propio enlazador de modelos personalizado, puede implementar su propio IModelBinderProvider tipo o usar el ModelBinderAttribute.

En el ejemplo siguiente se muestra cómo usar ByteArrayModelBinder para convertir una cadena codificada en base64 en y byte[] guardar el resultado en un archivo:

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

Si quiere que los comentarios de código se traduzcan en más idiomas además del inglés, háganoslo saber en este problema de debate de GitHub.

Puede publicar una cadena codificada en base64 en el método de API anterior mediante una herramienta como curl.

Siempre que el vinculador pueda enlazar datos de solicitud a propiedades o argumentos correctamente nombrados, la vinculación del modelo se llevará a cabo con éxito. En el ejemplo siguiente se muestra cómo usar ByteArrayModelBinder con un modelo de vista:

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

Ejemplo de enlazador de modelos personalizado

En esta sección implementaremos un enlazador de modelos personalizado que:

  • Convierte los datos de solicitud entrantes en argumentos de clave fuertemente tipificados.
  • Usa Entity Framework Core para capturar la entidad asociada.
  • Pasa la entidad asociada como argumento al método de operación.

En el ejemplo siguiente se usa el ModelBinder atributo en el 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; }
    }
}

En el código anterior, el ModelBinder atributo especifica el tipo de IModelBinder que se debe usar para enlazar Author parámetros de acción.

La siguiente AuthorEntityBinder clase enlaza un Author parámetro recuperando la entidad desde un origen de datos utilizando Entity Framework Core y un 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

La clase anterior AuthorEntityBinder está pensada para ilustrar un enlazador de modelos personalizado. La clase no está pensada para ilustrar los procedimientos recomendados para un escenario de búsqueda. Para la búsqueda, enlace el valor authorId y consulte la base de datos en un método de acción. Este enfoque separa los errores de enlace de modelos de los casos de NotFound.

En el código siguiente se muestra cómo usar el AuthorEntityBinder en un método de acción.

[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
    if (author == null)
    {
        return NotFound();
    }

    return Ok(author);
}

El ModelBinder atributo se puede usar para aplicar a AuthorEntityBinder los parámetros que no usan convenciones predeterminadas:

[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
    if (author == null)
    {
        return NotFound();
    }

    return Ok(author);
}

En este ejemplo, dado que el nombre del argumento no es el predeterminado authorId, se especifica en el parámetro mediante el ModelBinder atributo . Tanto el controlador como el método de acción se simplifican, en contraste con tener que buscar la entidad en el método de acción. La lógica para capturar el autor a través de Entity Framework Core se traslada al enlazador de modelos. Esto puede ser una simplificación considerable cuando tiene varios métodos que se enlazan al Author modelo.

Puede aplicar el ModelBinder atributo a propiedades de modelo individuales (como en un modelo de vista) o a parámetros de método de acción para especificar un determinado enlazador de modelos o un nombre de modelo solo para ese tipo o acción.

Implementar un ModelBinderProvider

En lugar de aplicar un atributo, puede implementar IModelBinderProvider. Así es como se implementan los enlazadores de marcos integrados. Al especificar el tipo en el que opera el enlazador, se especifica el tipo de argumento que genera, no la entrada que acepta el enlazador. El siguiente proveedor de enlazador funciona con AuthorEntityBinder. Cuando se agrega a la colección de proveedores de MVC, no es necesario usar el atributo ModelBinder en parámetros de tipo Author o 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;
        }
    }
}

Nota: El código anterior devuelve un BinderTypeModelBinder. BinderTypeModelBinder actúa como una fábrica de enlazadores de modelos y proporciona la inserción de dependencias. AuthorEntityBinder requiere que la inserción de dependencias tenga acceso a EF Core. Use BinderTypeModelBinder si su enlazador de modelos necesita servicios de inserción de dependencias.

Para usar un proveedor de enlazador de modelos personalizado, agréguelo en ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AuthorContext>(options => options.UseInMemoryDatabase("Authors"));

    services.AddControllers(options =>
    {
        options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
    });
}

Al evaluar enlazadores de modelos, la colección de proveedores se examina en orden. Se usa el primer proveedor que devuelve un enlazador que coincide con el modelo de entrada. Agregar a su proveedor al final de la colección, puede provocar que se llame a un enlazador de modelos integrado antes que al suyo. En este ejemplo, el proveedor personalizado se agrega al principio de la colección para asegurar que se use siempre en los argumentos de acción de Author.

Enlace de modelos polimórficos

El enlace a diferentes modelos de tipos derivados se conoce como enlace de modelos polimórficos. El enlace de modelo personalizado polimórfico es necesario cuando el valor de la solicitud debe enlazarse al tipo de modelo derivado específico. Enlace de modelos polimórficos:

  • No es habitual para una API de REST diseñada para interoperar con todos los lenguajes.
  • Hace que sea difícil razonar sobre los modelos enlazados.

Sin embargo, si una aplicación requiere un enlace de modelo polimórfico, una implementación podría tener un aspecto similar al código siguiente:

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

Recomendaciones y procedimientos recomendados

Enlazadores de modelos personalizados:

  • No debe intentar establecer códigos de estado ni devolver resultados (por ejemplo, 404 No encontrado). Si se produce un error en el enlace del modelo, un filtro de acción o una lógica dentro del propio método de acción debe controlar el error.
  • Son más útiles para eliminar código repetitivo y preocupaciones transversales de los métodos de acción.
  • Normalmente, no se debe usar para convertir una cadena en un tipo personalizado, normalmente TypeConverter es una opción mejor.

Por Steve Smith

El enlace de modelos permite que las acciones del controlador funcionen directamente con los tipos de modelo (pasados como argumentos de método), en lugar de las solicitudes HTTP. La asignación entre los datos de solicitudes entrantes y los modelos de aplicaciones se controla por medio de enlazadores de modelos. Los desarrolladores pueden ampliar la funcionalidad integrada de enlace de modelos implementando enlazadores de modelos personalizados (si bien, por lo general, no es necesario escribir un proveedor propio).

Vea o descargue el código de ejemplo (cómo descargarlo)

Limitaciones de los enlazadores de modelos predeterminados

Los enlazadores de modelos predeterminados admiten la mayoría de los tipos de datos comunes de .NET Core y deben satisfacer la mayoría de las necesidades de los desarrolladores. Esperan enlazar la entrada basada en texto desde la solicitud directamente a los tipos de modelo. Es posible que tenga que transformar la entrada antes de enlazarla. Por ejemplo, cuando tiene una clave que se puede usar para buscar datos del modelo. Puede usar un enlazador de modelos personalizado para obtener datos en función de la clave.

Revisión del enlace de modelos

El enlace de modelos usa definiciones específicas para los tipos en los que opera. Un tipo simple se convierte a partir de una cadena única de la entrada. Un tipo complejo se convierte a partir de varios valores de entrada. El marco determina la diferencia en función de la existencia de un TypeConverter. Le recomendamos crear un convertidor de tipos si tiene una asignación sencilla string ->SomeType que no requiere recursos externos.

Antes de crear su propio enlazador de modelos personalizado, merece la pena revisar cómo se implementan los enlazadores de modelos existentes. Tenga en cuenta el ByteArrayModelBinder que se puede usar para convertir cadenas codificadas en base64 en matrices de bytes. Las matrices de bytes a menudo se almacenan como archivos o campos BLOB de base de datos.

Trabajar con ByteArrayModelBinder

Las cadenas codificadas en Base64 se pueden usar para representar datos binarios. Por ejemplo, una imagen se puede codificar como una cadena. El ejemplo incluye una imagen como una cadena codificada en base64 en Base64String.txt.

ASP.NET Core MVC puede tomar una cadena codificada en base64 y usar un ByteArrayModelBinder para convertirlo en una matriz de bytes. ByteArrayModelBinderProvider mapea argumentos byte[] a 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;
}

Al crear su propio enlazador de modelos personalizado, puede implementar su propio IModelBinderProvider tipo o usar el ModelBinderAttribute.

En el ejemplo siguiente se muestra cómo usar ByteArrayModelBinder para convertir una cadena codificada en base64 en y byte[] guardar el resultado en un archivo:

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

Puede publicar una cadena codificada en base64 en el método de API anterior mediante una herramienta como curl.

Siempre que el vinculador pueda enlazar datos de solicitud a propiedades o argumentos correctamente nombrados, la vinculación del modelo se llevará a cabo con éxito. En el ejemplo siguiente se muestra cómo usar ByteArrayModelBinder con un modelo de vista:

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

Ejemplo de enlazador de modelos personalizado

En esta sección implementaremos un enlazador de modelos personalizado que:

  • Convierte los datos de solicitud entrantes en argumentos de clave fuertemente tipificados.
  • Usa Entity Framework Core para capturar la entidad asociada.
  • Pasa la entidad asociada como argumento al método de operación.

En el ejemplo siguiente se usa el ModelBinder atributo en el 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; }
    }
}

En el código anterior, el ModelBinder atributo especifica el tipo de IModelBinder que se debe usar para enlazar Author parámetros de acción.

La siguiente AuthorEntityBinder clase enlaza un Author parámetro recuperando la entidad desde un origen de datos utilizando Entity Framework Core y un 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

La clase anterior AuthorEntityBinder está pensada para ilustrar un enlazador de modelos personalizado. La clase no está pensada para ilustrar los procedimientos recomendados para un escenario de búsqueda. Para la búsqueda, enlace el valor authorId y consulte la base de datos en un método de acción. Este enfoque separa los errores de enlace de modelos de los casos de NotFound.

En el código siguiente se muestra cómo usar el AuthorEntityBinder en un método de acción.

[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
    if (author == null)
    {
        return NotFound();
    }
    
    return Ok(author);
}

El ModelBinder atributo se puede usar para aplicar a AuthorEntityBinder los parámetros que no usan convenciones predeterminadas:

[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
    if (author == null)
    {
        return NotFound();
    }

    return Ok(author);
}

En este ejemplo, dado que el nombre del argumento no es el predeterminado authorId, se especifica en el parámetro mediante el ModelBinder atributo . Tanto el controlador como el método de acción se simplifican, en contraste con tener que buscar la entidad en el método de acción. La lógica para capturar el autor a través de Entity Framework Core se traslada al enlazador de modelos. Esto puede ser una simplificación considerable cuando tiene varios métodos que se enlazan al Author modelo.

Puede aplicar el ModelBinder atributo a propiedades de modelo individuales (como en un modelo de vista) o a parámetros de método de acción para especificar un determinado enlazador de modelos o un nombre de modelo solo para ese tipo o acción.

Implementar un ModelBinderProvider

En lugar de aplicar un atributo, puede implementar IModelBinderProvider. Así es como se implementan los enlazadores de marcos integrados. Al especificar el tipo en el que opera el enlazador, se especifica el tipo de argumento que genera, no la entrada que acepta el enlazador. El siguiente proveedor de enlazador funciona con AuthorEntityBinder. Cuando se agrega a la colección de proveedores de MVC, no es necesario usar el atributo ModelBinder en parámetros de tipo Author o 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;
        }
    }
}

Nota: El código anterior devuelve un BinderTypeModelBinder. BinderTypeModelBinder actúa como una fábrica de enlazadores de modelos y proporciona la inserción de dependencias. AuthorEntityBinder requiere que la inserción de dependencias tenga acceso a EF Core. Use BinderTypeModelBinder si su enlazador de modelos necesita servicios de inserción de dependencias.

Para usar un proveedor de enlazador de modelos personalizado, agréguelo en 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);
}

Al evaluar enlazadores de modelos, la colección de proveedores se examina en orden. Se usará el primer proveedor que devuelva un enlazador. Si su proveedor se agrega al final de la colección, puede ocurrir que se llame a un enlazador de modelos integrado antes que al suyo. En este ejemplo, el proveedor personalizado se agrega al principio de la colección para procurar que se use en los argumentos de acción de Author.

Enlace de modelos polimórficos

El enlace a diferentes modelos de tipos derivados se conoce como enlace de modelos polimórficos. El enlace de modelo personalizado polimórfico es necesario cuando el valor de la solicitud debe enlazarse al tipo de modelo derivado específico. Enlace de modelos polimórficos:

  • No es habitual para una API de REST diseñada para interoperar con todos los lenguajes.
  • Hace que sea difícil razonar sobre los modelos enlazados.

Sin embargo, si una aplicación requiere un enlace de modelo polimórfico, una implementación podría tener un aspecto similar al código siguiente:

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

Recomendaciones y procedimientos recomendados

Enlazadores de modelos personalizados:

  • No debe intentar establecer códigos de estado ni devolver resultados (por ejemplo, 404 No encontrado). Si se produce un error en el enlace del modelo, un filtro de acción o una lógica dentro del propio método de acción debe controlar el error.
  • Son más útiles para eliminar código repetitivo y preocupaciones transversales de los métodos de acción.
  • Normalmente, no se debe usar para convertir una cadena en un tipo personalizado, normalmente TypeConverter es una opción mejor.