Enlace de modelos en ASP.NET Core

En este artículo se explica qué es el enlace de modelos, cómo funciona y cómo personalizar su comportamiento.

Qué es el enlace de modelos

Los controladores y Razor las páginas funcionan con datos procedentes de solicitudes HTTP. Por ejemplo, los datos de ruta pueden proporcionar una clave de registro y los campos de formulario publicados pueden proporcionar valores para las propiedades del modelo. La escritura de código para recuperar cada uno de estos valores y convertirlos de cadenas a tipos de .NET sería tediosa y propensa a errores. El enlace de modelos automatiza este proceso. El sistema de enlace de modelos:

  • Recupera datos de diversos orígenes, como datos de ruta, campos de formulario y cadenas de consulta.
  • Proporciona los datos a controladores y páginas Razor en parámetros de método y propiedades públicas.
  • Convierte datos de cadena en tipos de .NET.
  • Actualiza las propiedades de tipos complejos.

Ejemplo

Imagine que tiene el siguiente método de acción:

[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)

Y que la aplicación recibe una solicitud con esta dirección URL:

https://contoso.com/api/pets/2?DogsOnly=true

El enlace de modelos realiza los pasos siguientes después de que el sistema de enrutamiento selecciona el método de acción:

  • Busca el primer parámetro de GetById, un entero denominado id.
  • Examina los orígenes disponibles en la solicitud HTTP y busca id = "2" en los datos de ruta.
  • Convierte la cadena "2" en el entero 2.
  • Busca el siguiente parámetro de GetById, un valor booleano denominado dogsOnly.
  • Examina los orígenes y busca "DogsOnly=true" en la cadena de consulta. La coincidencia de nombres no distingue mayúsculas de minúsculas.
  • Convierte la cadena "true" en un valor booleano true.

Después, el marco llama al método GetById y pasa 2 para el parámetro id y true para el parámetro dogsOnly.

En el ejemplo anterior, los destinos de enlace de modelos son parámetros de método que son tipos simples. Los destinos también pueden ser las propiedades de un tipo complejo. Después de que cada propiedad se haya enlazado correctamente, se produce la validación de modelos para esa propiedad. El registro de qué datos se han enlazado al modelo y los errores de enlace o validación se almacenan en ControllerBase.ModelState o PageModel.ModelState. Para averiguar si este proceso se ha realizado correctamente, la aplicación comprueba la marca ModelState.IsValid.

Targets

El enlace de modelos intenta encontrar valores para los tipos de destinos siguientes:

  • Parámetros del método de acción de controlador al que se enruta una solicitud.
  • Parámetros del método Razor de controlador Pages al que se enruta una solicitud.
  • Propiedades públicas de un controlador o una clase PageModel, si se especifican mediante atributos.

Atributo [BindProperty]

Se puede aplicar a una propiedad pública de un controlador o una clase PageModel para hacer que el enlace de modelos tenga esa propiedad como destino:

public class EditModel : PageModel
{
    [BindProperty]
    public Instructor? Instructor { get; set; }

    // ...
}

Atributo [BindProperties]

Se pueden aplicar a un controlador o una clase PageModel para indicar al enlace de modelos que seleccione como destino todas las propiedades públicas de la clase:

[BindProperties]
public class CreateModel : PageModel
{
    public Instructor? Instructor { get; set; }

    // ...
}

Enlace de modelos para solicitudes HTTP GET

De forma predeterminada, las propiedades no se enlazan para las solicitudes HTTP GET. Normalmente, todo lo que necesita para una solicitud GET es un parámetro de id. de registro. El id. de registro se usa para buscar el elemento en la base de datos. Por tanto, no es necesario enlazar una propiedad que contiene una instancia del modelo. En escenarios donde quiera propiedades enlazadas a datos de las solicitudes GET, establezca la propiedad SupportsGet en true:

[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }

Orígenes

De forma predeterminada, el enlace de modelos obtiene datos en forma de pares clave-valor de los siguientes orígenes de una solicitud HTTP:

  1. Campos de formulario
  2. El cuerpo de la solicitud (para controladores que tienen el atributo [ApiController]).
  3. Datos de ruta
  4. Parámetros de cadena de consulta
  5. Archivos cargados

Para cada parámetro o propiedad de destino, se examinan los orígenes en el orden indicado en la lista anterior. Hay algunas excepciones:

  • Los datos de ruta y los valores de cadena de consulta solo se usan para tipos simples.
  • Los archivos cargados solo se enlazan a tipos de destino que implementan IFormFile o IEnumerable<IFormFile>.

Si el origen predeterminado no es correcto, use uno de los atributos siguientes para especificar el origen:

  • [FromQuery]: obtiene valores de la cadena de consulta.
  • [FromRoute]: obtiene valores de los datos de ruta.
  • [FromForm]: obtiene valores de los campos de formulario publicados.
  • [FromBody]: obtiene valores del cuerpo de la solicitud.
  • [FromHeader]: obtiene valores de encabezados HTTP.

Estos atributos:

  • Se agregan a las propiedades del modelo individualmente y no a la clase de modelo, como en el ejemplo siguiente:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • Opcionalmente, acepte un valor de nombre de modelo en el constructor. Esta opción se proporciona en caso de que el nombre de la propiedad no coincida con el valor de la solicitud. Por ejemplo, es posible que el valor de la solicitud sea un encabezado con un guion en el nombre, como en el ejemplo siguiente:

    public void OnGet([FromHeader(Name = "Accept-Language")] string language)
    

Atributo [FromBody]

Aplique el atributo [FromBody] a un parámetro para rellenar sus propiedades desde el cuerpo de una solicitud HTTP. El runtime de ASP.NET Core delega la responsabilidad de leer el cuerpo al formateador de entrada. Los formateadores de entrada se explican más adelante en este artículo.

Cuando se aplica [FromBody] a un parámetro de tipo complejo, se omiten los atributos de origen de enlace aplicados a sus propiedades. Por ejemplo, la acción Create siguiente especifica que su parámetro pet se rellena a partir del cuerpo:

public ActionResult<Pet> Create([FromBody] Pet pet)

La clase Pet especifica que su propiedad Breed se rellena a partir de un parámetro de cadena de consulta:

public class Pet
{
    public string Name { get; set; } = null!;

    [FromQuery] // Attribute is ignored.
    public string Breed { get; set; } = null!;
}

En el ejemplo anterior:

  • El atributo [FromQuery] se ignora.
  • La propiedad Breed no se rellena desde un parámetro de cadena de consulta.

Los formateadores de entrada solo leen el cuerpo y no entienden los atributos de origen de enlace. Si se encuentra un valor adecuado en el cuerpo, ese valor se usa para rellenar la propiedad Breed.

No aplique [FromBody] a más de un parámetro por método de acción. Una vez que un formateador de entrada ha leído la secuencia de solicitudes, deja de estar disponible para una nueva lectura con el fin de enlazar otros parámetros [FromBody].

Orígenes adicionales

