Liaison de données dans ASP.NET Core
Remarque
Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 8 de cet article.
Avertissement
Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la Stratégie de prise en charge de .NET et .NET Core. Pour la version actuelle, consultez la version .NET 8 de cet article.
Important
Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.
Pour la version actuelle, consultez la version .NET 8 de cet article.
Cet article explique ce qu’est la liaison de modèle, comment elle fonctionne et comment personnaliser son comportement.
Description de la liaison de modèle
Les contrôleurs et Razor Pages utilisent des données provenant de requêtes HTTP. Par exemple, les données de routage peuvent fournir une clé d’enregistrement, et les champs de formulaire posté peuvent fournir des valeurs pour les propriétés du modèle. L’écriture du code permettant de récupérer chacune de ces valeurs et de les convertir en types .NET à partir de chaînes est fastidieuse et source d’erreurs. La liaison de modèle automatise ce processus. Le système de liaison de modèle :
- Récupère les données de diverses sources telles que les données de routage, les champs de formulaire et les chaînes de requête
- Fournit les données aux contrôleurs et à Razor Pages dans les paramètres de méthode et les propriétés publiques.
- Convertit les données de chaîne en types .NET
- Met à jour les propriétés des types complexes
Exemple
Supposons que vous ayez la méthode d’action suivante :
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
Et que l’application reçoive une requête avec l’URL suivante :
https://contoso.com/api/pets/2?DogsOnly=true
La liaison de modèle suit les étapes ci-après une fois que le système de routage a sélectionné la méthode d’action :
- Elle recherche le premier paramètre de
GetById
, un entier nomméid
. - Elle parcourt les sources disponibles dans la requête HTTP et trouve
id
= « 2 » dans les données de routage. - Elle convertit la chaîne « 2 » en entier 2.
- Elle recherche le paramètre suivant de
GetById
, un booléen nommédogsOnly
. - Elle parcourt les sources et trouve « DogsOnly=true » dans la chaîne de requête. La mise en correspondance des noms ne respecte pas la casse.
- Elle convertit la chaîne « true » en booléen
true
.
Le framework appelle ensuite la méthode GetById
, en passant 2 pour le paramètre id
, et true
pour le paramètre dogsOnly
.
Dans l’exemple précédent, les cibles de liaison de modèle sont des paramètres de méthode qui sont des types simples. Les cibles peuvent être également les propriétés d’un type complexe. Une fois chaque propriété correctement liée, la validation du modèle est effectuée pour la propriété concernée. L’enregistrement des données liées au modèle (ainsi que des erreurs de liaison ou de validation) est stocké dans ControllerBase.ModelState ou PageModel.ModelState. Pour savoir si ce processus a abouti, l’application vérifie l’indicateur ModelState.IsValid.
Targets
La liaison de modèle tente de trouver des valeurs pour les genres de cible suivants :
- Paramètres de la méthode d’action de contrôleur vers laquelle une requête est routée.
- Paramètres de la méthode de gestionnaire Razor Pages vers laquelle une requête est routée.
- Propriétés publiques d’un contrôleur ou d’une classe
PageModel
, si elles sont spécifiées par des attributs.
Attribut [BindProperty]
Peut être appliqué à une propriété publique d’un contrôleur ou à une classe PageModel
pour que la liaison de modèle cible cette propriété :
public class EditModel : PageModel
{
[BindProperty]
public Instructor? Instructor { get; set; }
// ...
}
Attribut [BindProperties]
Peut être appliqué à un contrôleur ou à une classe PageModel
pour indiquer à la liaison de modèle de cibler toutes les propriétés publiques de la classe :
[BindProperties]
public class CreateModel : PageModel
{
public Instructor? Instructor { get; set; }
// ...
}
Liaison de modèle pour les requêtes HTTP GET
Par défaut, les propriétés ne sont pas liées pour les requêtes HTTP GET. En règle générale, le paramètre ID d’un enregistrement est tout ce dont vous avez besoin pour une requête GET. L’ID d’enregistrement est utilisé pour rechercher l’élément dans la base de données. Il n’est donc pas nécessaire de lier une propriété qui contient une instance du modèle. Pour les scénarios dans lesquels vous souhaitez que les propriétés soient liées aux données provenant de requêtes GET, affectez à la propriété SupportsGet
la valeur true
:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }
Types simples et complexes de liaison de modèle
La liaison de données utilise des définitions spécifiques pour les types sur lesquels elle opère. Un type simple est converti à partir d’une seule chaîne en utilisant TypeConverter ou une méthode TryParse
. Un type complexe est converti à partir de plusieurs valeurs d’entrée. Le framework détermine la différence en fonction de l’existence de TypeConverter
ou TryParse
. Nous vous recommandons de créer un convertisseur de type ou d’utiliser TryParse
pour une conversion de string
vers SomeType
qui ne nécessite pas de ressources externes ou plusieurs entrées.
Sources
Par défaut, la liaison de modèle obtient des données sous la forme de paires clé-valeur à partir des sources suivantes dans une requête HTTP :
- Champs de formulaire
- Corps de la requête (pour les contrôleurs ayant l’attribut [ApiController].)
- Données de routage
- Paramètres de chaîne de requête
- Fichiers chargés
Pour chaque paramètre ou propriété cible, les sources sont analysées dans l’ordre indiqué dans cette liste. Il existe quelques exceptions :
- Les données de routage et les valeurs de chaîne de requête sont utilisées uniquement pour les types simples.
- Les fichiers chargés sont liés uniquement aux types cibles qui implémentent
IFormFile
ouIEnumerable<IFormFile>
.
Si la source par défaut n’est pas correcte, utilisez l’un des attributs suivants pour spécifier la source :
[FromQuery]
- Obtient les valeurs à partir de la chaîne de requête.[FromRoute]
- Obtient les valeurs à partir des données de routage.[FromForm]
- Obtient les valeurs à partir des champs de formulaire postés.[FromBody]
- Obtient les valeurs à partir du corps de la requête.[FromHeader]
- Obtient les valeurs à partir des en-têtes HTTP.
Ces attributs :
Sont ajoutés aux propriétés du modèle individuellement (et non à la classe de modèle), comme dans l’exemple suivant :
public class Instructor { public int Id { get; set; } [FromQuery(Name = "Note")] public string? NoteFromQueryString { get; set; } // ... }
Acceptent éventuellement une valeur de nom de modèle dans le constructeur. Cette option est fournie au cas où le nom de propriété ne correspondrait pas à la valeur de la requête. Par exemple, la valeur de la requête peut être un en-tête avec un trait d’union dans son nom, comme dans l’exemple suivant :
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
Attribut [FromBody]
Appliquez l’attribut [FromBody]
à un paramètre pour remplir ses propriétés à partir du corps d’une requête HTTP. Le runtime ASP.NET Core délègue la responsabilité de la lecture du flux de requête au formateur d’entrée. Les formateurs d’entrée sont décrits plus loin dans cet article.
Lorsque [FromBody]
est appliqué à un paramètre de type complexe, tous les attributs de source de liaison appliqués à ses propriétés sont ignorés. Par exemple, l’action suivante Create
spécifie que son paramètre pet
est rempli à partir du corps :
public ActionResult<Pet> Create([FromBody] Pet pet)
La classe Pet
spécifie que sa propriété Breed
est remplie à partir d’un paramètre de chaîne de requête :
public class Pet
{
public string Name { get; set; } = null!;
[FromQuery] // Attribute is ignored.
public string Breed { get; set; } = null!;
}
Dans l'exemple précédent :
- L'attribut
[FromQuery]
est ignoré. - La propriété
Breed
n’est pas remplie à partir d’un paramètre de chaîne de requête.
Les formateurs d’entrée lisent uniquement le corps et ne comprennent pas les attributs de la source de liaison. Si une valeur appropriée est trouvée dans le corps, cette valeur est utilisée pour remplir la propriété Breed
.
N’appliquez pas [FromBody]
à plus d’un paramètre par méthode d’action. Une fois le flux de requête lu par un formateur d’entrée, il ne peut plus être relu pour lier d’autres paramètres [FromBody]
.
Sources supplémentaires
Les données sources sont fournies au système de liaison de modèle par les fournisseurs de valeurs. Vous pouvez écrire et inscrire des fournisseurs de valeurs personnalisés qui obtiennent des données de liaison de modèle à partir d’autres sources. Par exemple, vous pouvez obtenir des données provenant de cookies ou de l’état de session. Pour obtenir des données provenant d’une nouvelle source :
- Créez une classe qui implémente
IValueProvider
. - Créez une classe qui implémente
IValueProviderFactory
. - Inscrivez la classe de fabrique dans
Program.cs
.
L’exemple comprend un exemple de fournisseur de valeurs et un exemple de fabrique, qui permet de récupérer les valeurs provenant de cookies. Inscrire des fabriques de fournisseurs de valeurs personnalisées dans Program.cs
:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
Le code précédent place le fournisseur de valeurs personnalisé après tous les fournisseurs de valeurs intégrés. Pour en faire le premier fournisseur de la liste, appelez Insert(0, new CookieValueProviderFactory())
à la place de Add
.
Aucune source pour une propriété de modèle
Par défaut, aucune erreur d’état de modèle n’est créée, s’il n’existe aucune valeur de propriété de modèle. La propriété a une valeur null ou une valeur par défaut :
- Les types simples pouvant accepter la valeur Null sont paramétrés sur
null
. - Les types valeur non Nullable ont la valeur
default(T)
. Par exemple, un paramètreint id
a la valeur 0. - Pour les types complexes, la liaison de modèle crée une instance à l’aide du constructeur par défaut, sans définir de propriétés.
- Les tableaux ont la valeur
Array.Empty<T>()
, sauf les tableauxbyte[]
qui ont une valeurnull
.
Si l’état de modèle doit être invalidé quand rien n’est trouvé dans les champs de formulaire d’une propriété de modèle, utilisez l’attribut [BindRequired]
.
Notez que ce comportement de [BindRequired]
s’applique à la liaison de modèle des données de formulaire postées, et non aux données JSON ou XML d’un corps de requête. Les données du corps de requête sont prises en charge par les formateurs d’entrée.
Erreurs de conversion de type
Si une source est localisée mais qu’elle ne peut pas être convertie vers le type cible, l’état du modèle est marqué comme étant non valide. Le paramètre ou la propriété cible a une valeur null ou une valeur par défaut, comme indiqué dans la section précédente.
Dans un contrôleur d’API ayant l’attribut [ApiController]
, un état de modèle non valide entraîne une réponse HTTP 400 automatique.
Dans une page Razor, réaffichez la page avec un message d’erreur :
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
// ...
return RedirectToPage("./Index");
}
Quand la page est réaffichée par le code précédent, l’entrée non valide n’est pas visible dans le champ de formulaire. En effet, la propriété de modèle à une valeur null ou une valeur par défaut. L’entrée non valide apparaît dans un message d’erreur. Toutefois, si vous souhaitez réafficher les données incorrectes dans le champ de formulaire, transformez la propriété de modèle en chaîne et procédez à la conversion des données manuellement.
La même stratégie est recommandée si vous ne souhaitez pas que les erreurs de conversion de type entraînent des erreurs d’état de modèle. Dans ce cas, transformez la propriété de modèle en chaîne.
Types simples
Consultez Types simples et complexes de liaison de modèle pour une explication des types simples et complexes.
Les types simples que le lieur de modèle peut convertir en chaînes sources sont les suivants :
- Booléen
- Byte, SByte
- Char
- DateOnly
- DateTime
- DateTimeOffset
- Décimal
- Double
- Enum
- GUID
- Int16, Int32, Int64
- Unique
- TimeOnly
- TimeSpan
- UInt16, UInt32, UInt64
- Uri
- Version
Liaison avec IParsable<T>.TryParse
L’API IParsable<TSelf>.TryParse
prend en charge les valeurs de paramètre d’action du contrôleur de liaison.
public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);
La classe DateRange
suivante implémente IParsable<TSelf>
pour prendre en charge la liaison d’une plage de dates :
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;
}
}
Le code précédent :
- Convertit une chaîne représentant deux dates en objet
DateRange
- Le classeur de modèles utilise la méthode
IParsable<TSelf>.TryParse
pour lier leDateRange
.
L’action de contrôleur suivante utilise la classe DateRange
pour lier une plage de dates :
// 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 classe Locale
suivante implémente IParsable<TSelf>
pour prendre en charge la liaison à 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;
}
}
}
L’action de contrôleur suivante utilise la classe Locale
pour lier une chaîne de 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);
}
L’action de contrôleur suivante utilise les classes DateRange
et Locale
pour lier une plage de dates avec 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);
}
L’exemple d’application API sur GitHub montre l’exemple précédent pour un contrôleur d’API.
Liaison avec TryParse
L’API TryParse
prend en charge les valeurs de paramètre d’action du contrôleur de liaison:
public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);
IParsable<T>.TryParse
est l’approche recommandée pour la liaison de paramètres, car contrairement à TryParse
, elle ne dépend pas de la réflexion.
La classe DateRangeTP
suivante implémenteTryParse
:
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;
}
}
L’action de contrôleur suivante utilise la classe DateRangeTP
pour lier une plage de dates :
// 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);
}
Types complexes
Un type complexe doit avoir un constructeur public par défaut et des propriétés publiques accessibles en écriture à lier. Quand la liaison de modèle se produit, la classe est instanciée à l’aide du constructeur public par défaut.
Pour chaque propriété du type complexe, la liaison de modèle recherche dans les sources le modèle de nom préfixe.nom_propriété. Si rien n’est trouvé, elle recherche uniquement nom_propriété sans le préfixe. La décision d’utiliser le préfixe n’est pas prise par propriété. Par exemple, avec une requête contenant ?Instructor.Id=100&Name=foo
, liée à la méthode OnGet(Instructor instructor)
, l’objet résultant de type Instructor
contient :
Id
défini sur100
.Name
défini surnull
. La liaison de modèle s’attend àInstructor.Name
carInstructor.Id
a été utilisé dans le paramètre de requête précédent.
Dans le cas d’une liaison à un paramètre, le préfixe représente le nom du paramètre. Dans le cas d’une liaison à une propriété publique PageModel
, le préfixe représente le nom de la propriété publique. Certains attributs ont une propriété Prefix
qui vous permet de remplacer l’utilisation par défaut du nom de paramètre ou de propriété.
Par exemple, supposons que le type complexe corresponde à la classe Instructor
suivante :
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
Préfixe = nom de paramètre
Si le modèle à lier est un paramètre nommé instructorToUpdate
:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
La liaison de modèle commence par rechercher dans les sources la clé instructorToUpdate.ID
. Si elle est introuvable, elle recherche ID
sans préfixe.
Préfixe = nom de propriété
Si le modèle à lier est une propriété nommée Instructor
du contrôleur ou de la classe PageModel
:
[BindProperty]
public Instructor Instructor { get; set; }
La liaison de modèle commence par rechercher dans les sources la clé Instructor.ID
. Si elle est introuvable, elle recherche ID
sans préfixe.
Préfixe personnalisé
Si le modèle à lier est un paramètre nommé instructorToUpdate
et si un attribut Bind
spécifie Instructor
en tant que préfixe :
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
La liaison de modèle commence par rechercher dans les sources la clé Instructor.ID
. Si elle est introuvable, elle recherche ID
sans préfixe.
Attributs des cibles de type complexe
Plusieurs attributs intégrés sont disponibles pour contrôler la liaison de modèle des types complexes :
Avertissement
Ces attributs affectent la liaison de modèle quand les données de formulaire postées représentent la source des valeurs. Ils n’affectent pas les formateurs d’entrée, qui traitent les corps de requête JSON et XML postés. Les formateurs d’entrée sont décrits plus loin dans cet article.
Attribut [Bind]
Il peut être appliqué à une classe ou à un paramètre de méthode. Il spécifie les propriétés d’un modèle à inclure dans la liaison de modèle. [Bind]
n’affectepas les formateurs d’entrée.
Dans l’exemple suivant, seules les propriétés spécifiées du modèle Instructor
sont liées quand une méthode de gestionnaire ou une méthode d’action est appelée :
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
Dans l’exemple suivant, seules les propriétés spécifiées du modèle Instructor
sont liées quand la méthode OnPost
est appelée :
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
Vous pouvez utiliser l’attribut [Bind]
pour éviter le surpostage dans les scénarios de création. Il ne fonctionne pas bien dans les scénarios de modification, car les propriétés exclues ont une valeur null ou une valeur par défaut au lieu de rester inchangées. Pour empêcher le surpostage, il est recommandé d’utiliser des modèles de vues à la place de l’attribut [Bind]
. Pour plus d’informations, consultez Remarque sur la sécurité concernant le surpostage.
Attribut [ModelBinder]
ModelBinderAttribute peut être appliqué à des types, des propriétés ou des paramètres. Il permet de spécifier le type de classeur de modèle utilisé pour lier l’instance ou le type spécifique. Par exemple :
[HttpPost]
public IActionResult OnPost(
[ModelBinder<MyInstructorModelBinder>] Instructor instructor)
L’attribut [ModelBinder]
peut également être utilisé pour modifier le nom d’une propriété ou d’un paramètre lorsqu’il est lié au modèle :
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
// ...
}
Attribut [BindRequired]
Il oblige la liaison de modèle à ajouter une erreur d’état de modèle si la liaison est impossible pour la propriété d’un modèle. Voici un exemple :
public class InstructorBindRequired
{
// ...
[BindRequired]
public DateTime HireDate { get; set; }
}
Consultez également la discussion sur l’attribut [Required]
dans Validation de modèle.
Attribut [BindNever]
Peut être appliqué à une propriété ou à un type. Il empêche la liaison de modèle de définir la propriété d’un modèle. Lorsqu’il est appliqué à un type, le système de liaison de modèle exclut toutes les propriétés définies par le type. Voici un exemple :
public class InstructorBindNever
{
[BindNever]
public int Id { get; set; }
// ...
}
Collections
Pour les cibles qui sont des collections de types simples, la liaison de modèle recherche les correspondances avec nom_paramètre ou nom_propriété. Si aucune correspondance n’est localisée, elle recherche l’un des formats pris en charge sans le préfixe. Par exemple :
Supposons que le paramètre à lier soit un tableau nommé
selectedCourses
:public IActionResult OnPost(int? id, int[] selectedCourses)
Les données de formulaire ou de chaîne de requête peuvent avoir l’un des formats suivants :
selectedCourses=1050&selectedCourses=2000
selectedCourses[0]=1050&selectedCourses[1]=2000
[0]=1050&[1]=2000
selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
[a]=1050&[b]=2000&index=a&index=b
Évitez de lier un paramètre ou une propriété nommée
index
ouIndex
adjacent(e) à une valeur de collection. La liaison de modèle tente d’utiliserindex
comme index pour la collection, ce qui peut entraîner une liaison incorrecte. Prenons par exemple l’action suivante :public IActionResult Post(string index, List<Product> products)
Dans le code précédent, le paramètre de chaîne de requête
index
se lie au paramètre de méthodeindex
et est également utilisé pour lier la collection de produits. Le changement de nom du paramètreindex
ou l’utilisation d’un attribut de liaison de modèle pour configurer la liaison évite ce problème :public IActionResult Post(string productIndex, List<Product> products)
Le format suivant est pris en charge uniquement dans les données de formulaire :
selectedCourses[]=1050&selectedCourses[]=2000
Pour tous les exemples de formats précédents, la liaison de modèle passe un tableau de deux éléments au paramètre
selectedCourses
:- selectedCourses[0]=1050
- selectedCourses[1]=2000
Les formats de données qui utilisent des nombres en indice (... [0] ... [1] ...) doivent être impérativement numérotés de manière séquentielle à partir de zéro. S’il existe des vides dans la numérotation en indice, tous les éléments suivants sont ignorés. Par exemple, si les indices sont 0 et 2 au lieu de 0 et 1, le second élément est ignoré.
Dictionnaires
Pour les cibles Dictionary
, la liaison de modèle recherche les correspondances avec nom_paramètre ou nom_propriété. Si aucune correspondance n’est localisée, elle recherche l’un des formats pris en charge sans le préfixe. Par exemple :
Supposons que le paramètre cible soit un
Dictionary<int, string>
nomméselectedCourses
:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
Les données de chaîne de requête ou de formulaire posté peuvent ressembler à l’un des exemples suivants :
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
[1050]=Chemistry&selectedCourses[2000]=Economics
selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
Pour tous les exemples de formats précédents, la liaison de modèle passe un dictionnaire de deux éléments au paramètre
selectedCourses
:- selectedCourses["1050"]="Chemistry"
- selectedCourses["2000"]="Economics"
Liaison de constructeur et types d’enregistrements
La liaison de modèle nécessite que les types complexes aient un constructeur sans paramètre. Les formateurs d’entrée basés sur System.Text.Json
et Newtonsoft.Json
prennent en charge la désérialisation des classes qui n’ont pas de constructeur sans paramètre.
Les types d’enregistrements sont un excellent moyen de représenter succinctement des données sur le réseau. ASP.NET Core prend en charge la liaison de modèle et la validation des types d’enregistrements avec un seul constructeur :
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>
Lors de la validation des types d’enregistrements, le CLR recherche les métadonnées de liaison et de validation spécifiquement sur les paramètres plutôt que sur les propriétés.
L’infrastructure permet de lier et de valider les types d’enregistrements :
public record Person([Required] string Name, [Range(0, 100)] int Age);
Pour que le précédent fonctionne, le type doit :
- Etre un type d’enregistrement.
- Avoir exactement un constructeur public.
- Contenir des paramètres qui ont une propriété portant le même nom et le même type. Les noms ne doivent pas différer par cas.
Types OCT sans constructeurs sans paramètre
Les types OCT qui n’ont pas de constructeurs sans paramètre ne peuvent pas être liés.
Le code suivant génère une exception indiquant que le type doit avoir un constructeur sans paramètre :
public class Person(string Name)
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0);
}
Types d’enregistrements avec des constructeurs créés manuellement
Types d’enregistrement avec des constructeurs créés manuellement qui ressemblent au travail des constructeurs principaux
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; }
}
Types d’enregistrements, métadonnées de validation et de liaison
Pour les types d’enregistrements, la validation et la liaison des métadonnées sur les paramètres est utilisée. Toutes les métadonnées sur les propriétés sont ignorées
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; }
}
Validation et métadonnées
La validation utilise des métadonnées sur le paramètre, mais utilise la propriété pour lire la valeur. Dans le cas ordinaire des constructeurs principaux, les deux sont identiques. Toutefois, il existe des moyens de le contourner :
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 ne met pas à jour les paramètres sur un type d’enregistrement
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
Dans ce cas, MVC ne tente pas de lier Name
à nouveau. Toutefois, Age
est autorisé à être mis à jour
Comportement de globalisation des données de routage et des chaînes de requête de liaison de modèle
Le fournisseur de valeur de routage ASP.NET et de valeur de chaîne de requête :
- Traite les valeurs comme une culture invariante.
- S’attend à ce que les URL soient de culture invariante.
En revanche, les valeurs provenant des données de formulaire subissent une conversion sensible à la culture. Cela est conçu pour que les URL soient partageables entre les paramètres régionaux.
Pour que le fournisseur de valeur de routage et le fournisseur de valeur de chaîne de requête ASP.NET Core subissent une conversion sensible à la culture :
- Héritent de IValueProviderFactory
- Copiez le code à partir de QueryStringValueProviderFactory ou RouteValueValueProviderFactory
- Remplacez la valeur de culture passée au constructeur du fournisseur de valeurs par CultureInfo.CurrentCulture
- Remplacez la fabrique de fournisseur de valeurs par défaut dans les options MVC par la nouvelle :
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();
});
Types de données spéciaux
Certains types de données spéciaux peuvent être pris en charge par la liaison de modèle.
IFormFile et IFormFileCollection
Fichier chargé inclus dans la requête HTTP. IEnumerable<IFormFile>
est également pris en charge pour plusieurs fichiers.
CancellationToken
Les actions peuvent éventuellement lier un CancellationToken
en tant que paramètre. Cette liaison de RequestAborted indique quand la connexion sous-jacente à la requête HTTP est abandonnée. Les actions peuvent utiliser ce paramètre pour annuler les opérations asynchrones durables exécutées dans le cadre des actions du contrôleur.
FormCollection
Permet de récupérer toutes les valeurs des données de formulaire posté.
Formateurs d’entrée
Les données contenues dans le corps de la requête peuvent être au format JSON, XML ou tout autre format. Pour analyser ces données, la liaison de modèle utilise un formateur d’entrée configuré pour prendre en charge un type de contenu particulier. Par défaut, ASP.NET Core inclut des formateurs d’entrée basés sur le format JSON pour prendre en charge les données JSON. Vous pouvez ajouter d’autres formateurs pour d’autres types de contenu.
ASP.NET Core sélectionne les formateurs d’entrée en fonction de l’attribut Consumes. Si aucun attribut n’est présent, il utilise l’en-tête Content-Type.
Pour utiliser les formateurs d’entrée XML intégrés :
Dans
Program.cs
, appelez AddXmlSerializerFormatters ou AddXmlDataContractSerializerFormatters.builder.Services.AddControllers() .AddXmlSerializerFormatters();
Appliquez l’attribut
Consumes
aux classes de contrôleur ou aux méthodes d’action devant contenir des données XML dans le corps de la requête.[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)
Pour plus d’informations, consultez Introduction à la sérialisation XML.
Personnaliser la liaison de modèle avec des formateurs d’entrée
Un formateur d’entrée assume l’entière responsabilité de la lecture des données du corps de la requête. Pour personnaliser ce processus, configurez les API utilisées par le formateur d’entrée. Cette section explique comment personnaliser le formateur d’entrée basé sur System.Text.Json
pour comprendre un type personnalisé nommé ObjectId
.
Considérez le modèle suivant, qui contient une propriété ObjectId
personnalisée :
public class InstructorObjectId
{
[Required]
public ObjectId ObjectId { get; set; } = null!;
}
Pour personnaliser le processus de liaison de modèle lors de l’utilisation deSystem.Text.Json
, créez une classe dérivée 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);
}
Pour utiliser un convertisseur personnalisé, appliquez l’attribut JsonConverterAttribute au type . Dans l’exemple suivant, le type ObjectId
est configuré avec ObjectIdConverter
comme convertisseur personnalisé :
[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);
Pour plus d’informations, consultez Comment écrire des convertisseurs personnalisés.
Exclure les types spécifiés de la liaison de modèle
Le comportement de la liaison de modèle et du système de validation est régi par ModelMetadata. Vous pouvez personnaliser ModelMetadata
en ajoutant un fournisseur de détails à MvcOptions.ModelMetadataDetailsProviders. Des fournisseurs de détails intégrés sont disponibles pour désactiver la liaison de modèle ou la validation des types spécifiés.
Pour désactiver la liaison de modèle sur tous les modèles d’un type spécifique, ajoutez ExcludeBindingMetadataProvider dans Program.cs
. Par exemple, pour désactiver la liaison de modèle sur tous les modèles de type System.Version
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
Pour désactiver la validation des propriétés d’un type spécifique, ajoutez SuppressChildValidationMetadataProvider dans Program.cs
. Par exemple, pour désactiver la validation sur les propriétés de type System.Guid
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
Lieurs de modèles personnalisés
Vous pouvez étendre la liaison de modèle en écrivant un lieur de modèle personnalisé et en utilisant l’attribut [ModelBinder]
afin de le sélectionner pour une cible donnée. Découvrez plus d’informations sur la liaison de modèle personnalisée.
Liaison de modèle manuelle
Vous pouvez appeler la liaison de modèle manuellement à l’aide de la méthode TryUpdateModelAsync. La méthode est définie sur les classes ControllerBase
et PageModel
. Les surcharges de méthode vous permettent de spécifier le préfixe et le fournisseur de valeurs à utiliser. La méthode retourne false
en cas d’échec de la liaison de modèle. Voici un exemple :
if (await TryUpdateModelAsync(
newInstructor,
"Instructor",
x => x.Name, x => x.HireDate!))
{
_instructorStore.Add(newInstructor);
return RedirectToPage("./Index");
}
return Page();
TryUpdateModelAsync utilise des fournisseurs de valeurs pour obtenir des données à partir du corps du formulaire, de la chaîne de requête et des données de routage. TryUpdateModelAsync
est généralement :
- Utilisé avec les applications RazorPages et MVC à l’aide de contrôleurs et de vues pour empêcher la publication excessive.
- Non utilisé avec une API web, sauf si consommé à partir de données de formulaire, de chaînes de requête et de données de routage. Les points de terminaison d’API web qui utilisent JSON utilisent des formateurs d’entrée pour désérialiser le corps de la demande en un objet.
Pour plus d’informations, consultez TryUpdateModelAsync.
Attribut [FromServices]
Le nom de cet attribut suit le modèle des attributs de liaison de modèle qui spécifient une source de données. Toutefois, il ne permet pas de lier les données d’un fournisseur de valeurs. Il obtient une instance d’un type à partir du conteneur d’injection de dépendances. Son objectif est de fournir une alternative à l’injection de constructeurs quand vous avez besoin d’un service uniquement si une méthode particulière est appelée.
Si une instance du type n’est pas inscrite dans le conteneur d’injection de dépendances, l’application lève une exception lors de la tentative de liaison du paramètre. Pour rendre le paramètre facultatif, utilisez l’une des approches suivantes :
- Transformez le paramètre en paramètre pouvant accepter la valeur Null.
- Définissez une valeur par défaut pour le paramètre .
Pour les paramètres pouvant accepter le paramètre Null, vérifiez que le paramètre n’est pas null
avant d’y accéder.
Ressources supplémentaires
Cet article explique ce qu’est la liaison de modèle, comment elle fonctionne et comment personnaliser son comportement.
Description de la liaison de modèle
Les contrôleurs et Razor Pages utilisent des données provenant de requêtes HTTP. Par exemple, les données de routage peuvent fournir une clé d’enregistrement, et les champs de formulaire posté peuvent fournir des valeurs pour les propriétés du modèle. L’écriture du code permettant de récupérer chacune de ces valeurs et de les convertir en types .NET à partir de chaînes est fastidieuse et source d’erreurs. La liaison de modèle automatise ce processus. Le système de liaison de modèle :
- Récupère les données de diverses sources telles que les données de routage, les champs de formulaire et les chaînes de requête
- Fournit les données aux contrôleurs et à Razor Pages dans les paramètres de méthode et les propriétés publiques.
- Convertit les données de chaîne en types .NET
- Met à jour les propriétés des types complexes
Exemple
Supposons que vous ayez la méthode d’action suivante :
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
Et que l’application reçoive une requête avec l’URL suivante :
https://contoso.com/api/pets/2?DogsOnly=true
La liaison de modèle suit les étapes ci-après une fois que le système de routage a sélectionné la méthode d’action :
- Elle recherche le premier paramètre de
GetById
, un entier nomméid
. - Elle parcourt les sources disponibles dans la requête HTTP et trouve
id
= « 2 » dans les données de routage. - Elle convertit la chaîne « 2 » en entier 2.
- Elle recherche le paramètre suivant de
GetById
, un booléen nommédogsOnly
. - Elle parcourt les sources et trouve « DogsOnly=true » dans la chaîne de requête. La mise en correspondance des noms ne respecte pas la casse.
- Elle convertit la chaîne « true » en booléen
true
.
Le framework appelle ensuite la méthode GetById
, en passant 2 pour le paramètre id
, et true
pour le paramètre dogsOnly
.
Dans l’exemple précédent, les cibles de liaison de modèle sont des paramètres de méthode qui sont des types simples. Les cibles peuvent être également les propriétés d’un type complexe. Une fois chaque propriété correctement liée, la validation du modèle est effectuée pour la propriété concernée. L’enregistrement des données liées au modèle (ainsi que des erreurs de liaison ou de validation) est stocké dans ControllerBase.ModelState ou PageModel.ModelState. Pour savoir si ce processus a abouti, l’application vérifie l’indicateur ModelState.IsValid.
Targets
La liaison de modèle tente de trouver des valeurs pour les genres de cible suivants :
- Paramètres de la méthode d’action de contrôleur vers laquelle une requête est routée.
- Paramètres de la méthode de gestionnaire Razor Pages vers laquelle une requête est routée.
- Propriétés publiques d’un contrôleur ou d’une classe
PageModel
, si elles sont spécifiées par des attributs.
Attribut [BindProperty]
Peut être appliqué à une propriété publique d’un contrôleur ou à une classe PageModel
pour que la liaison de modèle cible cette propriété :
public class EditModel : PageModel
{
[BindProperty]
public Instructor? Instructor { get; set; }
// ...
}
Attribut [BindProperties]
Peut être appliqué à un contrôleur ou à une classe PageModel
pour indiquer à la liaison de modèle de cibler toutes les propriétés publiques de la classe :
[BindProperties]
public class CreateModel : PageModel
{
public Instructor? Instructor { get; set; }
// ...
}
Liaison de modèle pour les requêtes HTTP GET
Par défaut, les propriétés ne sont pas liées pour les requêtes HTTP GET. En règle générale, le paramètre ID d’un enregistrement est tout ce dont vous avez besoin pour une requête GET. L’ID d’enregistrement est utilisé pour rechercher l’élément dans la base de données. Il n’est donc pas nécessaire de lier une propriété qui contient une instance du modèle. Pour les scénarios dans lesquels vous souhaitez que les propriétés soient liées aux données provenant de requêtes GET, affectez à la propriété SupportsGet
la valeur true
:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }
Types simples et complexes de liaison de modèle
La liaison de données utilise des définitions spécifiques pour les types sur lesquels elle opère. Un type simple est converti à partir d’une seule chaîne en utilisant TypeConverter ou une méthode TryParse
. Un type complexe est converti à partir de plusieurs valeurs d’entrée. Le framework détermine la différence en fonction de l’existence de TypeConverter
ou TryParse
. Nous vous recommandons de créer un convertisseur de type ou d’utiliser TryParse
pour une conversion de string
vers SomeType
qui ne nécessite pas de ressources externes ou plusieurs entrées.
Sources
Par défaut, la liaison de modèle obtient des données sous la forme de paires clé-valeur à partir des sources suivantes dans une requête HTTP :
- Champs de formulaire
- Corps de la requête (pour les contrôleurs ayant l’attribut [ApiController].)
- Données de routage
- Paramètres de chaîne de requête
- Fichiers chargés
Pour chaque paramètre ou propriété cible, les sources sont analysées dans l’ordre indiqué dans cette liste. Il existe quelques exceptions :
- Les données de routage et les valeurs de chaîne de requête sont utilisées uniquement pour les types simples.
- Les fichiers chargés sont liés uniquement aux types cibles qui implémentent
IFormFile
ouIEnumerable<IFormFile>
.
Si la source par défaut n’est pas correcte, utilisez l’un des attributs suivants pour spécifier la source :
[FromQuery]
- Obtient les valeurs à partir de la chaîne de requête.[FromRoute]
- Obtient les valeurs à partir des données de routage.[FromForm]
- Obtient les valeurs à partir des champs de formulaire postés.[FromBody]
- Obtient les valeurs à partir du corps de la requête.[FromHeader]
- Obtient les valeurs à partir des en-têtes HTTP.
Ces attributs :
Sont ajoutés aux propriétés du modèle individuellement (et non à la classe de modèle), comme dans l’exemple suivant :
public class Instructor { public int Id { get; set; } [FromQuery(Name = "Note")] public string? NoteFromQueryString { get; set; } // ... }
Acceptent éventuellement une valeur de nom de modèle dans le constructeur. Cette option est fournie au cas où le nom de propriété ne correspondrait pas à la valeur de la requête. Par exemple, la valeur de la requête peut être un en-tête avec un trait d’union dans son nom, comme dans l’exemple suivant :
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
Attribut [FromBody]
Appliquez l’attribut [FromBody]
à un paramètre pour remplir ses propriétés à partir du corps d’une requête HTTP. Le runtime ASP.NET Core délègue la responsabilité de la lecture du flux de requête au formateur d’entrée. Les formateurs d’entrée sont décrits plus loin dans cet article.
Lorsque [FromBody]
est appliqué à un paramètre de type complexe, tous les attributs de source de liaison appliqués à ses propriétés sont ignorés. Par exemple, l’action suivante Create
spécifie que son paramètre pet
est rempli à partir du corps :
public ActionResult<Pet> Create([FromBody] Pet pet)
La classe Pet
spécifie que sa propriété Breed
est remplie à partir d’un paramètre de chaîne de requête :
public class Pet
{
public string Name { get; set; } = null!;
[FromQuery] // Attribute is ignored.
public string Breed { get; set; } = null!;
}
Dans l'exemple précédent :
- L'attribut
[FromQuery]
est ignoré. - La propriété
Breed
n’est pas remplie à partir d’un paramètre de chaîne de requête.
Les formateurs d’entrée lisent uniquement le corps et ne comprennent pas les attributs de la source de liaison. Si une valeur appropriée est trouvée dans le corps, cette valeur est utilisée pour remplir la propriété Breed
.
N’appliquez pas [FromBody]
à plus d’un paramètre par méthode d’action. Une fois le flux de requête lu par un formateur d’entrée, il ne peut plus être relu pour lier d’autres paramètres [FromBody]
.
Sources supplémentaires
Les données sources sont fournies au système de liaison de modèle par les fournisseurs de valeurs. Vous pouvez écrire et inscrire des fournisseurs de valeurs personnalisés qui obtiennent des données de liaison de modèle à partir d’autres sources. Par exemple, vous pouvez obtenir des données provenant de cookies ou de l’état de session. Pour obtenir des données provenant d’une nouvelle source :
- Créez une classe qui implémente
IValueProvider
. - Créez une classe qui implémente
IValueProviderFactory
. - Inscrivez la classe de fabrique dans
Program.cs
.
L’exemple comprend un exemple de fournisseur de valeurs et un exemple de fabrique, qui permet de récupérer les valeurs provenant de cookies. Inscrire des fabriques de fournisseurs de valeurs personnalisées dans Program.cs
:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
Le code précédent place le fournisseur de valeurs personnalisé après tous les fournisseurs de valeurs intégrés. Pour en faire le premier fournisseur de la liste, appelez Insert(0, new CookieValueProviderFactory())
à la place de Add
.
Aucune source pour une propriété de modèle
Par défaut, aucune erreur d’état de modèle n’est créée, s’il n’existe aucune valeur de propriété de modèle. La propriété a une valeur null ou une valeur par défaut :
- Les types simples pouvant accepter la valeur Null sont paramétrés sur
null
. - Les types valeur non Nullable ont la valeur
default(T)
. Par exemple, un paramètreint id
a la valeur 0. - Pour les types complexes, la liaison de modèle crée une instance à l’aide du constructeur par défaut, sans définir de propriétés.
- Les tableaux ont la valeur
Array.Empty<T>()
, sauf les tableauxbyte[]
qui ont une valeurnull
.
Si l’état de modèle doit être invalidé quand rien n’est trouvé dans les champs de formulaire d’une propriété de modèle, utilisez l’attribut [BindRequired]
.
Notez que ce comportement de [BindRequired]
s’applique à la liaison de modèle des données de formulaire postées, et non aux données JSON ou XML d’un corps de requête. Les données du corps de requête sont prises en charge par les formateurs d’entrée.
Erreurs de conversion de type
Si une source est localisée mais qu’elle ne peut pas être convertie vers le type cible, l’état du modèle est marqué comme étant non valide. Le paramètre ou la propriété cible a une valeur null ou une valeur par défaut, comme indiqué dans la section précédente.
Dans un contrôleur d’API ayant l’attribut [ApiController]
, un état de modèle non valide entraîne une réponse HTTP 400 automatique.
Dans une page Razor, réaffichez la page avec un message d’erreur :
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
// ...
return RedirectToPage("./Index");
}
Quand la page est réaffichée par le code précédent, l’entrée non valide n’est pas visible dans le champ de formulaire. En effet, la propriété de modèle à une valeur null ou une valeur par défaut. L’entrée non valide apparaît dans un message d’erreur. Toutefois, si vous souhaitez réafficher les données incorrectes dans le champ de formulaire, transformez la propriété de modèle en chaîne et procédez à la conversion des données manuellement.
La même stratégie est recommandée si vous ne souhaitez pas que les erreurs de conversion de type entraînent des erreurs d’état de modèle. Dans ce cas, transformez la propriété de modèle en chaîne.
Types simples
Consultez Types simples et complexes de liaison de modèle pour une explication des types simples et complexes.
Les types simples que le lieur de modèle peut convertir en chaînes sources sont les suivants :
- Booléen
- Byte, SByte
- Char
- DateOnly
- DateTime
- DateTimeOffset
- Décimal
- Double
- Enum
- GUID
- Int16, Int32, Int64
- Unique
- TimeOnly
- TimeSpan
- UInt16, UInt32, UInt64
- Uri
- Version
Liaison avec IParsable<T>.TryParse
L’API IParsable<TSelf>.TryParse
prend en charge les valeurs de paramètre d’action du contrôleur de liaison.
public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);
La classe DateRange
suivante implémente IParsable<TSelf>
pour prendre en charge la liaison d’une plage de dates :
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;
}
}
Le code précédent :
- Convertit une chaîne représentant deux dates en objet
DateRange
- Le classeur de modèles utilise la méthode
IParsable<TSelf>.TryParse
pour lier leDateRange
.
L’action de contrôleur suivante utilise la classe DateRange
pour lier une plage de dates :
// 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 classe Locale
suivante implémente IParsable<TSelf>
pour prendre en charge la liaison à 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;
}
}
}
L’action de contrôleur suivante utilise la classe Locale
pour lier une chaîne de 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);
}
L’action de contrôleur suivante utilise les classes DateRange
et Locale
pour lier une plage de dates avec 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);
}
L’exemple d’application API sur GitHub montre l’exemple précédent pour un contrôleur d’API.
Liaison avec TryParse
L’API TryParse
prend en charge les valeurs de paramètre d’action du contrôleur de liaison:
public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);
IParsable<T>.TryParse
est l’approche recommandée pour la liaison de paramètres, car contrairement à TryParse
, elle ne dépend pas de la réflexion.
La classe DateRangeTP
suivante implémenteTryParse
:
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;
}
}
L’action de contrôleur suivante utilise la classe DateRangeTP
pour lier une plage de dates :
// 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);
}
Types complexes
Un type complexe doit avoir un constructeur public par défaut et des propriétés publiques accessibles en écriture à lier. Quand la liaison de modèle se produit, la classe est instanciée à l’aide du constructeur public par défaut.
Pour chaque propriété du type complexe, la liaison de modèle recherche dans les sources le modèle de nom préfixe.nom_propriété. Si rien n’est trouvé, elle recherche uniquement nom_propriété sans le préfixe. La décision d’utiliser le préfixe n’est pas prise par propriété. Par exemple, avec une requête contenant ?Instructor.Id=100&Name=foo
, liée à la méthode OnGet(Instructor instructor)
, l’objet résultant de type Instructor
contient :
Id
défini sur100
.Name
défini surnull
. La liaison de modèle s’attend àInstructor.Name
carInstructor.Id
a été utilisé dans le paramètre de requête précédent.
Dans le cas d’une liaison à un paramètre, le préfixe représente le nom du paramètre. Dans le cas d’une liaison à une propriété publique PageModel
, le préfixe représente le nom de la propriété publique. Certains attributs ont une propriété Prefix
qui vous permet de remplacer l’utilisation par défaut du nom de paramètre ou de propriété.
Par exemple, supposons que le type complexe corresponde à la classe Instructor
suivante :
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
Préfixe = nom de paramètre
Si le modèle à lier est un paramètre nommé instructorToUpdate
:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
La liaison de modèle commence par rechercher dans les sources la clé instructorToUpdate.ID
. Si elle est introuvable, elle recherche ID
sans préfixe.
Préfixe = nom de propriété
Si le modèle à lier est une propriété nommée Instructor
du contrôleur ou de la classe PageModel
:
[BindProperty]
public Instructor Instructor { get; set; }
La liaison de modèle commence par rechercher dans les sources la clé Instructor.ID
. Si elle est introuvable, elle recherche ID
sans préfixe.
Préfixe personnalisé
Si le modèle à lier est un paramètre nommé instructorToUpdate
et si un attribut Bind
spécifie Instructor
en tant que préfixe :
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
La liaison de modèle commence par rechercher dans les sources la clé Instructor.ID
. Si elle est introuvable, elle recherche ID
sans préfixe.
Attributs des cibles de type complexe
Plusieurs attributs intégrés sont disponibles pour contrôler la liaison de modèle des types complexes :
Avertissement
Ces attributs affectent la liaison de modèle quand les données de formulaire postées représentent la source des valeurs. Ils n’affectent pas les formateurs d’entrée, qui traitent les corps de requête JSON et XML postés. Les formateurs d’entrée sont décrits plus loin dans cet article.
Attribut [Bind]
Il peut être appliqué à une classe ou à un paramètre de méthode. Il spécifie les propriétés d’un modèle à inclure dans la liaison de modèle. [Bind]
n’affectepas les formateurs d’entrée.
Dans l’exemple suivant, seules les propriétés spécifiées du modèle Instructor
sont liées quand une méthode de gestionnaire ou une méthode d’action est appelée :
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
Dans l’exemple suivant, seules les propriétés spécifiées du modèle Instructor
sont liées quand la méthode OnPost
est appelée :
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
Vous pouvez utiliser l’attribut [Bind]
pour éviter le surpostage dans les scénarios de création. Il ne fonctionne pas bien dans les scénarios de modification, car les propriétés exclues ont une valeur null ou une valeur par défaut au lieu de rester inchangées. Pour empêcher le surpostage, il est recommandé d’utiliser des modèles de vues à la place de l’attribut [Bind]
. Pour plus d’informations, consultez Remarque sur la sécurité concernant le surpostage.
Attribut [ModelBinder]
ModelBinderAttribute peut être appliqué à des types, des propriétés ou des paramètres. Il permet de spécifier le type de classeur de modèle utilisé pour lier l’instance ou le type spécifique. Par exemple :
[HttpPost]
public IActionResult OnPost(
[ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
L’attribut [ModelBinder]
peut également être utilisé pour modifier le nom d’une propriété ou d’un paramètre lorsqu’il est lié au modèle :
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
// ...
}
Attribut [BindRequired]
Il oblige la liaison de modèle à ajouter une erreur d’état de modèle si la liaison est impossible pour la propriété d’un modèle. Voici un exemple :
public class InstructorBindRequired
{
// ...
[BindRequired]
public DateTime HireDate { get; set; }
}
Consultez également la discussion sur l’attribut [Required]
dans Validation de modèle.
Attribut [BindNever]
Peut être appliqué à une propriété ou à un type. Il empêche la liaison de modèle de définir la propriété d’un modèle. Lorsqu’il est appliqué à un type, le système de liaison de modèle exclut toutes les propriétés définies par le type. Voici un exemple :
public class InstructorBindNever
{
[BindNever]
public int Id { get; set; }
// ...
}
Collections
Pour les cibles qui sont des collections de types simples, la liaison de modèle recherche les correspondances avec nom_paramètre ou nom_propriété. Si aucune correspondance n’est localisée, elle recherche l’un des formats pris en charge sans le préfixe. Par exemple :
Supposons que le paramètre à lier soit un tableau nommé
selectedCourses
:public IActionResult OnPost(int? id, int[] selectedCourses)
Les données de formulaire ou de chaîne de requête peuvent avoir l’un des formats suivants :
selectedCourses=1050&selectedCourses=2000
selectedCourses[0]=1050&selectedCourses[1]=2000
[0]=1050&[1]=2000
selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
[a]=1050&[b]=2000&index=a&index=b
Évitez de lier un paramètre ou une propriété nommée
index
ouIndex
adjacent(e) à une valeur de collection. La liaison de modèle tente d’utiliserindex
comme index pour la collection, ce qui peut entraîner une liaison incorrecte. Prenons par exemple l’action suivante :public IActionResult Post(string index, List<Product> products)
Dans le code précédent, le paramètre de chaîne de requête
index
se lie au paramètre de méthodeindex
et est également utilisé pour lier la collection de produits. Le changement de nom du paramètreindex
ou l’utilisation d’un attribut de liaison de modèle pour configurer la liaison évite ce problème :public IActionResult Post(string productIndex, List<Product> products)
Le format suivant est pris en charge uniquement dans les données de formulaire :
selectedCourses[]=1050&selectedCourses[]=2000
Pour tous les exemples de formats précédents, la liaison de modèle passe un tableau de deux éléments au paramètre
selectedCourses
:- selectedCourses[0]=1050
- selectedCourses[1]=2000
Les formats de données qui utilisent des nombres en indice (... [0] ... [1] ...) doivent être impérativement numérotés de manière séquentielle à partir de zéro. S’il existe des vides dans la numérotation en indice, tous les éléments suivants sont ignorés. Par exemple, si les indices sont 0 et 2 au lieu de 0 et 1, le second élément est ignoré.
Dictionnaires
Pour les cibles Dictionary
, la liaison de modèle recherche les correspondances avec nom_paramètre ou nom_propriété. Si aucune correspondance n’est localisée, elle recherche l’un des formats pris en charge sans le préfixe. Par exemple :
Supposons que le paramètre cible soit un
Dictionary<int, string>
nomméselectedCourses
:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
Les données de chaîne de requête ou de formulaire posté peuvent ressembler à l’un des exemples suivants :
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
[1050]=Chemistry&selectedCourses[2000]=Economics
selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
Pour tous les exemples de formats précédents, la liaison de modèle passe un dictionnaire de deux éléments au paramètre
selectedCourses
:- selectedCourses["1050"]="Chemistry"
- selectedCourses["2000"]="Economics"
Liaison de constructeur et types d’enregistrements
La liaison de modèle nécessite que les types complexes aient un constructeur sans paramètre. Les formateurs d’entrée basés sur System.Text.Json
et Newtonsoft.Json
prennent en charge la désérialisation des classes qui n’ont pas de constructeur sans paramètre.
Les types d’enregistrements sont un excellent moyen de représenter succinctement des données sur le réseau. ASP.NET Core prend en charge la liaison de modèle et la validation des types d’enregistrements avec un seul constructeur :
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>
Lors de la validation des types d’enregistrements, le CLR recherche les métadonnées de liaison et de validation spécifiquement sur les paramètres plutôt que sur les propriétés.
L’infrastructure permet de lier et de valider les types d’enregistrements :
public record Person([Required] string Name, [Range(0, 100)] int Age);
Pour que le précédent fonctionne, le type doit :
- Etre un type d’enregistrement.
- Avoir exactement un constructeur public.
- Contenir des paramètres qui ont une propriété portant le même nom et le même type. Les noms ne doivent pas différer par cas.
Types OCT sans constructeurs sans paramètre
Les types OCT qui n’ont pas de constructeurs sans paramètre ne peuvent pas être liés.
Le code suivant génère une exception indiquant que le type doit avoir un constructeur sans paramètre :
public class Person(string Name)
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0);
}
Types d’enregistrements avec des constructeurs créés manuellement
Types d’enregistrement avec des constructeurs créés manuellement qui ressemblent au travail des constructeurs principaux
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; }
}
Types d’enregistrements, métadonnées de validation et de liaison
Pour les types d’enregistrements, la validation et la liaison des métadonnées sur les paramètres est utilisée. Toutes les métadonnées sur les propriétés sont ignorées
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; }
}
Validation et métadonnées
La validation utilise des métadonnées sur le paramètre, mais utilise la propriété pour lire la valeur. Dans le cas ordinaire des constructeurs principaux, les deux sont identiques. Toutefois, il existe des moyens de le contourner :
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 ne met pas à jour les paramètres sur un type d’enregistrement
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
Dans ce cas, MVC ne tente pas de lier Name
à nouveau. Toutefois, Age
est autorisé à être mis à jour
Comportement de globalisation des données de routage et des chaînes de requête de liaison de modèle
Le fournisseur de valeur de routage ASP.NET et de valeur de chaîne de requête :
- Traite les valeurs comme une culture invariante.
- S’attend à ce que les URL soient de culture invariante.
En revanche, les valeurs provenant des données de formulaire subissent une conversion sensible à la culture. Cela est conçu pour que les URL soient partageables entre les paramètres régionaux.
Pour que le fournisseur de valeur de routage et le fournisseur de valeur de chaîne de requête ASP.NET Core subissent une conversion sensible à la culture :
- Héritent de IValueProviderFactory
- Copiez le code à partir de QueryStringValueProviderFactory ou RouteValueValueProviderFactory
- Remplacez la valeur de culture passée au constructeur du fournisseur de valeurs par CultureInfo.CurrentCulture
- Remplacez la fabrique de fournisseur de valeurs par défaut dans les options MVC par la nouvelle :
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();
});
Types de données spéciaux
Certains types de données spéciaux peuvent être pris en charge par la liaison de modèle.
IFormFile et IFormFileCollection
Fichier chargé inclus dans la requête HTTP. IEnumerable<IFormFile>
est également pris en charge pour plusieurs fichiers.
CancellationToken
Les actions peuvent éventuellement lier un CancellationToken
en tant que paramètre. Cette liaison de RequestAborted indique quand la connexion sous-jacente à la requête HTTP est abandonnée. Les actions peuvent utiliser ce paramètre pour annuler les opérations asynchrones durables exécutées dans le cadre des actions du contrôleur.
FormCollection
Permet de récupérer toutes les valeurs des données de formulaire posté.
Formateurs d’entrée
Les données contenues dans le corps de la requête peuvent être au format JSON, XML ou tout autre format. Pour analyser ces données, la liaison de modèle utilise un formateur d’entrée configuré pour prendre en charge un type de contenu particulier. Par défaut, ASP.NET Core inclut des formateurs d’entrée basés sur le format JSON pour prendre en charge les données JSON. Vous pouvez ajouter d’autres formateurs pour d’autres types de contenu.
ASP.NET Core sélectionne les formateurs d’entrée en fonction de l’attribut Consumes. Si aucun attribut n’est présent, il utilise l’en-tête Content-Type.
Pour utiliser les formateurs d’entrée XML intégrés :
Dans
Program.cs
, appelez AddXmlSerializerFormatters ou AddXmlDataContractSerializerFormatters.builder.Services.AddControllers() .AddXmlSerializerFormatters();
Appliquez l’attribut
Consumes
aux classes de contrôleur ou aux méthodes d’action devant contenir des données XML dans le corps de la requête.[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)
Pour plus d’informations, consultez Introduction à la sérialisation XML.
Personnaliser la liaison de modèle avec des formateurs d’entrée
Un formateur d’entrée assume l’entière responsabilité de la lecture des données du corps de la requête. Pour personnaliser ce processus, configurez les API utilisées par le formateur d’entrée. Cette section explique comment personnaliser le formateur d’entrée basé sur System.Text.Json
pour comprendre un type personnalisé nommé ObjectId
.
Considérez le modèle suivant, qui contient une propriété ObjectId
personnalisée :
public class InstructorObjectId
{
[Required]
public ObjectId ObjectId { get; set; } = null!;
}
Pour personnaliser le processus de liaison de modèle lors de l’utilisation deSystem.Text.Json
, créez une classe dérivée 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);
}
Pour utiliser un convertisseur personnalisé, appliquez l’attribut JsonConverterAttribute au type . Dans l’exemple suivant, le type ObjectId
est configuré avec ObjectIdConverter
comme convertisseur personnalisé :
[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);
Pour plus d’informations, consultez Comment écrire des convertisseurs personnalisés.
Exclure les types spécifiés de la liaison de modèle
Le comportement de la liaison de modèle et du système de validation est régi par ModelMetadata. Vous pouvez personnaliser ModelMetadata
en ajoutant un fournisseur de détails à MvcOptions.ModelMetadataDetailsProviders. Des fournisseurs de détails intégrés sont disponibles pour désactiver la liaison de modèle ou la validation des types spécifiés.
Pour désactiver la liaison de modèle sur tous les modèles d’un type spécifique, ajoutez ExcludeBindingMetadataProvider dans Program.cs
. Par exemple, pour désactiver la liaison de modèle sur tous les modèles de type System.Version
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
Pour désactiver la validation des propriétés d’un type spécifique, ajoutez SuppressChildValidationMetadataProvider dans Program.cs
. Par exemple, pour désactiver la validation sur les propriétés de type System.Guid
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
Lieurs de modèles personnalisés
Vous pouvez étendre la liaison de modèle en écrivant un lieur de modèle personnalisé et en utilisant l’attribut [ModelBinder]
afin de le sélectionner pour une cible donnée. Découvrez plus d’informations sur la liaison de modèle personnalisée.
Liaison de modèle manuelle
Vous pouvez appeler la liaison de modèle manuellement à l’aide de la méthode TryUpdateModelAsync. La méthode est définie sur les classes ControllerBase
et PageModel
. Les surcharges de méthode vous permettent de spécifier le préfixe et le fournisseur de valeurs à utiliser. La méthode retourne false
en cas d’échec de la liaison de modèle. Voici un exemple :
if (await TryUpdateModelAsync(
newInstructor,
"Instructor",
x => x.Name, x => x.HireDate!))
{
_instructorStore.Add(newInstructor);
return RedirectToPage("./Index");
}
return Page();
TryUpdateModelAsync utilise des fournisseurs de valeurs pour obtenir des données à partir du corps du formulaire, de la chaîne de requête et des données de routage. TryUpdateModelAsync
est généralement :
- Utilisé avec les applications RazorPages et MVC à l’aide de contrôleurs et de vues pour empêcher la publication excessive.
- Non utilisé avec une API web, sauf si consommé à partir de données de formulaire, de chaînes de requête et de données de routage. Les points de terminaison d’API web qui utilisent JSON utilisent des formateurs d’entrée pour désérialiser le corps de la demande en un objet.
Pour plus d’informations, consultez TryUpdateModelAsync.
Attribut [FromServices]
Le nom de cet attribut suit le modèle des attributs de liaison de modèle qui spécifient une source de données. Toutefois, il ne permet pas de lier les données d’un fournisseur de valeurs. Il obtient une instance d’un type à partir du conteneur d’injection de dépendances. Son objectif est de fournir une alternative à l’injection de constructeurs quand vous avez besoin d’un service uniquement si une méthode particulière est appelée.
Si une instance du type n’est pas inscrite dans le conteneur d’injection de dépendances, l’application lève une exception lors de la tentative de liaison du paramètre. Pour rendre le paramètre facultatif, utilisez l’une des approches suivantes :
- Transformez le paramètre en paramètre pouvant accepter la valeur Null.
- Définissez une valeur par défaut pour le paramètre .
Pour les paramètres pouvant accepter le paramètre Null, vérifiez que le paramètre n’est pas null
avant d’y accéder.
Ressources supplémentaires
Cet article explique ce qu’est la liaison de modèle, comment elle fonctionne et comment personnaliser son comportement.
Description de la liaison de modèle
Les contrôleurs et Razor Pages utilisent des données provenant de requêtes HTTP. Par exemple, les données de routage peuvent fournir une clé d’enregistrement, et les champs de formulaire posté peuvent fournir des valeurs pour les propriétés du modèle. L’écriture du code permettant de récupérer chacune de ces valeurs et de les convertir en types .NET à partir de chaînes est fastidieuse et source d’erreurs. La liaison de modèle automatise ce processus. Le système de liaison de modèle :
- Récupère les données de diverses sources telles que les données de routage, les champs de formulaire et les chaînes de requête
- Fournit les données aux contrôleurs et à Razor Pages dans les paramètres de méthode et les propriétés publiques.
- Convertit les données de chaîne en types .NET
- Met à jour les propriétés des types complexes
Exemple
Supposons que vous ayez la méthode d’action suivante :
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
Et que l’application reçoive une requête avec l’URL suivante :
https://contoso.com/api/pets/2?DogsOnly=true
La liaison de modèle suit les étapes ci-après une fois que le système de routage a sélectionné la méthode d’action :
- Elle recherche le premier paramètre de
GetById
, un entier nomméid
. - Elle parcourt les sources disponibles dans la requête HTTP et trouve
id
= « 2 » dans les données de routage. - Elle convertit la chaîne « 2 » en entier 2.
- Elle recherche le paramètre suivant de
GetById
, un booléen nommédogsOnly
. - Elle parcourt les sources et trouve « DogsOnly=true » dans la chaîne de requête. La mise en correspondance des noms ne respecte pas la casse.
- Elle convertit la chaîne « true » en booléen
true
.
Le framework appelle ensuite la méthode GetById
, en passant 2 pour le paramètre id
, et true
pour le paramètre dogsOnly
.
Dans l’exemple précédent, les cibles de liaison de modèle sont des paramètres de méthode qui sont des types simples. Les cibles peuvent être également les propriétés d’un type complexe. Une fois chaque propriété correctement liée, la validation du modèle est effectuée pour la propriété concernée. L’enregistrement des données liées au modèle (ainsi que des erreurs de liaison ou de validation) est stocké dans ControllerBase.ModelState ou PageModel.ModelState. Pour savoir si ce processus a abouti, l’application vérifie l’indicateur ModelState.IsValid.
Targets
La liaison de modèle tente de trouver des valeurs pour les genres de cible suivants :
- Paramètres de la méthode d’action de contrôleur vers laquelle une requête est routée.
- Paramètres de la méthode de gestionnaire Razor Pages vers laquelle une requête est routée.
- Propriétés publiques d’un contrôleur ou d’une classe
PageModel
, si elles sont spécifiées par des attributs.
Attribut [BindProperty]
Peut être appliqué à une propriété publique d’un contrôleur ou à une classe PageModel
pour que la liaison de modèle cible cette propriété :
public class EditModel : PageModel
{
[BindProperty]
public Instructor? Instructor { get; set; }
// ...
}
Attribut [BindProperties]
Peut être appliqué à un contrôleur ou à une classe PageModel
pour indiquer à la liaison de modèle de cibler toutes les propriétés publiques de la classe :
[BindProperties]
public class CreateModel : PageModel
{
public Instructor? Instructor { get; set; }
// ...
}
Liaison de modèle pour les requêtes HTTP GET
Par défaut, les propriétés ne sont pas liées pour les requêtes HTTP GET. En règle générale, le paramètre ID d’un enregistrement est tout ce dont vous avez besoin pour une requête GET. L’ID d’enregistrement est utilisé pour rechercher l’élément dans la base de données. Il n’est donc pas nécessaire de lier une propriété qui contient une instance du modèle. Pour les scénarios dans lesquels vous souhaitez que les propriétés soient liées aux données provenant de requêtes GET, affectez à la propriété SupportsGet
la valeur true
:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }
Sources
Par défaut, la liaison de modèle obtient des données sous la forme de paires clé-valeur à partir des sources suivantes dans une requête HTTP :
- Champs de formulaire
- Corps de la requête (pour les contrôleurs ayant l’attribut [ApiController].)
- Données de routage
- Paramètres de chaîne de requête
- Fichiers chargés
Pour chaque paramètre ou propriété cible, les sources sont analysées dans l’ordre indiqué dans cette liste. Il existe quelques exceptions :
- Les données de routage et les valeurs de chaîne de requête sont utilisées uniquement pour les types simples.
- Les fichiers chargés sont liés uniquement aux types cibles qui implémentent
IFormFile
ouIEnumerable<IFormFile>
.
Si la source par défaut n’est pas correcte, utilisez l’un des attributs suivants pour spécifier la source :
[FromQuery]
- Obtient les valeurs à partir de la chaîne de requête.[FromRoute]
- Obtient les valeurs à partir des données de routage.[FromForm]
- Obtient les valeurs à partir des champs de formulaire postés.[FromBody]
- Obtient les valeurs à partir du corps de la requête.[FromHeader]
- Obtient les valeurs à partir des en-têtes HTTP.
Ces attributs :
Sont ajoutés aux propriétés du modèle individuellement (et non à la classe de modèle), comme dans l’exemple suivant :
public class Instructor { public int Id { get; set; } [FromQuery(Name = "Note")] public string? NoteFromQueryString { get; set; } // ... }
Acceptent éventuellement une valeur de nom de modèle dans le constructeur. Cette option est fournie au cas où le nom de propriété ne correspondrait pas à la valeur de la requête. Par exemple, la valeur de la requête peut être un en-tête avec un trait d’union dans son nom, comme dans l’exemple suivant :
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
Attribut [FromBody]
Appliquez l’attribut [FromBody]
à un paramètre pour remplir ses propriétés à partir du corps d’une requête HTTP. Le runtime ASP.NET Core délègue la responsabilité de la lecture du flux de requête au formateur d’entrée. Les formateurs d’entrée sont décrits plus loin dans cet article.
Lorsque [FromBody]
est appliqué à un paramètre de type complexe, tous les attributs de source de liaison appliqués à ses propriétés sont ignorés. Par exemple, l’action suivante Create
spécifie que son paramètre pet
est rempli à partir du corps :
public ActionResult<Pet> Create([FromBody] Pet pet)
La classe Pet
spécifie que sa propriété Breed
est remplie à partir d’un paramètre de chaîne de requête :
public class Pet
{
public string Name { get; set; } = null!;
[FromQuery] // Attribute is ignored.
public string Breed { get; set; } = null!;
}
Dans l'exemple précédent :
- L'attribut
[FromQuery]
est ignoré. - La propriété
Breed
n’est pas remplie à partir d’un paramètre de chaîne de requête.
Les formateurs d’entrée lisent uniquement le corps et ne comprennent pas les attributs de la source de liaison. Si une valeur appropriée est trouvée dans le corps, cette valeur est utilisée pour remplir la propriété Breed
.
N’appliquez pas [FromBody]
à plus d’un paramètre par méthode d’action. Une fois le flux de requête lu par un formateur d’entrée, il ne peut plus être relu pour lier d’autres paramètres [FromBody]
.
Sources supplémentaires
Les données sources sont fournies au système de liaison de modèle par les fournisseurs de valeurs. Vous pouvez écrire et inscrire des fournisseurs de valeurs personnalisés qui obtiennent des données de liaison de modèle à partir d’autres sources. Par exemple, vous pouvez obtenir des données provenant de cookies ou de l’état de session. Pour obtenir des données provenant d’une nouvelle source :
- Créez une classe qui implémente
IValueProvider
. - Créez une classe qui implémente
IValueProviderFactory
. - Inscrivez la classe de fabrique dans
Program.cs
.
L’exemple comprend un exemple de fournisseur de valeurs et un exemple de fabrique, qui permet de récupérer les valeurs provenant de cookies. Inscrire des fabriques de fournisseurs de valeurs personnalisées dans Program.cs
:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
Le code précédent place le fournisseur de valeurs personnalisé après tous les fournisseurs de valeurs intégrés. Pour en faire le premier fournisseur de la liste, appelez Insert(0, new CookieValueProviderFactory())
à la place de Add
.
Aucune source pour une propriété de modèle
Par défaut, aucune erreur d’état de modèle n’est créée, s’il n’existe aucune valeur de propriété de modèle. La propriété a une valeur null ou une valeur par défaut :
- Les types simples Nullable ont une valeur
null
. - Les types valeur non Nullable ont la valeur
default(T)
. Par exemple, un paramètreint id
a la valeur 0. - Pour les types complexes, la liaison de modèle crée une instance à l’aide du constructeur par défaut, sans définir de propriétés.
- Les tableaux ont la valeur
Array.Empty<T>()
, sauf les tableauxbyte[]
qui ont une valeurnull
.
Si l’état de modèle doit être invalidé quand rien n’est trouvé dans les champs de formulaire d’une propriété de modèle, utilisez l’attribut [BindRequired]
.
Notez que ce comportement de [BindRequired]
s’applique à la liaison de modèle des données de formulaire postées, et non aux données JSON ou XML d’un corps de requête. Les données du corps de requête sont prises en charge par les formateurs d’entrée.
Erreurs de conversion de type
Si une source est localisée mais qu’elle ne peut pas être convertie vers le type cible, l’état du modèle est marqué comme étant non valide. Le paramètre ou la propriété cible a une valeur null ou une valeur par défaut, comme indiqué dans la section précédente.
Dans un contrôleur d’API ayant l’attribut [ApiController]
, un état de modèle non valide entraîne une réponse HTTP 400 automatique.
Dans une page Razor, réaffichez la page avec un message d’erreur :
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
// ...
return RedirectToPage("./Index");
}
Quand la page est réaffichée par le code précédent, l’entrée non valide n’est pas visible dans le champ de formulaire. En effet, la propriété de modèle à une valeur null ou une valeur par défaut. L’entrée non valide apparaît dans un message d’erreur. Toutefois, si vous souhaitez réafficher les données incorrectes dans le champ de formulaire, transformez la propriété de modèle en chaîne et procédez à la conversion des données manuellement.
La même stratégie est recommandée si vous ne souhaitez pas que les erreurs de conversion de type entraînent des erreurs d’état de modèle. Dans ce cas, transformez la propriété de modèle en chaîne.
Types simples
Les types simples que le lieur de modèle peut convertir en chaînes sources sont les suivants :
- Booléen
- Byte, SByte
- Char
- DateTime
- DateTimeOffset
- Décimal
- Double
- Enum
- GUID
- Int16, Int32, Int64
- Unique
- TimeSpan
- UInt16, UInt32, UInt64
- Uri
- Version
Types complexes
Un type complexe doit avoir un constructeur public par défaut et des propriétés publiques accessibles en écriture à lier. Quand la liaison de modèle se produit, la classe est instanciée à l’aide du constructeur public par défaut.
Pour chaque propriété du type complexe, la liaison de modèle recherche dans les sources le modèle de nom préfixe.nom_propriété. Si rien n’est trouvé, elle recherche uniquement nom_propriété sans le préfixe. La décision d’utiliser le préfixe n’est pas prise par propriété. Par exemple, avec une requête contenant ?Instructor.Id=100&Name=foo
, liée à la méthode OnGet(Instructor instructor)
, l’objet résultant de type Instructor
contient :
Id
défini sur100
.Name
défini surnull
. La liaison de modèle s’attend àInstructor.Name
carInstructor.Id
a été utilisé dans le paramètre de requête précédent.
Dans le cas d’une liaison à un paramètre, le préfixe représente le nom du paramètre. Dans le cas d’une liaison à une propriété publique PageModel
, le préfixe représente le nom de la propriété publique. Certains attributs ont une propriété Prefix
qui vous permet de remplacer l’utilisation par défaut du nom de paramètre ou de propriété.
Par exemple, supposons que le type complexe corresponde à la classe Instructor
suivante :
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
Préfixe = nom de paramètre
Si le modèle à lier est un paramètre nommé instructorToUpdate
:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
La liaison de modèle commence par rechercher dans les sources la clé instructorToUpdate.ID
. Si elle est introuvable, elle recherche ID
sans préfixe.
Préfixe = nom de propriété
Si le modèle à lier est une propriété nommée Instructor
du contrôleur ou de la classe PageModel
:
[BindProperty]
public Instructor Instructor { get; set; }
La liaison de modèle commence par rechercher dans les sources la clé Instructor.ID
. Si elle est introuvable, elle recherche ID
sans préfixe.
Préfixe personnalisé
Si le modèle à lier est un paramètre nommé instructorToUpdate
et si un attribut Bind
spécifie Instructor
en tant que préfixe :
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
La liaison de modèle commence par rechercher dans les sources la clé Instructor.ID
. Si elle est introuvable, elle recherche ID
sans préfixe.
Attributs des cibles de type complexe
Plusieurs attributs intégrés sont disponibles pour contrôler la liaison de modèle des types complexes :
Avertissement
Ces attributs affectent la liaison de modèle quand les données de formulaire postées représentent la source des valeurs. Ils n’affectent pas les formateurs d’entrée, qui traitent les corps de requête JSON et XML postés. Les formateurs d’entrée sont décrits plus loin dans cet article.
Attribut [Bind]
Il peut être appliqué à une classe ou à un paramètre de méthode. Il spécifie les propriétés d’un modèle à inclure dans la liaison de modèle. [Bind]
n’affectepas les formateurs d’entrée.
Dans l’exemple suivant, seules les propriétés spécifiées du modèle Instructor
sont liées quand une méthode de gestionnaire ou une méthode d’action est appelée :
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
Dans l’exemple suivant, seules les propriétés spécifiées du modèle Instructor
sont liées quand la méthode OnPost
est appelée :
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
Vous pouvez utiliser l’attribut [Bind]
pour éviter le surpostage dans les scénarios de création. Il ne fonctionne pas bien dans les scénarios de modification, car les propriétés exclues ont une valeur null ou une valeur par défaut au lieu de rester inchangées. Pour empêcher le surpostage, il est recommandé d’utiliser des modèles de vues à la place de l’attribut [Bind]
. Pour plus d’informations, consultez Remarque sur la sécurité concernant le surpostage.
Attribut [ModelBinder]
ModelBinderAttribute peut être appliqué à des types, des propriétés ou des paramètres. Il permet de spécifier le type de classeur de modèle utilisé pour lier l’instance ou le type spécifique. Par exemple :
[HttpPost]
public IActionResult OnPost(
[ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
L’attribut [ModelBinder]
peut également être utilisé pour modifier le nom d’une propriété ou d’un paramètre lorsqu’il est lié au modèle :
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
// ...
}
Attribut [BindRequired]
Il oblige la liaison de modèle à ajouter une erreur d’état de modèle si la liaison est impossible pour la propriété d’un modèle. Voici un exemple :
public class InstructorBindRequired
{
// ...
[BindRequired]
public DateTime HireDate { get; set; }
}
Consultez également la discussion sur l’attribut [Required]
dans Validation de modèle.
Attribut [BindNever]
Peut être appliqué à une propriété ou à un type. Il empêche la liaison de modèle de définir la propriété d’un modèle. Lorsqu’il est appliqué à un type, le système de liaison de modèle exclut toutes les propriétés définies par le type. Voici un exemple :
public class InstructorBindNever
{
[BindNever]
public int Id { get; set; }
// ...
}
Collections
Pour les cibles qui sont des collections de types simples, la liaison de modèle recherche les correspondances avec nom_paramètre ou nom_propriété. Si aucune correspondance n’est localisée, elle recherche l’un des formats pris en charge sans le préfixe. Par exemple :
Supposons que le paramètre à lier soit un tableau nommé
selectedCourses
:public IActionResult OnPost(int? id, int[] selectedCourses)
Les données de formulaire ou de chaîne de requête peuvent avoir l’un des formats suivants :
selectedCourses=1050&selectedCourses=2000
selectedCourses[0]=1050&selectedCourses[1]=2000
[0]=1050&[1]=2000
selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
[a]=1050&[b]=2000&index=a&index=b
Évitez de lier un paramètre ou une propriété nommée
index
ouIndex
adjacent(e) à une valeur de collection. La liaison de modèle tente d’utiliserindex
comme index pour la collection, ce qui peut entraîner une liaison incorrecte. Prenons par exemple l’action suivante :public IActionResult Post(string index, List<Product> products)
Dans le code précédent, le paramètre de chaîne de requête
index
se lie au paramètre de méthodeindex
et est également utilisé pour lier la collection de produits. Le changement de nom du paramètreindex
ou l’utilisation d’un attribut de liaison de modèle pour configurer la liaison évite ce problème :public IActionResult Post(string productIndex, List<Product> products)
Le format suivant est pris en charge uniquement dans les données de formulaire :
selectedCourses[]=1050&selectedCourses[]=2000
Pour tous les exemples de formats précédents, la liaison de modèle passe un tableau de deux éléments au paramètre
selectedCourses
:- selectedCourses[0]=1050
- selectedCourses[1]=2000
Les formats de données qui utilisent des nombres en indice (... [0] ... [1] ...) doivent être impérativement numérotés de manière séquentielle à partir de zéro. S’il existe des vides dans la numérotation en indice, tous les éléments suivants sont ignorés. Par exemple, si les indices sont 0 et 2 au lieu de 0 et 1, le second élément est ignoré.
Dictionnaires
Pour les cibles Dictionary
, la liaison de modèle recherche les correspondances avec nom_paramètre ou nom_propriété. Si aucune correspondance n’est localisée, elle recherche l’un des formats pris en charge sans le préfixe. Par exemple :
Supposons que le paramètre cible soit un
Dictionary<int, string>
nomméselectedCourses
:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
Les données de chaîne de requête ou de formulaire posté peuvent ressembler à l’un des exemples suivants :
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
[1050]=Chemistry&selectedCourses[2000]=Economics
selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
Pour tous les exemples de formats précédents, la liaison de modèle passe un dictionnaire de deux éléments au paramètre
selectedCourses
:- selectedCourses["1050"]="Chemistry"
- selectedCourses["2000"]="Economics"
Liaison de constructeur et types d’enregistrements
La liaison de modèle nécessite que les types complexes aient un constructeur sans paramètre. Les formateurs d’entrée basés sur System.Text.Json
et Newtonsoft.Json
prennent en charge la désérialisation des classes qui n’ont pas de constructeur sans paramètre.
Les types d’enregistrements sont un excellent moyen de représenter succinctement des données sur le réseau. ASP.NET Core prend en charge la liaison de modèle et la validation des types d’enregistrements avec un seul constructeur :
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>
Lors de la validation des types d’enregistrements, le CLR recherche les métadonnées de liaison et de validation spécifiquement sur les paramètres plutôt que sur les propriétés.
L’infrastructure permet de lier et de valider les types d’enregistrements :
public record Person([Required] string Name, [Range(0, 100)] int Age);
Pour que le précédent fonctionne, le type doit :
- Etre un type d’enregistrement.
- Avoir exactement un constructeur public.
- Contenir des paramètres qui ont une propriété portant le même nom et le même type. Les noms ne doivent pas différer par cas.
Types OCT sans constructeurs sans paramètre
Les types OCT qui n’ont pas de constructeurs sans paramètre ne peuvent pas être liés.
Le code suivant génère une exception indiquant que le type doit avoir un constructeur sans paramètre :
public class Person(string Name)
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0);
}
Types d’enregistrements avec des constructeurs créés manuellement
Types d’enregistrement avec des constructeurs créés manuellement qui ressemblent au travail des constructeurs principaux
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; }
}
Types d’enregistrements, métadonnées de validation et de liaison
Pour les types d’enregistrements, la validation et la liaison des métadonnées sur les paramètres est utilisée. Toutes les métadonnées sur les propriétés sont ignorées
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; }
}
Validation et métadonnées
La validation utilise des métadonnées sur le paramètre, mais utilise la propriété pour lire la valeur. Dans le cas ordinaire des constructeurs principaux, les deux sont identiques. Toutefois, il existe des moyens de le contourner :
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 ne met pas à jour les paramètres sur un type d’enregistrement
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
Dans ce cas, MVC ne tente pas de lier Name
à nouveau. Toutefois, Age
est autorisé à être mis à jour
Comportement de globalisation des données de routage et des chaînes de requête de liaison de modèle
Le fournisseur de valeur de routage ASP.NET et de valeur de chaîne de requête :
- Traite les valeurs comme une culture invariante.
- S’attend à ce que les URL soient de culture invariante.
En revanche, les valeurs provenant des données de formulaire subissent une conversion sensible à la culture. Cela est conçu pour que les URL soient partageables entre les paramètres régionaux.
Pour que le fournisseur de valeur de routage et le fournisseur de valeur de chaîne de requête ASP.NET Core subissent une conversion sensible à la culture :
- Héritent de IValueProviderFactory
- Copiez le code à partir de QueryStringValueProviderFactory ou RouteValueValueProviderFactory
- Remplacez la valeur de culture passée au constructeur du fournisseur de valeurs par CultureInfo.CurrentCulture
- Remplacez la fabrique de fournisseur de valeurs par défaut dans les options MVC par la nouvelle :
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();
});
Types de données spéciaux
Certains types de données spéciaux peuvent être pris en charge par la liaison de modèle.
IFormFile et IFormFileCollection
Fichier chargé inclus dans la requête HTTP. IEnumerable<IFormFile>
est également pris en charge pour plusieurs fichiers.
CancellationToken
Les actions peuvent éventuellement lier un CancellationToken
en tant que paramètre. Cette liaison de RequestAborted indique quand la connexion sous-jacente à la requête HTTP est abandonnée. Les actions peuvent utiliser ce paramètre pour annuler les opérations asynchrones durables exécutées dans le cadre des actions du contrôleur.
FormCollection
Permet de récupérer toutes les valeurs des données de formulaire posté.
Formateurs d’entrée
Les données contenues dans le corps de la requête peuvent être au format JSON, XML ou tout autre format. Pour analyser ces données, la liaison de modèle utilise un formateur d’entrée configuré pour prendre en charge un type de contenu particulier. Par défaut, ASP.NET Core inclut des formateurs d’entrée basés sur le format JSON pour prendre en charge les données JSON. Vous pouvez ajouter d’autres formateurs pour d’autres types de contenu.
ASP.NET Core sélectionne les formateurs d’entrée en fonction de l’attribut Consumes. Si aucun attribut n’est présent, il utilise l’en-tête Content-Type.
Pour utiliser les formateurs d’entrée XML intégrés :
Dans
Program.cs
, appelez AddXmlSerializerFormatters ou AddXmlDataContractSerializerFormatters.builder.Services.AddControllers() .AddXmlSerializerFormatters();
Appliquez l’attribut
Consumes
aux classes de contrôleur ou aux méthodes d’action devant contenir des données XML dans le corps de la requête.[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)
Pour plus d’informations, consultez Introduction à la sérialisation XML.
Personnaliser la liaison de modèle avec des formateurs d’entrée
Un formateur d’entrée assume l’entière responsabilité de la lecture des données du corps de la requête. Pour personnaliser ce processus, configurez les API utilisées par le formateur d’entrée. Cette section explique comment personnaliser le formateur d’entrée basé sur System.Text.Json
pour comprendre un type personnalisé nommé ObjectId
.
Considérez le modèle suivant, qui contient une propriété ObjectId
personnalisée :
public class InstructorObjectId
{
[Required]
public ObjectId ObjectId { get; set; } = null!;
}
Pour personnaliser le processus de liaison de modèle lors de l’utilisation deSystem.Text.Json
, créez une classe dérivée 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);
}
Pour utiliser un convertisseur personnalisé, appliquez l’attribut JsonConverterAttribute au type . Dans l’exemple suivant, le type ObjectId
est configuré avec ObjectIdConverter
comme convertisseur personnalisé :
[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);
Pour plus d’informations, consultez Comment écrire des convertisseurs personnalisés.
Exclure les types spécifiés de la liaison de modèle
Le comportement de la liaison de modèle et du système de validation est régi par ModelMetadata. Vous pouvez personnaliser ModelMetadata
en ajoutant un fournisseur de détails à MvcOptions.ModelMetadataDetailsProviders. Des fournisseurs de détails intégrés sont disponibles pour désactiver la liaison de modèle ou la validation des types spécifiés.
Pour désactiver la liaison de modèle sur tous les modèles d’un type spécifique, ajoutez ExcludeBindingMetadataProvider dans Program.cs
. Par exemple, pour désactiver la liaison de modèle sur tous les modèles de type System.Version
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
Pour désactiver la validation des propriétés d’un type spécifique, ajoutez SuppressChildValidationMetadataProvider dans Program.cs
. Par exemple, pour désactiver la validation sur les propriétés de type System.Guid
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
Lieurs de modèles personnalisés
Vous pouvez étendre la liaison de modèle en écrivant un lieur de modèle personnalisé et en utilisant l’attribut [ModelBinder]
afin de le sélectionner pour une cible donnée. Découvrez plus d’informations sur la liaison de modèle personnalisée.
Liaison de modèle manuelle
Vous pouvez appeler la liaison de modèle manuellement à l’aide de la méthode TryUpdateModelAsync. La méthode est définie sur les classes ControllerBase
et PageModel
. Les surcharges de méthode vous permettent de spécifier le préfixe et le fournisseur de valeurs à utiliser. La méthode retourne false
en cas d’échec de la liaison de modèle. Voici un exemple :
if (await TryUpdateModelAsync(
newInstructor,
"Instructor",
x => x.Name, x => x.HireDate!))
{
_instructorStore.Add(newInstructor);
return RedirectToPage("./Index");
}
return Page();
TryUpdateModelAsync utilise des fournisseurs de valeurs pour obtenir des données à partir du corps du formulaire, de la chaîne de requête et des données de routage. TryUpdateModelAsync
est généralement :
- Utilisé avec les applications RazorPages et MVC à l’aide de contrôleurs et de vues pour empêcher la publication excessive.
- Non utilisé avec une API web, sauf si consommé à partir de données de formulaire, de chaînes de requête et de données de routage. Les points de terminaison d’API web qui utilisent JSON utilisent des formateurs d’entrée pour désérialiser le corps de la demande en un objet.
Pour plus d’informations, consultez TryUpdateModelAsync.
Attribut [FromServices]
Le nom de cet attribut suit le modèle des attributs de liaison de modèle qui spécifient une source de données. Toutefois, il ne permet pas de lier les données d’un fournisseur de valeurs. Il obtient une instance d’un type à partir du conteneur d’injection de dépendances. Son objectif est de fournir une alternative à l’injection de constructeurs quand vous avez besoin d’un service uniquement si une méthode particulière est appelée.
Si une instance du type n’est pas inscrite dans le conteneur d’injection de dépendances, l’application lève une exception lors de la tentative de liaison du paramètre. Pour rendre le paramètre facultatif, utilisez l’une des approches suivantes :
- Transformez le paramètre en paramètre pouvant accepter la valeur Null.
- Définissez une valeur par défaut pour le paramètre .
Pour les paramètres pouvant accepter le paramètre Null, vérifiez que le paramètre n’est pas null
avant d’y accéder.
Ressources supplémentaires
Cet article explique ce qu’est la liaison de modèle, comment elle fonctionne et comment personnaliser son comportement.
Affichez ou téléchargez un exemple de code (procédure de téléchargement).
Description de la liaison de modèle
Les contrôleurs et Razor Pages utilisent des données provenant de requêtes HTTP. Par exemple, les données de routage peuvent fournir une clé d’enregistrement, et les champs de formulaire posté peuvent fournir des valeurs pour les propriétés du modèle. L’écriture du code permettant de récupérer chacune de ces valeurs et de les convertir en types .NET à partir de chaînes est fastidieuse et source d’erreurs. La liaison de modèle automatise ce processus. Le système de liaison de modèle :
- Récupère les données de diverses sources telles que les données de routage, les champs de formulaire et les chaînes de requête
- Fournit les données aux contrôleurs et à Razor Pages dans les paramètres de méthode et les propriétés publiques.
- Convertit les données de chaîne en types .NET
- Met à jour les propriétés des types complexes
Exemple
Supposons que vous ayez la méthode d’action suivante :
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
Et que l’application reçoive une requête avec l’URL suivante :
http://contoso.com/api/pets/2?DogsOnly=true
La liaison de modèle suit les étapes ci-après une fois que le système de routage a sélectionné la méthode d’action :
- Elle recherche le premier paramètre de
GetById
, un entier nomméid
. - Elle parcourt les sources disponibles dans la requête HTTP et trouve
id
= « 2 » dans les données de routage. - Elle convertit la chaîne « 2 » en entier 2.
- Elle recherche le paramètre suivant de
GetById
, un booléen nommédogsOnly
. - Elle parcourt les sources et trouve « DogsOnly=true » dans la chaîne de requête. La mise en correspondance des noms ne respecte pas la casse.
- Elle convertit la chaîne « true » en booléen
true
.
Le framework appelle ensuite la méthode GetById
, en passant 2 pour le paramètre id
, et true
pour le paramètre dogsOnly
.
Dans l’exemple précédent, les cibles de liaison de modèle sont des paramètres de méthode qui sont des types simples. Les cibles peuvent être également les propriétés d’un type complexe. Une fois chaque propriété correctement liée, la validation du modèle est effectuée pour la propriété concernée. L’enregistrement des données liées au modèle (ainsi que des erreurs de liaison ou de validation) est stocké dans ControllerBase.ModelState ou PageModel.ModelState. Pour savoir si ce processus a abouti, l’application vérifie l’indicateur ModelState.IsValid.
Targets
La liaison de modèle tente de trouver des valeurs pour les genres de cible suivants :
- Paramètres de la méthode d’action de contrôleur vers laquelle une requête est routée.
- Paramètres de la méthode de gestionnaire Razor Pages vers laquelle une requête est routée.
- Propriétés publiques d’un contrôleur ou d’une classe
PageModel
, si elles sont spécifiées par des attributs.
Attribut [BindProperty]
Peut être appliqué à une propriété publique d’un contrôleur ou à une classe PageModel
pour que la liaison de modèle cible cette propriété :
public class EditModel : InstructorsPageModel
{
[BindProperty]
public Instructor Instructor { get; set; }
Attribut [BindProperties]
Disponible avec ASP.NET Core 2.1 et les versions ultérieures. Peut être appliqué à un contrôleur ou à une classe PageModel
pour indiquer à la liaison de modèle de cibler toutes les propriétés publiques de la classe :
[BindProperties(SupportsGet = true)]
public class CreateModel : InstructorsPageModel
{
public Instructor Instructor { get; set; }
Liaison de modèle pour les requêtes HTTP GET
Par défaut, les propriétés ne sont pas liées pour les requêtes HTTP GET. En règle générale, le paramètre ID d’un enregistrement est tout ce dont vous avez besoin pour une requête GET. L’ID d’enregistrement est utilisé pour rechercher l’élément dans la base de données. Il n’est donc pas nécessaire de lier une propriété qui contient une instance du modèle. Pour les scénarios dans lesquels vous souhaitez que les propriétés soient liées aux données provenant de requêtes GET, affectez à la propriété SupportsGet
la valeur true
:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string ApplicationInsightsCookie { get; set; }
Sources
Par défaut, la liaison de modèle obtient des données sous la forme de paires clé-valeur à partir des sources suivantes dans une requête HTTP :
- Champs de formulaire
- Corps de la requête (pour les contrôleurs ayant l’attribut [ApiController].)
- Données de routage
- Paramètres de chaîne de requête
- Fichiers chargés
Pour chaque paramètre ou propriété cible, les sources sont analysées dans l’ordre indiqué dans cette liste. Il existe quelques exceptions :
- Les données de routage et les valeurs de chaîne de requête sont utilisées uniquement pour les types simples.
- Les fichiers chargés sont liés uniquement aux types cibles qui implémentent
IFormFile
ouIEnumerable<IFormFile>
.
Si la source par défaut n’est pas correcte, utilisez l’un des attributs suivants pour spécifier la source :
[FromQuery]
- Obtient les valeurs à partir de la chaîne de requête.[FromRoute]
- Obtient les valeurs à partir des données de routage.[FromForm]
- Obtient les valeurs à partir des champs de formulaire postés.[FromBody]
- Obtient les valeurs à partir du corps de la requête.[FromHeader]
- Obtient les valeurs à partir des en-têtes HTTP.
Ces attributs :
Sont ajoutés aux propriétés du modèle individuellement (et non à la classe de modèle), comme dans l’exemple suivant :
public class Instructor { public int ID { get; set; } [FromQuery(Name = "Note")] public string NoteFromQueryString { get; set; }
Acceptent éventuellement une valeur de nom de modèle dans le constructeur. Cette option est fournie au cas où le nom de propriété ne correspondrait pas à la valeur de la requête. Par exemple, la valeur de la requête peut être un en-tête avec un trait d’union dans son nom, comme dans l’exemple suivant :
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
Attribut [FromBody]
Appliquez l’attribut [FromBody]
à un paramètre pour remplir ses propriétés à partir du corps d’une requête HTTP. Le runtime ASP.NET Core délègue la responsabilité de la lecture du flux de requête au formateur d’entrée. Les formateurs d’entrée sont décrits plus loin dans cet article.
Lorsque [FromBody]
est appliqué à un paramètre de type complexe, tous les attributs de source de liaison appliqués à ses propriétés sont ignorés. Par exemple, l’action suivante Create
spécifie que son paramètre pet
est rempli à partir du corps :
public ActionResult<Pet> Create([FromBody] Pet pet)
La classe Pet
spécifie que sa propriété Breed
est remplie à partir d’un paramètre de chaîne de requête :
public class Pet
{
public string Name { get; set; }
[FromQuery] // Attribute is ignored.
public string Breed { get; set; }
}
Dans l'exemple précédent :
- L'attribut
[FromQuery]
est ignoré. - La propriété
Breed
n’est pas remplie à partir d’un paramètre de chaîne de requête.
Les formateurs d’entrée lisent uniquement le corps et ne comprennent pas les attributs de la source de liaison. Si une valeur appropriée est trouvée dans le corps, cette valeur est utilisée pour remplir la propriété Breed
.
N’appliquez pas [FromBody]
à plus d’un paramètre par méthode d’action. Une fois le flux de requête lu par un formateur d’entrée, il ne peut plus être relu pour lier d’autres paramètres [FromBody]
.
Sources supplémentaires
Les données sources sont fournies au système de liaison de modèle par les fournisseurs de valeurs. Vous pouvez écrire et inscrire des fournisseurs de valeurs personnalisés qui obtiennent des données de liaison de modèle à partir d’autres sources. Par exemple, vous pouvez obtenir des données provenant de cookies ou de l’état de session. Pour obtenir des données provenant d’une nouvelle source :
- Créez une classe qui implémente
IValueProvider
. - Créez une classe qui implémente
IValueProviderFactory
. - Inscrivez la classe de fabrique dans
Startup.ConfigureServices
.
L’exemple d’application comprend un exemple de fournisseur de valeurs et de fabrique, qui permet de récupérer les valeurs provenant des cookies. Voici le code d’inscription dans 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();
Le code affiché place le fournisseur de valeurs personnalisé après tous les fournisseurs de valeurs intégrés. Pour en faire le premier fournisseur de la liste, appelez Insert(0, new CookieValueProviderFactory())
à la place de Add
.
Aucune source pour une propriété de modèle
Par défaut, aucune erreur d’état de modèle n’est créée, s’il n’existe aucune valeur de propriété de modèle. La propriété a une valeur null ou une valeur par défaut :
- Les types simples Nullable ont une valeur
null
. - Les types valeur non Nullable ont la valeur
default(T)
. Par exemple, un paramètreint id
a la valeur 0. - Pour les types complexes, la liaison de modèle crée une instance à l’aide du constructeur par défaut, sans définir de propriétés.
- Les tableaux ont la valeur
Array.Empty<T>()
, sauf les tableauxbyte[]
qui ont une valeurnull
.
Si l’état de modèle doit être invalidé quand rien n’est trouvé dans les champs de formulaire d’une propriété de modèle, utilisez l’attribut [BindRequired]
.
Notez que ce comportement de [BindRequired]
s’applique à la liaison de modèle des données de formulaire postées, et non aux données JSON ou XML d’un corps de requête. Les données du corps de requête sont prises en charge par les formateurs d’entrée.
Erreurs de conversion de type
Si une source est localisée mais qu’elle ne peut pas être convertie vers le type cible, l’état du modèle est marqué comme étant non valide. Le paramètre ou la propriété cible a une valeur null ou une valeur par défaut, comme indiqué dans la section précédente.
Dans un contrôleur d’API ayant l’attribut [ApiController]
, un état de modèle non valide entraîne une réponse HTTP 400 automatique.
Dans une page Razor, réaffichez la page avec un message d’erreur :
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
_instructorsInMemoryStore.Add(Instructor);
return RedirectToPage("./Index");
}
La validation côté client intercepte la plupart des données incorrectes qui sont envoyées à un formulaire Razor Pages. Cette validation rend difficile le déclenchement du code en surbrillance indiqué plus haut. L’exemple d’application comprend un bouton Submit with Invalid Date (Envoyer avec une date non valide), qui place les données incorrectes dans le champ Hire Date (Date d’embauche) et envoie le formulaire. Ce bouton montre comment fonctionne le code permettant de réafficher la page quand des erreurs de conversion de données se produisent.
Quand la page est réaffichée par le code précédent, l’entrée non valide n’est pas visible dans le champ de formulaire. En effet, la propriété de modèle à une valeur null ou une valeur par défaut. L’entrée non valide apparaît dans un message d’erreur. Toutefois, si vous souhaitez réafficher les données incorrectes dans le champ de formulaire, transformez la propriété de modèle en chaîne et procédez à la conversion des données manuellement.
La même stratégie est recommandée si vous ne souhaitez pas que les erreurs de conversion de type entraînent des erreurs d’état de modèle. Dans ce cas, transformez la propriété de modèle en chaîne.
Types simples
Les types simples que le lieur de modèle peut convertir en chaînes sources sont les suivants :
- Booléen
- Byte, SByte
- Char
- DateTime
- DateTimeOffset
- Décimal
- Double
- Enum
- GUID
- Int16, Int32, Int64
- Unique
- TimeSpan
- UInt16, UInt32, UInt64
- Uri
- Version
Types complexes
Un type complexe doit avoir un constructeur public par défaut et des propriétés publiques accessibles en écriture à lier. Quand la liaison de modèle se produit, la classe est instanciée à l’aide du constructeur public par défaut.
Pour chaque propriété du type complexe, la liaison de modèle recherche dans les sources le modèle de nom préfixe.nom_propriété. Si rien n’est trouvé, elle recherche uniquement nom_propriété sans le préfixe.
Dans le cas d’une liaison à un paramètre, le préfixe représente le nom du paramètre. Dans le cas d’une liaison à une propriété publique PageModel
, le préfixe représente le nom de la propriété publique. Certains attributs ont une propriété Prefix
qui vous permet de remplacer l’utilisation par défaut du nom de paramètre ou de propriété.
Par exemple, supposons que le type complexe corresponde à la classe Instructor
suivante :
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
Préfixe = nom de paramètre
Si le modèle à lier est un paramètre nommé instructorToUpdate
:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
La liaison de modèle commence par rechercher dans les sources la clé instructorToUpdate.ID
. Si elle est introuvable, elle recherche ID
sans préfixe.
Préfixe = nom de propriété
Si le modèle à lier est une propriété nommée Instructor
du contrôleur ou de la classe PageModel
:
[BindProperty]
public Instructor Instructor { get; set; }
La liaison de modèle commence par rechercher dans les sources la clé Instructor.ID
. Si elle est introuvable, elle recherche ID
sans préfixe.
Préfixe personnalisé
Si le modèle à lier est un paramètre nommé instructorToUpdate
et si un attribut Bind
spécifie Instructor
en tant que préfixe :
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
La liaison de modèle commence par rechercher dans les sources la clé Instructor.ID
. Si elle est introuvable, elle recherche ID
sans préfixe.
Attributs des cibles de type complexe
Plusieurs attributs intégrés sont disponibles pour contrôler la liaison de modèle des types complexes :
[Bind]
[BindRequired]
[BindNever]
Avertissement
Ces attributs affectent la liaison de modèle quand les données de formulaire postées représentent la source des valeurs. Ils n’affectent pas les formateurs d’entrée, qui traitent les corps de requête JSON et XML postés. Les formateurs d’entrée sont décrits plus loin dans cet article.
Attribut [Bind]
Il peut être appliqué à une classe ou à un paramètre de méthode. Il spécifie les propriétés d’un modèle à inclure dans la liaison de modèle. [Bind]
n’affectepas les formateurs d’entrée.
Dans l’exemple suivant, seules les propriétés spécifiées du modèle Instructor
sont liées quand une méthode de gestionnaire ou une méthode d’action est appelée :
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
Dans l’exemple suivant, seules les propriétés spécifiées du modèle Instructor
sont liées quand la méthode OnPost
est appelée :
[HttpPost]
public IActionResult OnPost([Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
Vous pouvez utiliser l’attribut [Bind]
pour éviter le surpostage dans les scénarios de création. Il ne fonctionne pas bien dans les scénarios de modification, car les propriétés exclues ont une valeur null ou une valeur par défaut au lieu de rester inchangées. Pour empêcher le surpostage, il est recommandé d’utiliser des modèles de vues à la place de l’attribut [Bind]
. Pour plus d’informations, consultez Remarque sur la sécurité concernant le surpostage.
Attribut [ModelBinder]
ModelBinderAttribute peut être appliqué à des types, des propriétés ou des paramètres. Il permet de spécifier le type de classeur de modèle utilisé pour lier l’instance ou le type spécifique. Par exemple :
[HttpPost]
public IActionResult OnPost([ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
L’attribut [ModelBinder]
peut également être utilisé pour modifier le nom d’une propriété ou d’un paramètre lorsqu’il est lié au modèle :
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
public string Name { get; set; }
}
Attribut [BindRequired]
Il s’applique uniquement aux propriétés de modèle, pas aux paramètres de méthode. Il oblige la liaison de modèle à ajouter une erreur d’état de modèle si la liaison est impossible pour la propriété d’un modèle. Voici un exemple :
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; }
Consultez également la discussion sur l’attribut [Required]
dans Validation de modèle.
Attribut [BindNever]
Il s’applique uniquement aux propriétés de modèle, pas aux paramètres de méthode. Il empêche la liaison de modèle de définir la propriété d’un modèle. Voici un exemple :
public class InstructorWithDictionary
{
[BindNever]
public int ID { get; set; }
Collections
Pour les cibles qui sont des collections de types simples, la liaison de modèle recherche les correspondances avec nom_paramètre ou nom_propriété. Si aucune correspondance n’est localisée, elle recherche l’un des formats pris en charge sans le préfixe. Par exemple :
Supposons que le paramètre à lier soit un tableau nommé
selectedCourses
:public IActionResult OnPost(int? id, int[] selectedCourses)
Les données de formulaire ou de chaîne de requête peuvent avoir l’un des formats suivants :
selectedCourses=1050&selectedCourses=2000
selectedCourses[0]=1050&selectedCourses[1]=2000
[0]=1050&[1]=2000
selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
[a]=1050&[b]=2000&index=a&index=b
Évitez de lier un paramètre ou une propriété nommée
index
ouIndex
adjacent(e) à une valeur de collection. La liaison de modèle tente d’utiliserindex
comme index pour la collection, ce qui peut entraîner une liaison incorrecte. Prenons par exemple l’action suivante :public IActionResult Post(string index, List<Product> products)
Dans le code précédent, le paramètre de chaîne de requête
index
se lie au paramètre de méthodeindex
et est également utilisé pour lier la collection de produits. Le changement de nom du paramètreindex
ou l’utilisation d’un attribut de liaison de modèle pour configurer la liaison évite ce problème :public IActionResult Post(string productIndex, List<Product> products)
Le format suivant est pris en charge uniquement dans les données de formulaire :
selectedCourses[]=1050&selectedCourses[]=2000
Pour tous les exemples de formats précédents, la liaison de modèle passe un tableau de deux éléments au paramètre
selectedCourses
:- selectedCourses[0]=1050
- selectedCourses[1]=2000
Les formats de données qui utilisent des nombres en indice (... [0] ... [1] ...) doivent être impérativement numérotés de manière séquentielle à partir de zéro. S’il existe des vides dans la numérotation en indice, tous les éléments suivants sont ignorés. Par exemple, si les indices sont 0 et 2 au lieu de 0 et 1, le second élément est ignoré.
Dictionnaires
Pour les cibles Dictionary
, la liaison de modèle recherche les correspondances avec nom_paramètre ou nom_propriété. Si aucune correspondance n’est localisée, elle recherche l’un des formats pris en charge sans le préfixe. Par exemple :
Supposons que le paramètre cible soit un
Dictionary<int, string>
nomméselectedCourses
:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
Les données de chaîne de requête ou de formulaire posté peuvent ressembler à l’un des exemples suivants :
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
[1050]=Chemistry&selectedCourses[2000]=Economics
selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
Pour tous les exemples de formats précédents, la liaison de modèle passe un dictionnaire de deux éléments au paramètre
selectedCourses
:- selectedCourses["1050"]="Chemistry"
- selectedCourses["2000"]="Economics"
Liaison de constructeur et types d’enregistrements
La liaison de modèle nécessite que les types complexes aient un constructeur sans paramètre. Les formateurs d’entrée basés sur System.Text.Json
et Newtonsoft.Json
prennent en charge la désérialisation des classes qui n’ont pas de constructeur sans paramètre.
C# 9 introduit les types d’enregistrements, qui sont un excellent moyen de représenter succinctement les données sur le réseau. ASP.NET Core ajoute la prise en charge de la liaison de modèles et de la validation des types d’enregistrements avec un seul constructeur :
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>
Lors de la validation des types d’enregistrements, le CLR recherche les métadonnées de liaison et de validation spécifiquement sur les paramètres plutôt que sur les propriétés.
L’infrastructure permet de lier et de valider les types d’enregistrements :
public record Person([Required] string Name, [Range(0, 100)] int Age);
Pour que le précédent fonctionne, le type doit :
- Etre un type d’enregistrement.
- Avoir exactement un constructeur public.
- Contenir des paramètres qui ont une propriété portant le même nom et le même type. Les noms ne doivent pas différer par cas.
Types OCT sans constructeurs sans paramètre
Les types OCT qui n’ont pas de constructeurs sans paramètre ne peuvent pas être liés.
Le code suivant génère une exception indiquant que le type doit avoir un constructeur sans paramètre :
public class Person(string Name)
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0);
}
Types d’enregistrements avec des constructeurs créés manuellement
Types d’enregistrement avec des constructeurs créés manuellement qui ressemblent au travail des constructeurs principaux
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; }
}
Types d’enregistrements, métadonnées de validation et de liaison
Pour les types d’enregistrements, la validation et la liaison des métadonnées sur les paramètres est utilisée. Toutes les métadonnées sur les propriétés sont ignorées
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; }
}
Validation et métadonnées
La validation utilise des métadonnées sur le paramètre, mais utilise la propriété pour lire la valeur. Dans le cas ordinaire des constructeurs principaux, les deux sont identiques. Toutefois, il existe des moyens de le contourner :
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 ne met pas à jour les paramètres sur un type d’enregistrement
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
Dans ce cas, MVC ne tente pas de lier Name
à nouveau. Toutefois, Age
est autorisé à être mis à jour
Comportement de globalisation des données de routage et des chaînes de requête de liaison de modèle
Le fournisseur de valeur de routage ASP.NET et de valeur de chaîne de requête :
- Traite les valeurs comme une culture invariante.
- S’attend à ce que les URL soient de culture invariante.
En revanche, les valeurs provenant des données de formulaire subissent une conversion sensible à la culture. Cela est conçu pour que les URL soient partageables entre les paramètres régionaux.
Pour que le fournisseur de valeur de routage et le fournisseur de valeur de chaîne de requête ASP.NET Core subissent une conversion sensible à la culture :
- Héritent de IValueProviderFactory
- Copiez le code à partir de QueryStringValueProviderFactory ou RouteValueValueProviderFactory
- Remplacez la valeur de culture passée au constructeur du fournisseur de valeurs par CultureInfo.CurrentCulture
- Remplacez la fabrique de fournisseur de valeurs par défaut dans les options MVC par la nouvelle :
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;
}
}
Types de données spéciaux
Certains types de données spéciaux peuvent être pris en charge par la liaison de modèle.
IFormFile et IFormFileCollection
Fichier chargé inclus dans la requête HTTP. IEnumerable<IFormFile>
est également pris en charge pour plusieurs fichiers.
CancellationToken
Les actions peuvent éventuellement lier un CancellationToken
en tant que paramètre. Cette liaison de RequestAborted indique quand la connexion sous-jacente à la requête HTTP est abandonnée. Les actions peuvent utiliser ce paramètre pour annuler les opérations asynchrones durables exécutées dans le cadre des actions du contrôleur.
FormCollection
Permet de récupérer toutes les valeurs des données de formulaire posté.
Formateurs d’entrée
Les données contenues dans le corps de la requête peuvent être au format JSON, XML ou tout autre format. Pour analyser ces données, la liaison de modèle utilise un formateur d’entrée configuré pour prendre en charge un type de contenu particulier. Par défaut, ASP.NET Core inclut des formateurs d’entrée basés sur le format JSON pour prendre en charge les données JSON. Vous pouvez ajouter d’autres formateurs pour d’autres types de contenu.
ASP.NET Core sélectionne les formateurs d’entrée en fonction de l’attribut Consumes. Si aucun attribut n’est présent, il utilise l’en-tête Content-Type.
Pour utiliser les formateurs d’entrée XML intégrés :
Installez le package NuGet
Microsoft.AspNetCore.Mvc.Formatters.Xml
.Dans
Startup.ConfigureServices
, appelez AddXmlSerializerFormatters ou 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();
Appliquez l’attribut
Consumes
aux classes de contrôleur ou aux méthodes d’action devant contenir des données XML dans le corps de la requête.[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)
Pour plus d’informations, consultez Introduction à la sérialisation XML.
Personnaliser la liaison de modèle avec des formateurs d’entrée
Un formateur d’entrée assume l’entière responsabilité de la lecture des données du corps de la requête. Pour personnaliser ce processus, configurez les API utilisées par le formateur d’entrée. Cette section explique comment personnaliser le formateur d’entrée basé sur System.Text.Json
pour comprendre un type personnalisé nommé ObjectId
.
Considérez le modèle suivant, qui contient une propriété ObjectId
personnalisée nommée Id
:
public class ModelWithObjectId
{
public ObjectId Id { get; set; }
}
Pour personnaliser le processus de liaison de modèle lors de l’utilisation deSystem.Text.Json
, créez une classe dérivée 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);
}
}
Pour utiliser un convertisseur personnalisé, appliquez l’attribut JsonConverterAttribute au type . Dans l’exemple suivant, le type ObjectId
est configuré avec ObjectIdConverter
comme convertisseur personnalisé :
[JsonConverter(typeof(ObjectIdConverter))]
public struct ObjectId
{
public ObjectId(int id) =>
Id = id;
public int Id { get; }
}
Pour plus d’informations, consultez Comment écrire des convertisseurs personnalisés.
Exclure les types spécifiés de la liaison de modèle
Le comportement de la liaison de modèle et du système de validation est régi par ModelMetadata. Vous pouvez personnaliser ModelMetadata
en ajoutant un fournisseur de détails à MvcOptions.ModelMetadataDetailsProviders. Des fournisseurs de détails intégrés sont disponibles pour désactiver la liaison de modèle ou la validation des types spécifiés.
Pour désactiver la liaison de modèle sur tous les modèles d’un type spécifique, ajoutez ExcludeBindingMetadataProvider dans Startup.ConfigureServices
. Par exemple, pour désactiver la liaison de modèle sur tous les modèles de type 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();
Pour désactiver la validation des propriétés d’un type spécifique, ajoutez SuppressChildValidationMetadataProvider dans Startup.ConfigureServices
. Par exemple, pour désactiver la validation sur les propriétés de type 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();
Lieurs de modèles personnalisés
Vous pouvez étendre la liaison de modèle en écrivant un lieur de modèle personnalisé et en utilisant l’attribut [ModelBinder]
afin de le sélectionner pour une cible donnée. Découvrez plus d’informations sur la liaison de modèle personnalisée.
Liaison de modèle manuelle
Vous pouvez appeler la liaison de modèle manuellement à l’aide de la méthode TryUpdateModelAsync. La méthode est définie sur les classes ControllerBase
et PageModel
. Les surcharges de méthode vous permettent de spécifier le préfixe et le fournisseur de valeurs à utiliser. La méthode retourne false
en cas d’échec de la liaison de modèle. Voici un exemple :
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 utilise des fournisseurs de valeurs pour obtenir des données à partir du corps du formulaire, de la chaîne de requête et des données de routage. TryUpdateModelAsync
est généralement :
- Utilisé avec les applications RazorPages et MVC à l’aide de contrôleurs et de vues pour empêcher la publication excessive.
- Non utilisé avec une API web, sauf si consommé à partir de données de formulaire, de chaînes de requête et de données de routage. Les points de terminaison d’API web qui utilisent JSON utilisent des formateurs d’entrée pour désérialiser le corps de la demande en un objet.
Pour plus d’informations, consultez TryUpdateModelAsync.
Attribut [FromServices]
Le nom de cet attribut suit le modèle des attributs de liaison de modèle qui spécifient une source de données. Toutefois, il ne permet pas de lier les données d’un fournisseur de valeurs. Il obtient une instance d’un type à partir du conteneur d’injection de dépendances. Son objectif est de fournir une alternative à l’injection de constructeurs quand vous avez besoin d’un service uniquement si une méthode particulière est appelée.