Enlace de modelos en ASP.NET Core
Nota:
Esta no es la versión más reciente de este artículo. Para la versión actual, consulta la versión .NET 8 de este artículo.
Advertencia
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulta la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulta la versión .NET 8 de este artículo.
Importante
Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.
Para la versión actual, consulte la versión .NET 8 de este artículo.
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 lasRazor pages trabajan con datos que provienen 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 los controladores y Razor pages 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 denominadoid
. - 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 denominadodogsOnly
. - 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 de administración de Razor páginas 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; }
Tipos simples y complejos de enlace de modelos
El enlace de modelos usa definiciones específicas de los tipos con los que funciona. Un tipo simple se convierte a partir de una sola cadena mediante un método TypeConverter o TryParse
. mientras que un tipo complejo se convierte a partir de varios valores de entrada. El marco establece la diferencia basándose en la existencia de un TypeConverter
oTryParse
. Se recomienda crear un convertidor de tipos o usar TryParse
para una conversión de string
a SomeType
que no requiera recursos externos ni varias entradas.
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:
- Campos de formulario
- El cuerpo de la solicitud (para controladores que tienen el atributo [ApiController]).
- Datos de ruta
- Parámetros de cadena de consulta
- 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
oIEnumerable<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 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 le interesen datos de cookies o del 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
.
En la aplicación de ejemplo se incluye un proveedor de valores y un ejemplo de fábrica que obtiene valores de cookies. Registre generadores de proveedores de valores personalizados en Program.cs
:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
En el código siguiente, 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ámetroint 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 matricesbyte[]
, que se establecen ennull
.
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 página de Razor, se vuelve 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, 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. 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
Consulte Tipos simples y complejos de enlace de modelos para obtener una explicación de tipos simples y complejos.
Los tipos simples a los que el enlazador de modelos puede convertir las cadenas de origen incluyen los siguientes:
- Boolean
- Byte, SByte
- Char
- DateOnly
- DateTime
- DateTimeOffset
- Decimal
- Double
- Enum
- Guid
- Int16, Int32, Int64
- Single
- TimeOnly
- TimeSpan
- UInt16, UInt32, UInt64
- Uri
- Versión
Enlazar con IParsable<T>.TryParse
La API IParsable<TSelf>.TryParse
admite el enlace de valores de parámetros de acción del controlador:
public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);
La siguiente clase DateRange
implementa IParsable<TSelf>
para admitir el enlace de un intervalo de fechas:
public class DateRange : IParsable<DateRange>
{
public DateOnly? From { get; init; }
public DateOnly? To { get; init; }
public static DateRange Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse(string? value,
IFormatProvider? provider, out DateRange dateRange)
{
var segments = value?.Split(',', StringSplitOptions.RemoveEmptyEntries
| StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& DateOnly.TryParse(segments[0], provider, out var fromDate)
&& DateOnly.TryParse(segments[1], provider, out var toDate))
{
dateRange = new DateRange { From = fromDate, To = toDate };
return true;
}
dateRange = new DateRange { From = default, To = default };
return false;
}
}
El código anterior:
- Convierte una cadena que representa dos fechas en un objeto
DateRange
- El enlazador de modelos usa el método
IParsable<TSelf>.TryParse
para enlazarDateRange
.
La siguiente acción del controlador usa la clase DateRange
para enlazar un intervalo de fechas:
// GET /WeatherForecast/ByRange?range=7/24/2022,07/26/2022
public IActionResult ByRange([FromQuery] DateRange range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
La siguiente clase Locale
implementa IParsable<TSelf>
para admitir el enlace de CultureInfo
:
public class Locale : CultureInfo, IParsable<Locale>
{
public Locale(string culture) : base(culture)
{
}
public static Locale Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse([NotNullWhen(true)] string? value,
IFormatProvider? provider, out Locale locale)
{
if (value is null)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
try
{
locale = new Locale(value);
return true;
}
catch (CultureNotFoundException)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
}
}
La siguiente acción del controlador usa la clase Locale
para enlazar una cadena CultureInfo
:
// GET /en-GB/WeatherForecast
public IActionResult Index([FromRoute] Locale locale)
{
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View(weatherForecasts);
}
La siguiente acción del controlador usa la clase DateRange
y Locale
para enlazar un intervalo de fechas con CultureInfo
:
// GET /af-ZA/WeatherForecast/RangeByLocale?range=2022-07-24,2022-07-29
public IActionResult RangeByLocale([FromRoute] Locale locale, [FromQuery] string range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
if (!DateRange.TryParse(range, locale, out DateRange rangeResult))
{
ModelState.TryAddModelError(nameof(range),
$"Invalid date range: {range} for locale {locale.DisplayName}");
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
}
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= rangeResult.From
&& DateOnly.FromDateTime(wf.Date) <= rangeResult.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int) (wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
La aplicación de ejemplo de API en GitHub muestra el ejemplo anterior para un controlador de API.
Enlazar con TryParse
La API TryParse
admite el enlace de valores de parámetros de acción del controlador:
public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);
IParsable<T>.TryParse
es el enfoque recomendado para el enlace de parámetros porque, a diferencia de TryParse
, no depende de la reflexión.
La siguiente clase DateRangeTP
implementa TryParse
:
public class DateRangeTP
{
public DateOnly? From { get; }
public DateOnly? To { get; }
public DateRangeTP(string from, string to)
{
if (string.IsNullOrEmpty(from))
throw new ArgumentNullException(nameof(from));
if (string.IsNullOrEmpty(to))
throw new ArgumentNullException(nameof(to));
From = DateOnly.Parse(from);
To = DateOnly.Parse(to);
}
public static bool TryParse(string? value, out DateRangeTP? result)
{
var range = value?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (range?.Length != 2)
{
result = default;
return false;
}
result = new DateRangeTP(range[0], range[1]);
return true;
}
}
La siguiente acción del controlador usa la clase DateRangeTP
para enlazar un intervalo de fechas:
// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRangeTP range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
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.
En todas las propiedades del tipo complejo, el enlace de modelos busca entre los orígenes el patrón de nombre prefijo.nombreDePropiedad. Si no se encuentra nada, solo busca nombre_de_propiedad sin el prefijo. La decisión de usar el prefijo no se realiza 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 en100
.Name
se establece ennull
. El enlace de modelos esperaInstructor.Name
porqueInstructor.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 enviados. 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 defenderte 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 utilizado para enlazar la instancia o el tipo específicos. Por ejemplo:
[HttpPost]
public IActionResult OnPost(
[ModelBinder<MyInstructorModelBinder>] Instructor instructor)
El atributo [ModelBinder]
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]
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]
Se puede aplicar a una propiedad o a un tipo. Impide que el enlace de modelos establezca la propiedad de un modelo. Cuando se aplica a un tipo, el sistema de enlace de modelos excluye todas las propiedades que define el tipo. 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
oIndex
si está adyacente a un valor de colección. El enlace de modelos intenta usarindex
como índice para la colección, lo que podría dar lugar a un enlace incorrecto. Considere, por ejemplo, las siguientes acciones:public IActionResult Post(string index, List<Product> products)
En el código anterior, el parámetro de cadena de consulta
index
se enlaza al parámetro de métodoindex
y también se usa para enlazar la colección de productos. Cambiar el nombre del parámetroindex
o usar un atributo de enlace de modelos 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>
denominadoselectedCourses
: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 constructores
El enlace de modelos requiere que los tipos complejos tengan un constructor sin parámetros. Tanto en System.Text.Json
como en Newtonsoft.Json
los formateadores de entrada basados admiten la deserialización de clases que no tienen un constructor sin parámetros.
Los tipos de registro son un modo fantástico de representar más concisamente datos en la red. ASP.NET Core admite el enlace de modelos y la validación de tipos de registro con un único 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
<label>Name: <input asp-for="Name" /></label>
<br />
<label>Age: <input asp-for="Age" /></label>
Al validar los tipos de registro, el tiempo de ejecución busca metadatos de enlace y validación específicamente en parámetros en lugar de en propiedades.
El marco permite enlazar y validar tipos de registro:
public record Person([Required] string Name, [Range(0, 100)] int Age);
Para que funcione el anterior, el tipo debe:
- Ser un tipo de registro.
- Tener exactamente un constructor público.
- Contener parámetros que tienen una propiedad con el mismo nombre y tipo. Los nombres no deben diferir entre mayúsculas y minúsculas.
POCO sin constructores carentes de parámetros
Los POCO que no tienen constructores sin parámetros no se pueden enlazar.
El código siguiente da como resultado una excepción que indica 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 creados manualmente
Los tipos de registro con constructores creados manualmente que parecen los 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
En el caso de 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 derrotarlo:
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 parámetros en 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á enlazarse Name
de nuevo. Sin embargo, Age
se puede 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:
- Heredan de IValueProviderFactory.
- Copie el código de QueryStringValueProviderFactory o RouteValueValueProviderFactory
- Reemplace el valor de referencia cultural pasado al constructor de proveedor de valores con CultureInfo.CurrentCulture
- Reemplace el generador de proveedores de valor predeterminado en las opciones de MVC por el nuevo:
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
Las acciones pueden enlazar opcionalmente CancellationToken
como un parámetro. Esto enlaza RequestAborted que 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:
En
Program.cs
, llame a AddXmlSerializerFormatters o a AddXmlDataContractSerializerFormatters.builder.Services.AddControllers() .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
:
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 del enlace de modelos y los sistemas de validación se controla mediante 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 valor para obtener datos del cuerpo del formulario, la cadena de consulta y los datos de ruta. TryUpdateModelAsync
suele ser:
- Se usa con aplicaciones de Razor Pages y MVC con 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.
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 métodos siguientes:
- Hacer que el parámetro acepte valores NULL.
- Establezca un valor predeterminado para el parámetro.
Para los parámetros que aceptan valores NULL, asegúrese de que el parámetro no sea null
antes de acceder a él.
Recursos adicionales
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 lasRazor pages trabajan con datos que provienen 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 los controladores y Razor pages 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 denominadoid
. - 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 denominadodogsOnly
. - 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 de administración de Razor páginas 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; }
Tipos simples y complejos de enlace de modelos
El enlace de modelos usa definiciones específicas de los tipos con los que funciona. Un tipo simple se convierte a partir de una sola cadena mediante un método TypeConverter o TryParse
. mientras que un tipo complejo se convierte a partir de varios valores de entrada. El marco establece la diferencia basándose en la existencia de un TypeConverter
oTryParse
. Se recomienda crear un convertidor de tipos o usar TryParse
para una conversión de string
a SomeType
que no requiera recursos externos ni varias entradas.
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:
- Campos de formulario
- El cuerpo de la solicitud (para controladores que tienen el atributo [ApiController]).
- Datos de ruta
- Parámetros de cadena de consulta
- 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
oIEnumerable<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 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 le interesen datos de cookies o del 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
.
En la aplicación de ejemplo se incluye un proveedor de valores y un ejemplo de fábrica que obtiene valores de cookies. Registre generadores de proveedores de valores personalizados en Program.cs
:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
En el código siguiente, 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ámetroint 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 matricesbyte[]
, que se establecen ennull
.
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 página de Razor, se vuelve 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, 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. 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
Consulte Tipos simples y complejos de enlace de modelos para obtener una explicación de tipos simples y complejos.
Los tipos simples a los que el enlazador de modelos puede convertir las cadenas de origen incluyen los siguientes:
- Boolean
- Byte, SByte
- Char
- DateOnly
- DateTime
- DateTimeOffset
- Decimal
- Double
- Enum
- Guid
- Int16, Int32, Int64
- Single
- TimeOnly
- TimeSpan
- UInt16, UInt32, UInt64
- Uri
- Versión
Enlazar con IParsable<T>.TryParse
La API IParsable<TSelf>.TryParse
admite el enlace de valores de parámetros de acción del controlador:
public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);
La siguiente clase DateRange
implementa IParsable<TSelf>
para admitir el enlace de un intervalo de fechas:
public class DateRange : IParsable<DateRange>
{
public DateOnly? From { get; init; }
public DateOnly? To { get; init; }
public static DateRange Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse(string? value,
IFormatProvider? provider, out DateRange dateRange)
{
var segments = value?.Split(',', StringSplitOptions.RemoveEmptyEntries
| StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& DateOnly.TryParse(segments[0], provider, out var fromDate)
&& DateOnly.TryParse(segments[1], provider, out var toDate))
{
dateRange = new DateRange { From = fromDate, To = toDate };
return true;
}
dateRange = new DateRange { From = default, To = default };
return false;
}
}
El código anterior:
- Convierte una cadena que representa dos fechas en un objeto
DateRange
- El enlazador de modelos usa el método
IParsable<TSelf>.TryParse
para enlazarDateRange
.
La siguiente acción del controlador usa la clase DateRange
para enlazar un intervalo de fechas:
// GET /WeatherForecast/ByRange?range=7/24/2022,07/26/2022
public IActionResult ByRange([FromQuery] DateRange range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
La siguiente clase Locale
implementa IParsable<TSelf>
para admitir el enlace de CultureInfo
:
public class Locale : CultureInfo, IParsable<Locale>
{
public Locale(string culture) : base(culture)
{
}
public static Locale Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse([NotNullWhen(true)] string? value,
IFormatProvider? provider, out Locale locale)
{
if (value is null)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
try
{
locale = new Locale(value);
return true;
}
catch (CultureNotFoundException)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
}
}
La siguiente acción del controlador usa la clase Locale
para enlazar una cadena CultureInfo
:
// GET /en-GB/WeatherForecast
public IActionResult Index([FromRoute] Locale locale)
{
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View(weatherForecasts);
}
La siguiente acción del controlador usa la clase DateRange
y Locale
para enlazar un intervalo de fechas con CultureInfo
:
// GET /af-ZA/WeatherForecast/RangeByLocale?range=2022-07-24,2022-07-29
public IActionResult RangeByLocale([FromRoute] Locale locale, [FromQuery] string range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
if (!DateRange.TryParse(range, locale, out DateRange rangeResult))
{
ModelState.TryAddModelError(nameof(range),
$"Invalid date range: {range} for locale {locale.DisplayName}");
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
}
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= rangeResult.From
&& DateOnly.FromDateTime(wf.Date) <= rangeResult.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int) (wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
La aplicación de ejemplo de API en GitHub muestra el ejemplo anterior para un controlador de API.
Enlazar con TryParse
La API TryParse
admite el enlace de valores de parámetros de acción del controlador:
public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);
IParsable<T>.TryParse
es el enfoque recomendado para el enlace de parámetros porque, a diferencia de TryParse
, no depende de la reflexión.
La siguiente clase DateRangeTP
implementa TryParse
:
public class DateRangeTP
{
public DateOnly? From { get; }
public DateOnly? To { get; }
public DateRangeTP(string from, string to)
{
if (string.IsNullOrEmpty(from))
throw new ArgumentNullException(nameof(from));
if (string.IsNullOrEmpty(to))
throw new ArgumentNullException(nameof(to));
From = DateOnly.Parse(from);
To = DateOnly.Parse(to);
}
public static bool TryParse(string? value, out DateRangeTP? result)
{
var range = value?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (range?.Length != 2)
{
result = default;
return false;
}
result = new DateRangeTP(range[0], range[1]);
return true;
}
}
La siguiente acción del controlador usa la clase DateRangeTP
para enlazar un intervalo de fechas:
// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRangeTP range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
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.
En todas las propiedades del tipo complejo, el enlace de modelos busca entre los orígenes el patrón de nombre prefijo.nombreDePropiedad. Si no se encuentra nada, solo busca nombre_de_propiedad sin el prefijo. La decisión de usar el prefijo no se realiza 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 en100
.Name
se establece ennull
. El enlace de modelos esperaInstructor.Name
porqueInstructor.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 enviados. 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 defenderte 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 utilizado para enlazar la instancia o el tipo específicos. Por ejemplo:
[HttpPost]
public IActionResult OnPost(
[ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
El atributo [ModelBinder]
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]
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]
Se puede aplicar a una propiedad o a un tipo. Impide que el enlace de modelos establezca la propiedad de un modelo. Cuando se aplica a un tipo, el sistema de enlace de modelos excluye todas las propiedades que define el tipo. 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
oIndex
si está adyacente a un valor de colección. El enlace de modelos intenta usarindex
como índice para la colección, lo que podría dar lugar a un enlace incorrecto. Considere, por ejemplo, las siguientes acciones:public IActionResult Post(string index, List<Product> products)
En el código anterior, el parámetro de cadena de consulta
index
se enlaza al parámetro de métodoindex
y también se usa para enlazar la colección de productos. Cambiar el nombre del parámetroindex
o usar un atributo de enlace de modelos 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>
denominadoselectedCourses
: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 constructores
El enlace de modelos requiere que los tipos complejos tengan un constructor sin parámetros. Tanto en System.Text.Json
como en Newtonsoft.Json
los formateadores de entrada basados admiten la deserialización de clases que no tienen un constructor sin parámetros.
Los tipos de registro son un modo fantástico de representar más concisamente datos en la red. ASP.NET Core admite el enlace de modelos y la validación de tipos de registro con un único 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
<label>Name: <input asp-for="Name" /></label>
<br />
<label>Age: <input asp-for="Age" /></label>
Al validar los tipos de registro, el tiempo de ejecución busca metadatos de enlace y validación específicamente en parámetros en lugar de en propiedades.
El marco permite enlazar y validar tipos de registro:
public record Person([Required] string Name, [Range(0, 100)] int Age);
Para que funcione el anterior, el tipo debe:
- Ser un tipo de registro.
- Tener exactamente un constructor público.
- Contener parámetros que tienen una propiedad con el mismo nombre y tipo. Los nombres no deben diferir entre mayúsculas y minúsculas.
POCO sin constructores carentes de parámetros
Los POCO que no tienen constructores sin parámetros no se pueden enlazar.
El código siguiente da como resultado una excepción que indica 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 creados manualmente
Los tipos de registro con constructores creados manualmente que parecen los 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
En el caso de 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 derrotarlo:
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 parámetros en 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á enlazarse Name
de nuevo. Sin embargo, Age
se puede 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:
- Heredan de IValueProviderFactory.
- Copie el código de QueryStringValueProviderFactory o RouteValueValueProviderFactory
- Reemplace el valor de referencia cultural pasado al constructor de proveedor de valores con CultureInfo.CurrentCulture
- Reemplace el generador de proveedores de valor predeterminado en las opciones de MVC por el nuevo:
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
Las acciones pueden enlazar opcionalmente CancellationToken
como un parámetro. Esto enlaza RequestAborted que 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:
En
Program.cs
, llame a AddXmlSerializerFormatters o a AddXmlDataContractSerializerFormatters.builder.Services.AddControllers() .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
:
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 del enlace de modelos y los sistemas de validación se controla mediante 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 valor para obtener datos del cuerpo del formulario, la cadena de consulta y los datos de ruta. TryUpdateModelAsync
suele ser:
- Se usa con aplicaciones de Razor Pages y MVC con 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.
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 métodos siguientes:
- Hacer que el parámetro acepte valores NULL.
- Establezca un valor predeterminado para el parámetro.
Para los parámetros que aceptan valores NULL, asegúrese de que el parámetro no sea null
antes de acceder a él.
Recursos adicionales
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 lasRazor pages trabajan con datos que provienen 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 los controladores y Razor pages 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 denominadoid
. - 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 denominadodogsOnly
. - 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 de administración de Razor páginas 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:
- Campos de formulario
- El cuerpo de la solicitud (para controladores que tienen el atributo [ApiController]).
- Datos de ruta
- Parámetros de cadena de consulta
- 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
oIEnumerable<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 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 le interesen datos de cookies o del 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
.
En la aplicación de ejemplo se incluye un proveedor de valores y un ejemplo de fábrica que obtiene valores de cookies. Registre generadores de proveedores de valores personalizados en Program.cs
:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
En el código siguiente, 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ámetroint 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 matricesbyte[]
, que se establecen ennull
.
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 página de Razor, se vuelve 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, 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. 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:
- Boolean
- Byte, SByte
- Char
- DateTime
- DateTimeOffset
- Decimal
- Double
- Enum
- Guid
- Int16, Int32, Int64
- Single
- TimeSpan
- UInt16, UInt32, UInt64
- Uri
- Versión
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.
En todas las propiedades del tipo complejo, el enlace de modelos busca entre los orígenes el patrón de nombre prefijo.nombreDePropiedad. Si no se encuentra nada, solo busca nombre_de_propiedad sin el prefijo. La decisión de usar el prefijo no se realiza 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 en100
.Name
se establece ennull
. El enlace de modelos esperaInstructor.Name
porqueInstructor.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 enviados. 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 defenderte 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 utilizado para enlazar la instancia o el tipo específicos. Por ejemplo:
[HttpPost]
public IActionResult OnPost(
[ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
El atributo [ModelBinder]
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]
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]
Se puede aplicar a una propiedad o a un tipo. Impide que el enlace de modelos establezca la propiedad de un modelo. Cuando se aplica a un tipo, el sistema de enlace de modelos excluye todas las propiedades que define el tipo. 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
oIndex
si está adyacente a un valor de colección. El enlace de modelos intenta usarindex
como índice para la colección, lo que podría dar lugar a un enlace incorrecto. Considere, por ejemplo, las siguientes acciones:public IActionResult Post(string index, List<Product> products)
En el código anterior, el parámetro de cadena de consulta
index
se enlaza al parámetro de métodoindex
y también se usa para enlazar la colección de productos. Cambiar el nombre del parámetroindex
o usar un atributo de enlace de modelos 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>
denominadoselectedCourses
: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 constructores
El enlace de modelos requiere que los tipos complejos tengan un constructor sin parámetros. Tanto en System.Text.Json
como en Newtonsoft.Json
los formateadores de entrada basados admiten la deserialización de clases que no tienen un constructor sin parámetros.
Los tipos de registro son un modo fantástico de representar más concisamente datos en la red. ASP.NET Core admite el enlace de modelos y la validación de tipos de registro con un único 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
<label>Name: <input asp-for="Name" /></label>
<br />
<label>Age: <input asp-for="Age" /></label>
Al validar los tipos de registro, el tiempo de ejecución busca metadatos de enlace y validación específicamente en parámetros en lugar de en propiedades.
El marco permite enlazar y validar tipos de registro:
public record Person([Required] string Name, [Range(0, 100)] int Age);
Para que funcione el anterior, el tipo debe:
- Ser un tipo de registro.
- Tener exactamente un constructor público.
- Contener parámetros que tienen una propiedad con el mismo nombre y tipo. Los nombres no deben diferir entre mayúsculas y minúsculas.
POCO sin constructores carentes de parámetros
Los POCO que no tienen constructores sin parámetros no se pueden enlazar.
El código siguiente da como resultado una excepción que indica 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 creados manualmente
Los tipos de registro con constructores creados manualmente que parecen los 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
En el caso de 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 derrotarlo:
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 parámetros en 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á enlazarse Name
de nuevo. Sin embargo, Age
se puede 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:
- Heredan de IValueProviderFactory.
- Copie el código de QueryStringValueProviderFactory o RouteValueValueProviderFactory
- Reemplace el valor de referencia cultural pasado al constructor de proveedor de valores con CultureInfo.CurrentCulture
- Reemplace el generador de proveedores de valor predeterminado en las opciones de MVC por el nuevo:
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
Las acciones pueden enlazar opcionalmente CancellationToken
como un parámetro. Esto enlaza RequestAborted que 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:
En
Program.cs
, llame a AddXmlSerializerFormatters o a AddXmlDataContractSerializerFormatters.builder.Services.AddControllers() .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
:
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 del enlace de modelos y los sistemas de validación se controla mediante 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 valor para obtener datos del cuerpo del formulario, la cadena de consulta y los datos de ruta. TryUpdateModelAsync
suele ser:
- Se usa con aplicaciones de Razor Pages y MVC con 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.
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 métodos siguientes:
- Hacer que el parámetro acepte valores NULL.
- Establezca un valor predeterminado para el parámetro.
Para los parámetros que aceptan valores NULL, asegúrese de que el parámetro no sea null
antes de acceder a él.
Recursos adicionales
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 lasRazor pages trabajan con datos que provienen 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 los controladores y Razor pages 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 denominadoid
. - 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 denominadodogsOnly
. - 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 de administración de Razor páginas 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:
- Campos de formulario
- El cuerpo de la solicitud (para controladores que tienen el atributo [ApiController]).
- Datos de ruta
- Parámetros de cadena de consulta
- 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
oIEnumerable<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 le interesen datos de cookies o del 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
.
En la aplicación de ejemplo se incluye un proveedor de valores y un generador que obtiene valores de cookies. 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ámetroint 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 matricesbyte[]
, que se establecen ennull
.
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 página de Razor, se vuelve 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 incorrectos que en otros casos se enviarían a un formulario de 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:
- Boolean
- Byte, SByte
- Char
- DateTime
- DateTimeOffset
- Decimal
- Double
- Enum
- Guid
- Int16, Int32, Int64
- Single
- TimeSpan
- UInt16, UInt32, UInt64
- Uri
- Versión
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 enviados. 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 defenderte 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 utilizado para enlazar la instancia o el tipo específicos. Por ejemplo:
[HttpPost]
public IActionResult OnPost([ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
El atributo [ModelBinder]
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
oIndex
si está adyacente a un valor de colección. El enlace de modelos intenta usarindex
como índice para la colección, lo que podría dar lugar a un enlace incorrecto. Considere, por ejemplo, las siguientes acciones:public IActionResult Post(string index, List<Product> products)
En el código anterior, el parámetro de cadena de consulta
index
se enlaza al parámetro de métodoindex
y también se usa para enlazar la colección de productos. Cambiar el nombre del parámetroindex
o usar un atributo de enlace de modelos 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>
denominadoselectedCourses
: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 constructores
El enlace de modelos requiere que los tipos complejos tengan un constructor sin parámetros. Tanto en System.Text.Json
como en Newtonsoft.Json
los formateadores de entrada basados 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 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 único 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
<label>Name: <input asp-for="Name" /></label>
...
<label>Age: <input asp-for="Age" /></label>
Al validar los tipos de registro, el tiempo de ejecución busca metadatos de enlace y validación específicamente en parámetros en lugar de en propiedades.
El marco permite enlazar y validar tipos de registro:
public record Person([Required] string Name, [Range(0, 100)] int Age);
Para que funcione el anterior, el tipo debe:
- Ser un tipo de registro.
- Tener exactamente un constructor público.
- Contener parámetros que tienen una propiedad con el mismo nombre y tipo. Los nombres no deben diferir entre mayúsculas y minúsculas.
POCO sin constructores carentes de parámetros
Los POCO que no tienen constructores sin parámetros no se pueden enlazar.
El código siguiente da como resultado una excepción que indica 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 creados manualmente
Los tipos de registro con constructores creados manualmente que parecen los 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
En el caso de 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 derrotarlo:
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 parámetros en 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á enlazarse Name
de nuevo. Sin embargo, Age
se puede 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:
- Heredan de IValueProviderFactory.
- Copie el código de QueryStringValueProviderFactory o RouteValueValueProviderFactory
- Reemplace el valor de referencia cultural pasado al constructor de proveedor de valores con CultureInfo.CurrentCulture
- Reemplace el generador de proveedores de valor predeterminado en las opciones de MVC por el nuevo:
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
Las acciones pueden enlazar opcionalmente CancellationToken
como un parámetro. Esto enlaza RequestAborted que 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 del enlace de modelos y los sistemas de validación se controla mediante 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 valor para obtener datos del cuerpo del formulario, la cadena de consulta y los datos de ruta. TryUpdateModelAsync
suele ser:
- Se usa con aplicaciones de Razor Pages y MVC con 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.