Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Note
Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 10 de este artículo.
Warning
Esta versión de ASP.NET Core ya no se admite. Para más información, consulte la directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulte la versión de .NET 9 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 las páginas Razor 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.
Example
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. Una vez enlazada correctamente cada propiedad, la validación del modelo se produce para esa propiedad. El registro de los datos que están enlazados al modelo y los errores de enlace o validación se almacenan en ControllerBase.ModelState o PageModel.ModelState. Para averiguar si este proceso se realizó 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 TypeConverter o un TryParse método. 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 o TryParse. 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.
Sources
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
IFormFileoIEnumerable<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 los valores a partir 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
Breedno 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 datos de origen se proporcionan al sistema de enlace de modelos por proveedores de valores. 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.
El ejemplo incluye un proveedor de valores y un ejemplo de fábrica que obtiene valores de las 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 idse 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 desde los datos JSON o XML del cuerpo de una solicitud. Los datos del cuerpo de la solicitud son gestionados por los 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
- Version
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>.TryParsepara 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.
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, busca simplemente property_name 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:
- Establezca
Iden100. - Establezca
Nameennull. El enlace de modelos esperaInstructor.NameporqueInstructor.Idse usó en el parámetro de consulta anterior.
Note
Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta para una versión específica, use la lista desplegable Cambiar ramas o etiquetas . Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).
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:
Warning
Estos atributos afectan al enlace de modelos cuando el origen de los valores son datos de formulario publicados. No afectan a los formateadores de entrada, que procesan los cuerpos de solicitud JSON y XML publicados. Los formateadores de entrada se explican más adelante en este artículo.
Atributo [Bind]
Se puede aplicar a una clase o un parámetro de método. Especifica qué propiedades de un modelo se deben incluir en el enlace de modelos.
[Bind]
no afecta a los formateadores de entrada.
En el ejemplo siguiente, solo se enlazan las propiedades especificadas del modelo Instructor cuando se llama a cualquier método de acción o controlador:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
En el ejemplo siguiente, solo se enlazan las propiedades especificadas del modelo Instructor cuando se llama al método OnPost:
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
El atributo [Bind] se puede usar para proteger contra 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 poder defenderse de publicaciones excesiva, se recomienda disponer de modelos de vista en lugar de 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; }
}
Consulte también la explicación del [Required] atributo en Validación del modelo.
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; }
// ...
}
Collections
Para destinos que son colecciones de tipos simples, el enlace de modelos busca coincidencias con parameter_name o property_name. 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=2000selectedCourses[0]=1050&selectedCourses[1]=2000[0]=1050&[1]=2000selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b[a]=1050&[b]=2000&index=a&index=bEvite enlazar un parámetro o una propiedad denominada
indexoIndexsi está adyacente a un valor de colección. El enlace de modelos intenta usarindexcomo í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
indexse enlaza al parámetro de métodoindexy también se usa para enlazar la colección de productos. Cambiar el nombre del parámetroindexo 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[]=2000Para 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.
Dictionaries
Para Dictionary objetivos, la vinculación de modelos busca coincidencias con parameter_name o property_name. 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]=EconomicsselectedCourses[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=EconomicsPara 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 {
public 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 del proveedor de valores por 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, ASP.NET Core incluye formateadores de entrada basados en JSON para controlar los datos JSON mediante System.Text.Json. Puede agregar otros formateadores para otros tipos de contenido.
El formateador de entrada JSON predeterminado se puede configurar mediante el AddJsonOptions método :
builder.Services.AddControllers().AddJsonOptions(options =>
{
// Configure property naming policy (camelCase)
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
// Add enum converter to serialize enums as strings
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
// Configure other JSON options
options.JsonSerializerOptions.WriteIndented = true;
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
});
Entre las opciones de configuración comunes se incluyen:
- Directiva de nomenclatura de propiedades : configurar camelCase u otras convenciones de nomenclatura
- Convertidores de enumeración : control de la serialización de enumeración como cadenas
- Convertidores personalizados - Añadir lógica de serialización específica del tipo
ASP.NET Core selecciona formateadores de entrada en función del atributo Consumes . Si no hay ningún atributo presente, utiliza la cabecera 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
Consumesa 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 agregando 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 API web que consumen JSON usan formateadores de entrada para deserializar el cuerpo de la solicitud en un objeto.
Para obtener más información, vea 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 del 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.
Deserialización de Json+PipeReader en MVC
A partir de .NET 10, las siguientes áreas funcionales de ASP.NET Core usan sobrecargas de JsonSerializer.DeserializeAsync basadas en PipeReader en lugar de Stream:
- API mínima (enlace de parámetros, lectura del cuerpo de la solicitud)
- MVC (formateadores de entrada, modelo)
- Métodos HttpRequestJsonExtensions de extensión para leer el cuerpo de la solicitud como JSON.
Para la mayoría de las aplicaciones, una transición de Stream a PipeReader proporciona un mejor rendimiento sin necesidad de cambios en el código de la aplicación. Pero si su aplicación tiene un convertidor personalizado, es posible que el convertidor no maneje Utf8JsonReader.HasValueSequence correctamente. Si no es así, el resultado podría ser errores como ArgumentOutOfRangeException o faltan datos al deserializar. Tiene las siguientes opciones para que el convertidor funcione sin errores relacionados con PipeReader.
Opción 1: Solución alternativa temporal
La solución rápida consiste en volver al uso de Stream sin compatibilidad con PipeReader. Para implementar esta opción, configure el conmutador de AppContext "Microsoft.AspNetCore.UseStreamBasedJsonParsing" a "true". Se recomienda hacerlo solo como solución temporal y actualizar el convertidor para admitir HasValueSequence lo antes posible. Es posible que el switch se retire en .NET 11. Su único propósito era dar tiempo a los desarrolladores para actualizar sus convertidores.
Opción 2: Solución rápida para implementaciones de JsonConverter
Para esta corrección, asigna una matriz desde el ReadOnlySequence. En este ejemplo se muestra el aspecto del código:
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
// previous code
}
Opción 3: Una corrección más complicada pero con un mejor rendimiento
Esta corrección implica configurar una ruta de acceso de código independiente para el manejo de ReadOnlySequence.
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.HasValueSequence)
{
reader.ValueSequence;
// ReadOnlySequence optimized path
}
else
{
reader.ValueSpan;
// ReadOnlySpan optimized path
}
}
Para obtener más información, vea
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 las páginas Razor 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.
Example
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. Una vez enlazada correctamente cada propiedad, la validación del modelo se produce para esa propiedad. El registro de los datos que están enlazados al modelo y los errores de enlace o validación se almacenan en ControllerBase.ModelState o PageModel.ModelState. Para averiguar si este proceso se realizó 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 TypeConverter o un TryParse método. 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 o TryParse. 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.
Sources
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
IFormFileoIEnumerable<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 los valores a partir 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
Breedno 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 datos de origen se proporcionan al sistema de enlace de modelos por proveedores de valores. 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.
El ejemplo incluye un proveedor de valores y un ejemplo de fábrica que obtiene valores de las 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 idse 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 son gestionados por los 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
- Version
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>.TryParsepara 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.
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, busca simplemente property_name 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:
- Establezca
Iden100. - Establezca
Nameennull. El enlace de modelos esperaInstructor.NameporqueInstructor.Idse usó en el parámetro de consulta anterior.
Note
Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta para una versión específica, use la lista desplegable Cambiar ramas o etiquetas . Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).
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:
Warning
Estos atributos afectan al enlace de modelos cuando el origen de los valores son datos de formulario publicados. No afectan a los formateadores de entrada, que procesan los cuerpos de solicitud JSON y XML publicados. Los formateadores de entrada se explican más adelante en este artículo.
Atributo [Bind]
Se puede aplicar a una clase o un parámetro de método. Especifica qué propiedades de un modelo se deben incluir en el enlace de modelos.
[Bind]
no afecta a los formateadores de entrada.
En el ejemplo siguiente, solo se enlazan las propiedades especificadas del modelo Instructor cuando se llama a cualquier método de acción o controlador:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
En el ejemplo siguiente, solo se enlazan las propiedades especificadas del modelo Instructor cuando se llama al método OnPost:
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
El atributo [Bind] se puede usar para proteger contra 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 poder defenderse de publicaciones excesiva, se recomienda disponer de modelos de vista en lugar de 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; }
}
Consulte también la explicación del [Required] atributo en Validación del modelo.
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; }
// ...
}
Collections
Para destinos que son colecciones de tipos simples, el enlace de modelos busca coincidencias con parameter_name o property_name. 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=2000selectedCourses[0]=1050&selectedCourses[1]=2000[0]=1050&[1]=2000selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b[a]=1050&[b]=2000&index=a&index=bEvite enlazar un parámetro o una propiedad denominada
indexoIndexsi está adyacente a un valor de colección. El enlace de modelos intenta usarindexcomo í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
indexse enlaza al parámetro de métodoindexy también se usa para enlazar la colección de productos. Cambiar el nombre del parámetroindexo 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[]=2000Para 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.
Dictionaries
Para Dictionary objetivos, la vinculación de modelos busca coincidencias con parameter_name o property_name. 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]=EconomicsselectedCourses[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=EconomicsPara 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 del proveedor de valores por 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 formateadores de entrada en función del atributo Consumes . Si no hay ningún atributo presente, utiliza la cabecera 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
Consumesa 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 agregando 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 API web que consumen JSON usan formateadores de entrada para deserializar el cuerpo de la solicitud en un objeto.
Para obtener más información, vea 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 del 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 las páginas Razor 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.
Example
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. Una vez enlazada correctamente cada propiedad, la validación del modelo se produce para esa propiedad. El registro de los datos que están enlazados al modelo y los errores de enlace o validación se almacenan en ControllerBase.ModelState o PageModel.ModelState. Para averiguar si este proceso se realizó 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; }
Sources
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
IFormFileoIEnumerable<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 los valores a partir 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
Breedno 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 datos de origen se proporcionan al sistema de enlace de modelos por proveedores de valores. 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.
El ejemplo incluye un proveedor de valores y un ejemplo de fábrica que obtiene valores de las 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 idse 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 son gestionados por los 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
- Version
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, busca simplemente property_name 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:
- Establezca
Iden100. - Establezca
Nameennull. El enlace de modelos esperaInstructor.NameporqueInstructor.Idse usó en el parámetro de consulta anterior.
Note
Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta para una versión específica, use la lista desplegable Cambiar ramas o etiquetas . Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).
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:
Warning
Estos atributos afectan al enlace de modelos cuando el origen de los valores son datos de formulario publicados. No afectan a los formateadores de entrada, que procesan los cuerpos de solicitud JSON y XML publicados. Los formateadores de entrada se explican más adelante en este artículo.
Atributo [Bind]
Se puede aplicar a una clase o un parámetro de método. Especifica qué propiedades de un modelo se deben incluir en el enlace de modelos.
[Bind]
no afecta a los formateadores de entrada.
En el ejemplo siguiente, solo se enlazan las propiedades especificadas del modelo Instructor cuando se llama a cualquier método de acción o controlador:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
En el ejemplo siguiente, solo se enlazan las propiedades especificadas del modelo Instructor cuando se llama al método OnPost:
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
El atributo [Bind] se puede usar para proteger contra 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 poder defenderse de publicaciones excesiva, se recomienda disponer de modelos de vista en lugar de 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; }
}
Consulte también la explicación del [Required] atributo en Validación del modelo.
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; }
// ...
}
Collections
Para destinos que son colecciones de tipos simples, el enlace de modelos busca coincidencias con parameter_name o property_name. 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=2000selectedCourses[0]=1050&selectedCourses[1]=2000[0]=1050&[1]=2000selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b[a]=1050&[b]=2000&index=a&index=bEvite enlazar un parámetro o una propiedad denominada
indexoIndexsi está adyacente a un valor de colección. El enlace de modelos intenta usarindexcomo í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
indexse enlaza al parámetro de métodoindexy también se usa para enlazar la colección de productos. Cambiar el nombre del parámetroindexo 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[]=2000Para 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.
Dictionaries
Para Dictionary objetivos, la vinculación de modelos busca coincidencias con parameter_name o property_name. 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]=EconomicsselectedCourses[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=EconomicsPara 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 del proveedor de valores por 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 formateadores de entrada en función del atributo Consumes . Si no hay ningún atributo presente, utiliza la cabecera 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
Consumesa 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 agregando 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 API web que consumen JSON usan formateadores de entrada para deserializar el cuerpo de la solicitud en un objeto.
Para obtener más información, vea 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 del 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 las páginas Razor 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.
Example
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. Una vez enlazada correctamente cada propiedad, la validación del modelo se produce para esa propiedad. El registro de los datos que están enlazados al modelo y los errores de enlace o validación se almacenan en ControllerBase.ModelState o PageModel.ModelState. Para averiguar si este proceso se realizó 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 Core 2.1 o posterior. 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; }
Sources
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
IFormFileoIEnumerable<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 los valores a partir 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
Breedno 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 datos de origen se proporcionan al sistema de enlace de modelos por proveedores de valores. 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.
La aplicación de ejemplo incluye un proveedor de valores y un ejemplo de fábrica que obtiene valores de las 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 idse 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 son gestionados por los 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
- Version
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 examina los orígenes del patrón de nombre prefix.property_name. Si no se encuentra nada, busca simplemente property_name 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]
Warning
Estos atributos afectan al enlace de modelos cuando el origen de los valores son datos de formulario publicados. No afectan a los formateadores de entrada, que procesan los cuerpos de solicitud JSON y XML publicados. Los formateadores de entrada se explican más adelante en este artículo.
Atributo [Bind]
Se puede aplicar a una clase o un parámetro de método. Especifica qué propiedades de un modelo se deben incluir en el enlace de modelos.
[Bind]
no afecta a los formateadores de entrada.
En el ejemplo siguiente, solo se enlazan las propiedades especificadas del modelo Instructor cuando se llama a cualquier método de acción o controlador:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
En el ejemplo siguiente, solo se enlazan las propiedades especificadas del modelo Instructor cuando se llama al método OnPost:
[HttpPost]
public IActionResult OnPost([Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
El atributo [Bind] se puede usar para proteger contra 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 poder defenderse de publicaciones excesiva, se recomienda disponer de modelos de vista en lugar de 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; }
Consulte también la explicación del [Required] atributo en Validación del modelo.
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; }
Collections
Para destinos que son colecciones de tipos simples, el enlace de modelos busca coincidencias con parameter_name o property_name. 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=2000selectedCourses[0]=1050&selectedCourses[1]=2000[0]=1050&[1]=2000selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b[a]=1050&[b]=2000&index=a&index=bEvite enlazar un parámetro o una propiedad denominada
indexoIndexsi está adyacente a un valor de colección. El enlace de modelos intenta usarindexcomo í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
indexse enlaza al parámetro de métodoindexy también se usa para enlazar la colección de productos. Cambiar el nombre del parámetroindexo 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[]=2000Para 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.
Dictionaries
Para Dictionary objetivos, la vinculación de modelos busca coincidencias con parameter_name o property_name. 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]=EconomicsselectedCourses[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=EconomicsPara 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 del proveedor de valores por 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 formateadores de entrada en función del atributo Consumes . Si no hay ningún atributo presente, utiliza la cabecera 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
Consumesa 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 agregando 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 API web que consumen JSON usan formateadores de entrada para deserializar el cuerpo de la solicitud en un objeto.
Para obtener más información, vea 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 del 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.