Los proveedores de valores proporcionan datos de origen al sistema de enlace de modelos. Puede escribir y registrar proveedores de valores personalizados que obtienen datos de otros orígenes para el enlace de modelos. Por ejemplo, es posible que desee datos de cookies o de estado de sesión. Para obtener datos desde un origen nuevo:

  • Cree una clase que implemente IValueProvider.
  • Cree una clase que implemente IValueProviderFactory.
  • Registre la clase de generador en Program.cs.

El ejemplo incluye un proveedor de valores yun ejemplo de generador que obtiene valores de cookie. Registre generadores de proveedores de valores personalizados en Program.cs:

builder.Services.AddControllers(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});

El código anterior coloca el proveedor de valores personalizado después de todos los proveedores de valores integrados. Para que sea el primero en la lista, llame a Insert(0, new CookieValueProviderFactory()) en lugar de a Add.

No hay origen para una propiedad de modelo

De forma predeterminada, si no se encuentra ningún valor para una propiedad de modelo no se crea un error de estado del modelo. La propiedad se establece en NULL o en un valor predeterminado:

  • Los tipos simples que aceptan valores NULL se establecen en null.
  • Los tipos de valor que no aceptan valores NULL se establecen en default(T). Por ejemplo, un parámetro int id se establece en 0.
  • Para los tipos complejos, el enlace de modelos crea una instancia mediante el constructor predeterminado, sin establecer propiedades.
  • Las matrices se establecen en Array.Empty<T>(), salvo las matrices byte[], que se establecen en null.

Si el estado del modelo se debe invalidar cuando no se encuentra nada en los campos de formulario para una propiedad de modelo, use el atributo [BindRequired].

Tenga en cuenta que este comportamiento de [BindRequired] se aplica al enlace de modelos desde datos de formulario publicados, no a los datos JSON o XML del cuerpo de una solicitud. Los datos del cuerpo de la solicitud se controlan mediante formateadores de entrada.

Errores de la conversión de tipos

Si se encuentra un origen pero no se puede convertir al tipo de destino, el estado del modelo se marca como no válido. El parámetro o la propiedad de destino se establece en NULL o en un valor predeterminado, como se ha indicado en la sección anterior.

En un controlador de API que tenga el atributo [ApiController], el estado de modelo no válido genera una respuesta HTTP 400 automática.

En una Razor página, vuelva a mostrar la página con un mensaje de error:

public IActionResult OnPost()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    // ...

    return RedirectToPage("./Index");
}

Cuando el código anterior vuelve a mostrar la página, la entrada no válida no se muestra en el campo de formulario. El motivo es que la propiedad de modelo se ha establecido en NULL o en un valor predeterminado. La entrada no válida sí aparece en un mensaje de error. Si desea volver a mostrar los datos no disponibles en el campo de formulario, considere la posibilidad de convertir la propiedad del modelo en una cadena y realizar la conversión de datos manualmente.

Se recomienda la misma estrategia si no quiere que los errores de conversión de tipo generen errores de estado de modelo. En ese caso, convierta la propiedad de modelo en una cadena.

Tipos simples

Los tipos simples a los que el enlazador de modelos puede convertir las cadenas de origen incluyen los siguientes:

Tipos complejos

Un tipo complejo debe tener un constructor público predeterminado y propiedades grabables públicas para enlazar. Cuando se produce el enlace de modelos, se crea una instancia de la clase con el constructor predeterminado público.

Para cada propiedad del tipo complejo, el enlace de modelos busca en los orígenes el patrón denombre prefix.property_name. Si no se encuentra nada, solo busca nombre_de_propiedad sin el prefijo. La decisión de usar la consulta no se toma por propiedad. Por ejemplo, con una consulta que contiene ?Instructor.Id=100&Name=foo, enlazada al método OnGet(Instructor instructor), el objeto resultante de tipo Instructor contiene:

  • Id se establece en 100.
  • Name se establece en null. El enlace de modelos espera Instructor.Name porque Instructor.Id se usó en el parámetro de consulta anterior.

Para el enlace a un parámetro, el prefijo es el nombre del parámetro. Para el enlace a una propiedad pública PageModel, el prefijo es el nombre de la propiedad pública. Algunos atributos tienen una propiedad Prefix que permite invalidar el uso predeterminado del nombre de parámetro o propiedad.

Por ejemplo, imagine que el tipo complejo es la clase Instructor siguiente:

public class Instructor
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Prefijo = nombre del parámetro

Si el modelo que se va a enlazar es un parámetro denominado instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

El enlace de modelos se inicia mediante el examen de los orígenes para la clave instructorToUpdate.ID. Si no se encuentra, busca ID sin un prefijo.

Prefijo = nombre de propiedad

Si el modelo que se va a enlazar es una propiedad denominada Instructor del controlador o la clase PageModel:

[BindProperty]
public Instructor Instructor { get; set; }

El enlace de modelos se inicia mediante el examen de los orígenes para la clave Instructor.ID. Si no se encuentra, busca ID sin un prefijo.

Prefijo personalizado

Si el modelo que se va a enlazar es un parámetro denominado instructorToUpdate y un atributo Bind, especifica Instructor como el prefijo:

public IActionResult OnPost(
    int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)

El enlace de modelos se inicia mediante el examen de los orígenes para la clave Instructor.ID. Si no se encuentra, busca ID sin un prefijo.

Atributos para destinos de tipo complejo

Existen varios atributos integrados para controlar el enlace de modelos de tipos complejos:

Advertencia

Estos atributos afectan al enlace de modelos cuando el origen de los valores son datos de formulario publicados. No afectan a los formateadores de entrada, que procesan los cuerpos de solicitud JSON y XML publicados. Los formateadores de entrada se explican más adelante en este artículo.

Atributo [Bind]

Se puede aplicar a una clase o un parámetro de método. Especifica qué propiedades de un modelo se deben incluir en el enlace de modelos. [Bind] no afecta a los formateadores de entrada.

En el ejemplo siguiente, solo se enlazan las propiedades especificadas del modelo Instructor cuando se llama a cualquier método de acción o controlador:

[Bind("LastName,FirstMidName,HireDate")]
public class Instructor

En el ejemplo siguiente, solo se enlazan las propiedades especificadas del modelo Instructor cuando se llama al método OnPost:

[HttpPost]
public IActionResult OnPost(
    [Bind("LastName,FirstMidName,HireDate")] Instructor instructor)

El atributo [Bind] se puede usar para protegerse de la publicación excesiva en escenarios de creación. No funciona bien en escenarios de edición porque las propiedades excluidas se establecen en NULL o en un valor predeterminado en lugar de mantenerse sin cambios. Para defenderse de la publicación excesiva, se recomiendan modelos de vista en lugar del atributo [Bind]. Para más información, vea Nota de seguridad sobre la publicación excesiva.

Atributo [ModelBinder]

ModelBinderAttribute se puede aplicar a tipos, propiedades o parámetros. Permite especificar el tipo de enlazador de modelos usado para enlazar la instancia o el tipo específicos. Por ejemplo:

[HttpPost]
public IActionResult OnPost(
    [ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)

El [ModelBinder] atributo también se puede usar para cambiar el nombre de una propiedad o parámetro cuando se enlaza al modelo:

public class Instructor
{
    [ModelBinder(Name = "instructor_id")]
    public string Id { get; set; }

    // ...
}

Atributo [BindRequired]

Solo se puede aplicar a propiedades del modelo, no a parámetros de método. Hace que el enlace de modelos agregue un error de estado de modelo si no se puede realizar el enlace para la propiedad de un modelo. Este es un ejemplo:

public class InstructorBindRequired
{
    // ...

    [BindRequired]
    public DateTime HireDate { get; set; }
}

Vea también la explicación del atributo [Required] en Validación de modelos.

Atributo [BindNever]

Solo se puede aplicar a propiedades del modelo, no a parámetros de método. Impide que el enlace de modelos establezca la propiedad de un modelo. Este es un ejemplo:

public class InstructorBindNever
{
    [BindNever]
    public int Id { get; set; }

    // ...
}

Colecciones

Para los destinos que son colecciones de tipos simples, el enlace de modelos busca coincidencias con nombre_de_parámetro o nombre_de_propiedad. Si no se encuentra ninguna coincidencia, busca uno de los formatos admitidos sin el prefijo. Por ejemplo:

  • Imagine que el parámetro que se va a enlazar es una matriz llamada selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Los datos de cadena de consulta o formulario pueden estar en uno de los formatos siguientes:

    selectedCourses=1050&selectedCourses=2000 
    
    selectedCourses[0]=1050&selectedCourses[1]=2000
    
    [0]=1050&[1]=2000
    
    selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
    
    [a]=1050&[b]=2000&index=a&index=b
    

Evite enlazar un parámetro o una propiedad denominada index o Index si es adyacente a un valor de colección. El enlace de modelos intenta usar index como índice de la colección, lo que podría dar lugar a un enlace incorrecto. Por ejemplo, considere la siguiente acción:

public IActionResult Post(string index, List<Product> products)

En el código anterior, el parámetro de indexindex cadena de consulta se enlaza al parámetro de método y también se usa para enlazar la colección de productos. El cambio de nombre index del parámetro o el uso de un atributo de enlace de modelo para configurar el enlace evita este problema:

public IActionResult Post(string productIndex, List<Product> products)
selectedCourses[]=1050&selectedCourses[]=2000
  • Para todos los formatos de ejemplo anteriores, el enlace de modelos pasa una matriz de dos elementos al parámetro selectedCourses:

    • selectedCourses[0]=1050
    • selectedCourses[1]=2000

    Los formatos de datos que usan números de subíndice (... [0] ... [1] ...) deben asegurarse de que se numeran de forma secuencial a partir de cero. Si hay algún hueco en la numeración de los subíndices, se omiten todos los elementos que aparecen después del hueco. Por ejemplo, si los subíndices son 0 y 2 en lugar de 0 y 1, se omite el segundo elemento.

Diccionarios

Para los destinos Dictionary, el enlace de modelos busca coincidencias con nombre_de_parámetro o nombre_de_propiedad. Si no se encuentra ninguna coincidencia, busca uno de los formatos admitidos sin el prefijo. Por ejemplo:

  • Imagine que el parámetro de destino es un elemento Dictionary<int, string> denominado selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Los datos de cadena de consulta o de formulario publicados pueden ser similares a uno de los ejemplos siguientes:

    selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
    
    [1050]=Chemistry&selectedCourses[2000]=Economics
    
    selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry&
    selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
    
    [0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
    
  • Para todos los formatos de ejemplo anteriores, el enlace de modelos pasa un diccionario de dos elementos al parámetro selectedCourses:

    • selectedCourses["1050"]="Chemistry"
    • selectedCourses["2000"]="Economics"

Tipos de registro y enlace de constructor

El enlace de modelos requiere que los tipos complejos tengan un constructor sin parámetros. Tanto System.Text.Json como los Newtonsoft.Json formateadores de entrada basados admiten la deserialización de clases que no tienen un constructor sin parámetros.

Los tipos de registro son una excelente manera de representar de forma concisa los datos a través de la red. ASP.NET Core admite el enlace de modelos y la validación de tipos de registro con un solo constructor:

public record Person(
    [Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);

public class PersonController
{
    public IActionResult Index() => View();

    [HttpPost]
    public IActionResult Index(Person person)
    {
        // ...
    }
}

Person/Index.cshtml:

@model Person

Name: <input asp-for="Name" />
<br />
Age: <input asp-for="Age" />

Al validar los tipos de registro, el tiempo de ejecución busca metadatos de enlace y validación específicamente en los parámetros en lugar de en las propiedades.

El marco permite enlazar y validar tipos de registro:

public record Person([Required] string Name, [Range(0, 100)] int Age);

Para que funcione lo anterior, el tipo debe:

  • Ser un tipo de registro.
  • Tener exactamente un constructor público.
  • Contienen parámetros que tienen una propiedad con el mismo nombre y tipo. Los nombres no deben diferir por mayúsculas y minúsculas.

POCO sin constructores sin parámetros

Los POCO que no tienen constructores sin parámetros no se pueden enlazar.

El código siguiente produce una excepción que dice que el tipo debe tener un constructor sin parámetros:

public class Person(string Name)

public record Person([Required] string Name, [Range(0, 100)] int Age)
{
    public Person(string Name) : this (Name, 0);
}

Tipos de registro con constructores de creación manual

Los tipos de registro con constructores de creación manual que parecen constructores principales funcionan

public record Person
{
    public Person([Required] string Name, [Range(0, 100)] int Age)
        => (this.Name, this.Age) = (Name, Age);

    public string Name { get; set; }
    public int Age { get; set; }
}

Tipos de registro, validación y metadatos de enlace

Para los tipos de registro, se usan metadatos de validación y enlace en parámetros. Se omiten los metadatos de las propiedades.

public record Person (string Name, int Age)
{
   [BindProperty(Name = "SomeName")] // This does not get used
   [Required] // This does not get used
   public string Name { get; init; }
}

Validación y metadatos

La validación usa metadatos en el parámetro , pero usa la propiedad para leer el valor. En el caso normal con constructores principales, los dos serían idénticos. Sin embargo, hay maneras de ganarla:

public record Person([Required] string Name)
{
    private readonly string _name;

    // The following property is never null.
    // However this object could have been constructed as "new Person(null)".
    public string Name { get; init => _name = value ?? string.Empty; }
}

TryUpdateModel no actualiza los parámetros de un tipo de registro

public record Person(string Name)
{
    public int Age { get; set; }
}

var person = new Person("initial-name");
TryUpdateModel(person, ...);

En este caso, MVC no intentará enlazar de nuevo Name . Sin embargo, Age se permite actualizar

Comportamiento de globalización del enlace de modelos datos de ruta y cadenas de consulta

El proveedor de valores de ruta de ASP.NET Core y el proveedor de valores de cadena de consulta:

  • Tratan los valores como referencia cultural de todos los idiomas.
  • Esperan que las direcciones URL sean independientes de la referencia cultural.

Por el contrario, los valores procedentes de datos de formulario se someten a una conversión que tiene en cuenta la referencia cultural. Esto es así por diseño, para que las direcciones URL se puedan compartir entre configuraciones regionales.

Para que el proveedor de valores de ruta de ASP.NET Core y el proveedor de valores de cadena de consulta se sometan a una conversión dependiente de la referencia cultural:

public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        _ = context ?? throw new ArgumentNullException(nameof(context));

        var query = context.ActionContext.HttpContext.Request.Query;
        if (query?.Count > 0)
        {
            context.ValueProviders.Add(
                new QueryStringValueProvider(
                    BindingSource.Query,
                    query,
                    CultureInfo.CurrentCulture));
        }

        return Task.CompletedTask;
    }
}
builder.Services.AddControllers(options =>
{
    var index = options.ValueProviderFactories.IndexOf(
        options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
            .Single());

    options.ValueProviderFactories[index] =
        new CultureQueryStringValueProviderFactory();
});

Tipos de datos especiales

Hay algunos tipos de datos especiales que el enlace de modelos puede controlar.

IFormFile e IFormFileCollection

Un archivo cargado incluido en la solicitud HTTP. También se admite IEnumerable<IFormFile> para varios archivos.

CancellationToken

Opcionalmente, las acciones pueden enlazar un CancellationToken como parámetro. Esto enlaza que RequestAborted indica cuándo se anula la conexión subyacente a la solicitud HTTP. Las acciones pueden usar este parámetro para cancelar las operaciones asincrónicas de ejecución larga que se ejecutan como parte de las acciones del controlador.

FormCollection

Se usa para recuperar todos los valores de los datos de formulario publicados.

Formateadores de entrada

Los datos del cuerpo de la solicitud pueden estar en XML, JSON u otro formato. Para analizar estos datos, el enlace de modelos usa un formateador de entrada configurado para controlar un tipo de contenido determinado. De forma predeterminada, en ASP.NET Core se incluyen formateadores de entrada basados en JSON para el control de los datos JSON. Puede agregar otros formateadores para otros tipos de contenido.

ASP.NET Core selecciona los formateadores de entrada en función del atributo Consumes. Si no hay ningún atributo, usa el encabezado Content-Type.

Para usar los formateadores de entrada XML integrados:

Personalización del enlace de modelos con formateadores de entrada

Un formateador de entrada asume toda la responsabilidad de leer datos del cuerpo de la solicitud. Para personalizar este proceso, configure las API que usa el formateador de entrada. En esta sección se describe cómo personalizar el formateador de entrada basado en System.Text.Json para entender un tipo personalizado denominado ObjectId.

Considere el modelo siguiente, que contiene una propiedad ObjectId denominada Id:

public class InstructorObjectId
{
    [Required]
    public ObjectId ObjectId { get; set; } = null!;
}

Para personalizar el proceso de enlace de modelos al usar System.Text.Json, cree una clase derivada de JsonConverter<T>:

internal class ObjectIdConverter : JsonConverter<ObjectId>
{
    public override ObjectId Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => new(JsonSerializer.Deserialize<int>(ref reader, options));

    public override void Write(
        Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
        => writer.WriteNumberValue(value.Id);
}

Para usar un convertidor personalizado, aplique el atributo JsonConverterAttribute al tipo. En el ejemplo siguiente, el tipo ObjectId se configura con ObjectIdConverter como su convertidor personalizado:

[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);

Para más información, consulte Procedimientos para escribir convertidores personalizados.

Exclusión de tipos especificados del enlace de modelos

El comportamiento de los sistemas de validación y enlace de modelos está controlado por ModelMetadata. Puede personalizar ModelMetadata mediante la adición de un proveedor de detalles a MvcOptions.ModelMetadataDetailsProviders. Los proveedores de detalles integrados están disponibles para deshabilitar el enlace de modelos o la validación para tipos especificados.

Para deshabilitar el enlace de modelos en todos los modelos de un tipo especificado, agregue una instancia de ExcludeBindingMetadataProvider en Program.cs. Por ejemplo, para deshabilitar el enlace de modelos en todos los modelos del tipo System.Version:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

Para deshabilitar la validación en propiedades de un tipo especificado, agregue una instancia de SuppressChildValidationMetadataProvider en Program.cs. Por ejemplo, para deshabilitar la validación en las propiedades de tipo System.Guid:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

Enlazadores de modelos personalizados

Puede ampliar el enlace de modelos si escribe un enlazador de modelos personalizado y usa el atributo [ModelBinder] para seleccionarlo para un destino concreto. Más información sobre el enlace de modelos personalizados.

Enlace de modelos manual

El enlace de modelos se puede invocar de forma manual mediante el método TryUpdateModelAsync. El método se define en las clases ControllerBase y PageModel. Las sobrecargas de método permiten especificar el prefijo y el proveedor de valores que se van a usar. El método devuelve false si se produce un error en el enlace de modelos. Este es un ejemplo:

if (await TryUpdateModelAsync(
    newInstructor,
    "Instructor",
    x => x.Name, x => x.HireDate!))
{
    _instructorStore.Add(newInstructor);
    return RedirectToPage("./Index");
}

return Page();

TryUpdateModelAsync usa proveedores de valores para obtener datos del cuerpo del formulario, la cadena de consulta y los datos de ruta. TryUpdateModelAsync suele ser:

  • Se usa con Razor aplicaciones de Pages y MVC que usan controladores y vistas para evitar la publicación en exceso.
  • No se usa con una API web a menos que se consuma a partir de datos de formulario, cadenas de consulta y datos de ruta. Los puntos de conexión de la API web que consumen JSON usan formateadores de entrada para deserializar el cuerpo de la solicitud en un objeto.

Para más información, consulte TryUpdateModelAsync.

Atributo [FromServices]

El nombre de este atributo sigue el patrón de los atributos de enlace de modelos que especifican un origen de datos. Pero no se trata de enlazar datos desde un proveedor de valores. Obtiene una instancia de un tipo desde el contenedor de inserción de dependencias. Su objetivo es proporcionar una alternativa a la inserción de constructores cuando se necesita un servicio solo si se llama a un método concreto.

Si una instancia del tipo no está registrada en el contenedor de inserción de dependencias, la aplicación produce una excepción al intentar enlazar el parámetro. Para que el parámetro sea opcional, use uno de los enfoques siguientes:

  • Haga que el parámetro acepta valores NULL.
  • Establezca un valor predeterminado para el parámetro .

En el caso de los parámetros que aceptan valores NULL, asegúrese de que el parámetro no esté antes null de acceder a él.

En este artículo se explica qué es el enlace de modelos, cómo funciona y cómo personalizar su comportamiento.

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

Qué es el enlace de modelos

Los controladores y Razor las páginas funcionan con datos procedentes de solicitudes HTTP. Por ejemplo, los datos de ruta pueden proporcionar una clave de registro y los campos de formulario publicados pueden proporcionar valores para las propiedades del modelo. La escritura de código para recuperar cada uno de estos valores y convertirlos de cadenas a tipos de .NET sería tediosa y propensa a errores. El enlace de modelos automatiza este proceso. El sistema de enlace de modelos:

  • Recupera datos de diversos orígenes, como datos de ruta, campos de formulario y cadenas de consulta.
  • Proporciona los datos a controladores y páginas Razor en parámetros de método y propiedades públicas.
  • Convierte datos de cadena en tipos de .NET.
  • Actualiza las propiedades de tipos complejos.

Ejemplo

Imagine que tiene el siguiente método de acción:

[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)

Y que la aplicación recibe una solicitud con esta dirección URL:

http://contoso.com/api/pets/2?DogsOnly=true

El enlace de modelos realiza los pasos siguientes después de que el sistema de enrutamiento selecciona el método de acción:

  • Busca el primer parámetro de GetById, un entero denominado id.
  • Examina los orígenes disponibles en la solicitud HTTP y busca id = "2" en los datos de ruta.
  • Convierte la cadena "2" en el entero 2.
  • Busca el siguiente parámetro de GetById, un valor booleano denominado dogsOnly.
  • Examina los orígenes y busca "DogsOnly=true" en la cadena de consulta. La coincidencia de nombres no distingue mayúsculas de minúsculas.
  • Convierte la cadena "true" en un valor booleano true.

Después, el marco llama al método GetById y pasa 2 para el parámetro id y true para el parámetro dogsOnly.

En el ejemplo anterior, los destinos de enlace de modelos son parámetros de método que son tipos simples. Los destinos también pueden ser las propiedades de un tipo complejo. Después de que cada propiedad se haya enlazado correctamente, se produce la validación de modelos para esa propiedad. El registro de qué datos se han enlazado al modelo y los errores de enlace o validación se almacenan en ControllerBase.ModelState o PageModel.ModelState. Para averiguar si este proceso se ha realizado correctamente, la aplicación comprueba la marca ModelState.IsValid.

Targets

El enlace de modelos intenta encontrar valores para los tipos de destinos siguientes:

  • Parámetros del método de acción de controlador al que se enruta una solicitud.
  • Parámetros del método Razor de controlador Pages al que se enruta una solicitud.
  • Propiedades públicas de un controlador o una clase PageModel, si se especifican mediante atributos.

Atributo [BindProperty]

Se puede aplicar a una propiedad pública de un controlador o una clase PageModel para hacer que el enlace de modelos tenga esa propiedad como destino:

public class EditModel : InstructorsPageModel
{
    [BindProperty]
    public Instructor Instructor { get; set; }

Atributo [BindProperties]

Disponible en ASP.NET 2.1 Core y versiones posteriores. Se pueden aplicar a un controlador o una clase PageModel para indicar al enlace de modelos que seleccione como destino todas las propiedades públicas de la clase:

[BindProperties(SupportsGet = true)]
public class CreateModel : InstructorsPageModel
{
    public Instructor Instructor { get; set; }

Enlace de modelos para solicitudes HTTP GET

De forma predeterminada, las propiedades no se enlazan para las solicitudes HTTP GET. Normalmente, todo lo que necesita para una solicitud GET es un parámetro de id. de registro. El id. de registro se usa para buscar el elemento en la base de datos. Por tanto, no es necesario enlazar una propiedad que contiene una instancia del modelo. En escenarios donde quiera propiedades enlazadas a datos de las solicitudes GET, establezca la propiedad SupportsGet en true:

[BindProperty(Name = "ai_user", SupportsGet = true)]
public string ApplicationInsightsCookie { get; set; }

Orígenes

De forma predeterminada, el enlace de modelos obtiene datos en forma de pares clave-valor de los siguientes orígenes de una solicitud HTTP:

  1. Campos de formulario
  2. El cuerpo de la solicitud (para controladores que tienen el atributo [ApiController]).
  3. Datos de ruta
  4. Parámetros de cadena de consulta
  5. Archivos cargados

Para cada parámetro o propiedad de destino, se examinan los orígenes en el orden indicado en la lista anterior. Hay algunas excepciones:

  • Los datos de ruta y los valores de cadena de consulta solo se usan para tipos simples.
  • Los archivos cargados solo se enlazan a tipos de destino que implementan IFormFile o IEnumerable<IFormFile>.

Si el origen predeterminado no es correcto, use uno de los atributos siguientes para especificar el origen:

  • [FromQuery]: obtiene valores de la cadena de consulta.
  • [FromRoute]: obtiene valores de los datos de ruta.
  • [FromForm]: obtiene valores de los campos de formulario publicados.
  • [FromBody]: obtiene valores del cuerpo de la solicitud.
  • [FromHeader]: obtiene valores de encabezados HTTP.

Estos atributos:

  • Se agregan de forma individual a las propiedades del modelo (no a la clase de modelo), como en el ejemplo siguiente:

    public class Instructor
    {
        public int ID { get; set; }
    
        [FromQuery(Name = "Note")]
        public string NoteFromQueryString { get; set; }
    
  • Opcionalmente, acepte un valor de nombre de modelo en el constructor. Esta opción se proporciona en caso de que el nombre de la propiedad no coincida con el valor de la solicitud. Por ejemplo, es posible que el valor de la solicitud sea un encabezado con un guion en el nombre, como en el ejemplo siguiente:

    public void OnGet([FromHeader(Name = "Accept-Language")] string language)
    

Atributo [FromBody]

Aplique el atributo [FromBody] a un parámetro para rellenar sus propiedades desde el cuerpo de una solicitud HTTP. El runtime de ASP.NET Core delega la responsabilidad de leer el cuerpo al formateador de entrada. Los formateadores de entrada se explican más adelante en este artículo.

Cuando se aplica [FromBody] a un parámetro de tipo complejo, se omiten los atributos de origen de enlace aplicados a sus propiedades. Por ejemplo, la acción Create siguiente especifica que su parámetro pet se rellena a partir del cuerpo:

public ActionResult<Pet> Create([FromBody] Pet pet)

La clase Pet especifica que su propiedad Breed se rellena a partir de un parámetro de cadena de consulta:

public class Pet
{
    public string Name { get; set; }

    [FromQuery] // Attribute is ignored.
    public string Breed { get; set; }
}

En el ejemplo anterior:

  • El atributo [FromQuery] se ignora.
  • La propiedad Breed no se rellena desde un parámetro de cadena de consulta.

Los formateadores de entrada solo leen el cuerpo y no entienden los atributos de origen de enlace. Si se encuentra un valor adecuado en el cuerpo, ese valor se usa para rellenar la propiedad Breed.

No aplique [FromBody] a más de un parámetro por método de acción. Una vez que un formateador de entrada ha leído la secuencia de solicitudes, deja de estar disponible para una nueva lectura con el fin de enlazar otros parámetros [FromBody].

Orígenes adicionales

Los proveedores de valores proporcionan datos de origen al sistema de enlace de modelos. Puede escribir y registrar proveedores de valores personalizados que obtienen datos de otros orígenes para el enlace de modelos. Por ejemplo, es posible que desee datos de cookies o de estado de sesión. Para obtener datos desde un origen nuevo:

  • Cree una clase que implemente IValueProvider.
  • Cree una clase que implemente IValueProviderFactory.
  • Registre la clase de generador en Startup.ConfigureServices.

La aplicación de ejemplo incluye un proveedor de valores yun ejemplo de generador que obtiene valores de cookie. Este es el código de registro de Startup.ConfigureServices:

services.AddRazorPages()
    .AddMvcOptions(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
    options.ModelMetadataDetailsProviders.Add(
        new ExcludeBindingMetadataProvider(typeof(System.Version)));
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();

En el código mostrado, el proveedor de valor personalizado se coloca después de todos los proveedores de valor integrados. Para que sea el primero en la lista, llame a Insert(0, new CookieValueProviderFactory()) en lugar de a Add.

No hay origen para una propiedad de modelo

De forma predeterminada, si no se encuentra ningún valor para una propiedad de modelo no se crea un error de estado del modelo. La propiedad se establece en NULL o en un valor predeterminado:

  • Los tipos simples que aceptan valores NULL se establecen en null.
  • Los tipos de valor que no aceptan valores NULL se establecen en default(T). Por ejemplo, un parámetro int id se establece en 0.
  • Para los tipos complejos, el enlace de modelos crea una instancia mediante el constructor predeterminado, sin establecer propiedades.
  • Las matrices se establecen en Array.Empty<T>(), salvo las matrices byte[], que se establecen en null.

Si el estado del modelo se debe invalidar cuando no se encuentra nada en los campos de formulario para una propiedad de modelo, use el atributo [BindRequired].

Tenga en cuenta que este comportamiento de [BindRequired] se aplica al enlace de modelos desde datos de formulario publicados, no a los datos JSON o XML del cuerpo de una solicitud. Los datos del cuerpo de la solicitud se controlan mediante formateadores de entrada.

Errores de la conversión de tipos

Si se encuentra un origen pero no se puede convertir al tipo de destino, el estado del modelo se marca como no válido. El parámetro o la propiedad de destino se establece en NULL o en un valor predeterminado, como se ha indicado en la sección anterior.

En un controlador de API que tenga el atributo [ApiController], el estado de modelo no válido genera una respuesta HTTP 400 automática.

En una Razor página, vuelva a mostrar la página con un mensaje de error:

public IActionResult OnPost()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _instructorsInMemoryStore.Add(Instructor);
    return RedirectToPage("./Index");
}

La validación del lado cliente detecta la mayoría de los datos negativos que, de lo contrario, se enviarían a un formulario Razor pages. Esta validación dificulta que se pueda desencadenar el código resaltado anterior. En la aplicación de ejemplo se incluye un botón Submit with Invalid Date (Enviar con fecha no válida) que agrega datos incorrectos al campo Hire Date (Fecha de contratación) y envía el formulario. Este botón muestra cómo funciona el código para volver a mostrar la página cuando se producen errores de conversión de datos.

Cuando el código anterior vuelve a mostrar la página, no se muestra la entrada no válida en el campo de formulario. El motivo es que la propiedad de modelo se ha establecido en NULL o en un valor predeterminado. La entrada no válida sí aparece en un mensaje de error. Pero si quiere volver a mostrar los datos incorrectos en el campo de formulario, considere la posibilidad de convertir la propiedad de modelo en una cadena y realizar la conversión de datos de forma manual.

Se recomienda la misma estrategia si no quiere que los errores de conversión de tipo generen errores de estado de modelo. En ese caso, convierta la propiedad de modelo en una cadena.

Tipos simples

Los tipos simples a los que el enlazador de modelos puede convertir las cadenas de origen incluyen los siguientes:

Tipos complejos

Un tipo complejo debe tener un constructor público predeterminado y propiedades grabables públicas para enlazar. Cuando se produce el enlace de modelos, se crea una instancia de la clase con el constructor predeterminado público.

Para cada propiedad del tipo complejo, el enlace de modelos busca entre los orígenes el patrón de nombre prefijo.nombre_de_propiedad. Si no se encuentra nada, solo busca nombre_de_propiedad sin el prefijo.

Para el enlace a un parámetro, el prefijo es el nombre del parámetro. Para el enlace a una propiedad pública PageModel, el prefijo es el nombre de la propiedad pública. Algunos atributos tienen una propiedad Prefix que permite invalidar el uso predeterminado del nombre de parámetro o propiedad.

Por ejemplo, imagine que el tipo complejo es la clase Instructor siguiente:

public class Instructor
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Prefijo = nombre del parámetro

Si el modelo que se va a enlazar es un parámetro denominado instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

El enlace de modelos se inicia mediante el examen de los orígenes para la clave instructorToUpdate.ID. Si no se encuentra, busca ID sin un prefijo.

Prefijo = nombre de propiedad

Si el modelo que se va a enlazar es una propiedad denominada Instructor del controlador o la clase PageModel:

[BindProperty]
public Instructor Instructor { get; set; }

El enlace de modelos se inicia mediante el examen de los orígenes para la clave Instructor.ID. Si no se encuentra, busca ID sin un prefijo.

Prefijo personalizado

Si el modelo que se va a enlazar es un parámetro denominado instructorToUpdate y un atributo Bind, especifica Instructor como el prefijo:

public IActionResult OnPost(
    int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)

El enlace de modelos se inicia mediante el examen de los orígenes para la clave Instructor.ID. Si no se encuentra, busca ID sin un prefijo.

Atributos para destinos de tipo complejo

Existen varios atributos integrados para controlar el enlace de modelos de tipos complejos:

  • [Bind]
  • [BindRequired]
  • [BindNever]

Advertencia

Estos atributos afectan al enlace de modelos cuando el origen de los valores son datos de formulario publicados. No afectan a los formateadores de entrada, que procesan los cuerpos de solicitud JSON y XML publicados. Los formateadores de entrada se explican más adelante en este artículo.

Atributo [Bind]

Se puede aplicar a una clase o un parámetro de método. Especifica qué propiedades de un modelo se deben incluir en el enlace de modelos. [Bind] no afecta a los formateadores de entrada.

En el ejemplo siguiente, solo se enlazan las propiedades especificadas del modelo Instructor cuando se llama a cualquier método de acción o controlador:

[Bind("LastName,FirstMidName,HireDate")]
public class Instructor

En el ejemplo siguiente, solo se enlazan las propiedades especificadas del modelo Instructor cuando se llama al método OnPost:

[HttpPost]
public IActionResult OnPost([Bind("LastName,FirstMidName,HireDate")] Instructor instructor)

El atributo [Bind] se puede usar para protegerse de la publicación excesiva en escenarios de creación. No funciona bien en escenarios de edición porque las propiedades excluidas se establecen en NULL o en un valor predeterminado en lugar de mantenerse sin cambios. Para defenderse de la publicación excesiva, se recomiendan modelos de vista en lugar del atributo [Bind]. Para más información, vea Nota de seguridad sobre la publicación excesiva.

Atributo [ModelBinder]

ModelBinderAttribute se puede aplicar a tipos, propiedades o parámetros. Permite especificar el tipo de enlazador de modelos usado para enlazar la instancia o el tipo específicos. Por ejemplo:

[HttpPost]
public IActionResult OnPost([ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)

El [ModelBinder] atributo también se puede usar para cambiar el nombre de una propiedad o parámetro cuando se enlaza al modelo:

public class Instructor
{
    [ModelBinder(Name = "instructor_id")]
    public string Id { get; set; }

    public string Name { get; set; }
}

Atributo [BindRequired]

Solo se puede aplicar a propiedades del modelo, no a parámetros de método. Hace que el enlace de modelos agregue un error de estado de modelo si no se puede realizar el enlace para la propiedad de un modelo. Este es un ejemplo:

public class InstructorWithCollection
{
    public int ID { get; set; }

    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    [Display(Name = "Hire Date")]
    [BindRequired]
    public DateTime HireDate { get; set; }

Vea también la explicación del atributo [Required] en Validación de modelos.

Atributo [BindNever]

Solo se puede aplicar a propiedades del modelo, no a parámetros de método. Impide que el enlace de modelos establezca la propiedad de un modelo. Este es un ejemplo:

public class InstructorWithDictionary
{
    [BindNever]
    public int ID { get; set; }

Colecciones

Para los destinos que son colecciones de tipos simples, el enlace de modelos busca coincidencias con nombre_de_parámetro o nombre_de_propiedad. Si no se encuentra ninguna coincidencia, busca uno de los formatos admitidos sin el prefijo. Por ejemplo:

  • Imagine que el parámetro que se va a enlazar es una matriz llamada selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Los datos de cadena de consulta o formulario pueden estar en uno de los formatos siguientes:

    selectedCourses=1050&selectedCourses=2000 
    
    selectedCourses[0]=1050&selectedCourses[1]=2000
    
    [0]=1050&[1]=2000
    
    selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
    
    [a]=1050&[b]=2000&index=a&index=b
    

    Evite enlazar un parámetro o una propiedad denominada index o Index si es adyacente a un valor de colección. El enlace de modelos intenta usar index como índice de la colección, lo que podría dar lugar a un enlace incorrecto. Por ejemplo, considere la siguiente acción:

    public IActionResult Post(string index, List<Product> products)
    

    En el código anterior, el parámetro de indexindex cadena de consulta se enlaza al parámetro de método y también se usa para enlazar la colección de productos. Cambiar el nombre del index parámetro o usar un atributo de enlace de modelo para configurar el enlace evita este problema:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • El formato siguiente solo se admite en datos de formulario:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Para todos los formatos de ejemplo anteriores, el enlace de modelos pasa una matriz de dos elementos al parámetro selectedCourses:

    • selectedCourses[0]=1050
    • selectedCourses[1]=2000

    Los formatos de datos que usan números de subíndice (... [0] ... [1] ...) deben asegurarse de que se numeran de forma secuencial a partir de cero. Si hay algún hueco en la numeración de los subíndices, se omiten todos los elementos que aparecen después del hueco. Por ejemplo, si los subíndices son 0 y 2 en lugar de 0 y 1, se omite el segundo elemento.

Diccionarios

Para los destinos Dictionary, el enlace de modelos busca coincidencias con nombre_de_parámetro o nombre_de_propiedad. Si no se encuentra ninguna coincidencia, busca uno de los formatos admitidos sin el prefijo. Por ejemplo:

  • Imagine que el parámetro de destino es un elemento Dictionary<int, string> denominado selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Los datos de cadena de consulta o de formulario publicados pueden ser similares a uno de los ejemplos siguientes:

    selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
    
    [1050]=Chemistry&selectedCourses[2000]=Economics
    
    selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry&
    selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
    
    [0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
    
  • Para todos los formatos de ejemplo anteriores, el enlace de modelos pasa un diccionario de dos elementos al parámetro selectedCourses:

    • selectedCourses["1050"]="Chemistry"
    • selectedCourses["2000"]="Economics"

Tipos de registro y enlace de constructor

El enlace de modelos requiere que los tipos complejos tengan un constructor sin parámetros. Los System.Text.Json formateadores Newtonsoft.Json de entrada basados en y admiten la deserialización de clases que no tienen un constructor sin parámetros.

C# 9 presenta tipos de registro, que son una excelente manera de representar de forma concisa los datos a través de la red. ASP.NET Core agrega compatibilidad con el enlace de modelos y la validación de tipos de registro con un solo constructor:

public record Person([Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);

public class PersonController
{
   public IActionResult Index() => View();

   [HttpPost]
   public IActionResult Index(Person person)
   {
       ...
   }
}

Person/Index.cshtml:

@model Person

Name: <input asp-for="Name" />
...
Age: <input asp-for="Age" />

Al validar los tipos de registro, el tiempo de ejecución busca metadatos de enlace y validación específicamente en los parámetros en lugar de en las propiedades.

El marco permite enlazar y validar tipos de registro:

public record Person([Required] string Name, [Range(0, 100)] int Age);

Para que funcione lo anterior, el tipo debe:

  • Ser un tipo de registro.
  • Tener exactamente un constructor público.
  • Contienen parámetros que tienen una propiedad con el mismo nombre y tipo. Los nombres no deben diferir por mayúsculas y minúsculas.

POCOs sin constructores sin parámetros

Los POCO que no tienen constructores sin parámetros no se pueden enlazar.

El código siguiente produce una excepción que dice que el tipo debe tener un constructor sin parámetros:

public class Person(string Name)

public record Person([Required] string Name, [Range(0, 100)] int Age)
{
   public Person(string Name) : this (Name, 0);
}

Tipos de registro con constructores de creación manual

Los tipos de registro con constructores de creación manual que parecen constructores principales funcionan

public record Person
{
   public Person([Required] string Name, [Range(0, 100)] int Age) => (this.Name, this.Age) = (Name, Age);

   public string Name { get; set; }
   public int Age { get; set; }
}

Tipos de registro, validación y metadatos de enlace

Para los tipos de registro, se usa la validación y los metadatos de enlace en los parámetros. Se omiten los metadatos de las propiedades.

public record Person (string Name, int Age)
{
   [BindProperty(Name = "SomeName")] // This does not get used
   [Required] // This does not get used
   public string Name { get; init; }
}

Validación y metadatos

La validación usa metadatos en el parámetro , pero usa la propiedad para leer el valor. En el caso normal con constructores principales, los dos serían idénticos. Sin embargo, hay maneras de ganarlo:

public record Person([Required] string Name)
{
   private readonly string _name;
   public Name { get; init => _name = value ?? string.Empty; } // Now this property is never null. However this object could have been constructed as `new Person(null);`
}

TryUpdateModel no actualiza los parámetros de un tipo de registro

public record Person(string Name)
{
   public int Age { get; set; }
}

var person = new Person("initial-name");
TryUpdateModel(person, ...);

En este caso, MVC no intentará enlazar de nuevo Name . Sin embargo, Age se permite actualizar

Comportamiento de globalización del enlace de modelos datos de ruta y cadenas de consulta

El proveedor de valores de ruta de ASP.NET Core y el proveedor de valores de cadena de consulta:

  • Tratan los valores como referencia cultural de todos los idiomas.
  • Esperan que las direcciones URL sean independientes de la referencia cultural.

Por el contrario, los valores procedentes de datos de formulario se someten a una conversión que tiene en cuenta la referencia cultural. Esto es así por diseño, para que las direcciones URL se puedan compartir entre configuraciones regionales.

Para que el proveedor de valores de ruta de ASP.NET Core y el proveedor de valores de cadena de consulta se sometan a una conversión dependiente de la referencia cultural:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
    {
        var index = options.ValueProviderFactories.IndexOf(
            options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>().Single());
        options.ValueProviderFactories[index] = new CulturedQueryStringValueProviderFactory();
    });
}
public class CulturedQueryStringValueProviderFactory : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        var query = context.ActionContext.HttpContext.Request.Query;
        if (query != null && query.Count > 0)
        {
            var valueProvider = new QueryStringValueProvider(
                BindingSource.Query,
                query,
                CultureInfo.CurrentCulture);

            context.ValueProviders.Add(valueProvider);
        }

        return Task.CompletedTask;
    }
}

Tipos de datos especiales

Hay algunos tipos de datos especiales que el enlace de modelos puede controlar.

IFormFile e IFormFileCollection

Un archivo cargado incluido en la solicitud HTTP. También se admite IEnumerable<IFormFile> para varios archivos.

CancellationToken

Opcionalmente, las acciones pueden enlazar un CancellationToken como parámetro. Esto enlaza que RequestAborted indica cuándo se anula la conexión subyacente a la solicitud HTTP. Las acciones pueden usar este parámetro para cancelar las operaciones asincrónicas de larga duración que se ejecutan como parte de las acciones del controlador.

FormCollection

Se usa para recuperar todos los valores de los datos de formulario publicados.

Formateadores de entrada

Los datos del cuerpo de la solicitud pueden estar en XML, JSON u otro formato. Para analizar estos datos, el enlace de modelos usa un formateador de entrada configurado para controlar un tipo de contenido determinado. De forma predeterminada, en ASP.NET Core se incluyen formateadores de entrada basados en JSON para el control de los datos JSON. Puede agregar otros formateadores para otros tipos de contenido.

ASP.NET Core selecciona los formateadores de entrada en función del atributo Consumes. Si no hay ningún atributo, usa el encabezado Content-Type.

Para usar los formateadores de entrada XML integrados:

  • Instale el paquete NuGet Microsoft.AspNetCore.Mvc.Formatters.Xml.

  • En Startup.ConfigureServices, llame a AddXmlSerializerFormatters o a AddXmlDataContractSerializerFormatters.

    services.AddRazorPages()
        .AddMvcOptions(options =>
    {
        options.ValueProviderFactories.Add(new CookieValueProviderFactory());
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(System.Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
    })
    .AddXmlSerializerFormatters();
    
  • Aplique el atributo Consumes a las clases de controlador o los métodos de acción que deben esperar XML en el cuerpo de la solicitud.

    [HttpPost]
    [Consumes("application/xml")]
    public ActionResult<Pet> Create(Pet pet)
    

    Para más información, vea Introducción de la serialización XML.

Personalización del enlace de modelos con formateadores de entrada

Un formateador de entrada asume toda la responsabilidad de leer datos del cuerpo de la solicitud. Para personalizar este proceso, configure las API que usa el formateador de entrada. En esta sección se describe cómo personalizar el formateador de entrada basado en System.Text.Json para entender un tipo personalizado denominado ObjectId.

Considere el modelo siguiente, que contiene una propiedad ObjectId denominada Id:

public class ModelWithObjectId
{
    public ObjectId Id { get; set; }
}

Para personalizar el proceso de enlace de modelos al usar System.Text.Json, cree una clase derivada de JsonConverter<T>:

internal class ObjectIdConverter : JsonConverter<ObjectId>
{
    public override ObjectId Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return new ObjectId(JsonSerializer.Deserialize<int>(ref reader, options));
    }

    public override void Write(
        Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
    {
        writer.WriteNumberValue(value.Id);
    }
}

Para usar un convertidor personalizado, aplique el atributo JsonConverterAttribute al tipo. En el ejemplo siguiente, el tipo ObjectId se configura con ObjectIdConverter como su convertidor personalizado:

[JsonConverter(typeof(ObjectIdConverter))]
public struct ObjectId
{
    public ObjectId(int id) =>
        Id = id;

    public int Id { get; }
}

Para más información, consulte Procedimientos para escribir convertidores personalizados.

Exclusión de tipos especificados del enlace de modelos

El comportamiento de los sistemas de validación y enlace de modelos está controlado por ModelMetadata. Puede personalizar ModelMetadata mediante la adición de un proveedor de detalles a MvcOptions.ModelMetadataDetailsProviders. Los proveedores de detalles integrados están disponibles para deshabilitar el enlace de modelos o la validación para tipos especificados.

Para deshabilitar el enlace de modelos en todos los modelos de un tipo especificado, agregue una instancia de ExcludeBindingMetadataProvider en Startup.ConfigureServices. Por ejemplo, para deshabilitar el enlace de modelos en todos los modelos del tipo System.Version:

services.AddRazorPages()
    .AddMvcOptions(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
    options.ModelMetadataDetailsProviders.Add(
        new ExcludeBindingMetadataProvider(typeof(System.Version)));
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();

Para deshabilitar la validación en propiedades de un tipo especificado, agregue una instancia de SuppressChildValidationMetadataProvider en Startup.ConfigureServices. Por ejemplo, para deshabilitar la validación en las propiedades de tipo System.Guid:

services.AddRazorPages()
    .AddMvcOptions(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
    options.ModelMetadataDetailsProviders.Add(
        new ExcludeBindingMetadataProvider(typeof(System.Version)));
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();

Enlazadores de modelos personalizados

Puede ampliar el enlace de modelos si escribe un enlazador de modelos personalizado y usa el atributo [ModelBinder] para seleccionarlo para un destino concreto. Más información sobre el enlace de modelos personalizados.

Enlace de modelos manual

El enlace de modelos se puede invocar de forma manual mediante el método TryUpdateModelAsync. El método se define en las clases ControllerBase y PageModel. Las sobrecargas de método permiten especificar el prefijo y el proveedor de valores que se van a usar. El método devuelve false si se produce un error en el enlace de modelos. Este es un ejemplo:

if (await TryUpdateModelAsync<InstructorWithCollection>(
    newInstructor,
    "Instructor",
    i => i.FirstMidName, i => i.LastName, i => i.HireDate))
{
    _instructorsInMemoryStore.Add(newInstructor);
    return RedirectToPage("./Index");
}
PopulateAssignedCourseData(newInstructor);
return Page();

TryUpdateModelAsync usa proveedores de valores para obtener datos del cuerpo del formulario, la cadena de consulta y los datos de ruta. TryUpdateModelAsync suele ser:

  • Se usa con Razor aplicaciones de Pages y MVC que usan controladores y vistas para evitar el exceso de publicación.
  • No se usa con una API web a menos que se consuma a partir de datos de formulario, cadenas de consulta y datos de ruta. Los puntos de conexión de la API web que consumen JSON usan formateadores de entrada para deserializar el cuerpo de la solicitud en un objeto.

Para más información, consulte TryUpdateModelAsync.

Atributo [FromServices]

El nombre de este atributo sigue el patrón de los atributos de enlace de modelos que especifican un origen de datos. Pero no se trata de enlazar datos desde un proveedor de valores. Obtiene una instancia de un tipo desde el contenedor de inserción de dependencias. Su objetivo es proporcionar una alternativa a la inserción de constructores cuando se necesita un servicio solo si se llama a un método concreto.

Recursos adicionales