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.

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 :

  1. Champs de formulaire
  2. Corps de la requête (pour les contrôleurs ayant l’attribut [ApiController].)
  3. Données de routage
  4. Paramètres de chaîne de requête
  5. 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 ou IEnumerable<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 vouloir obtenir des données provenant de cookie 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 d’application comprend un exemple de fournisseur de valeurs et un exemple de fabrique, qui permet de récupérer les valeurs provenant de cookie. 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ètre int 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 tableaux byte[] qui ont une valeur null.

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 [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 :

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 le DateRange.

L’action de contrôleur suivante utilise la classe DateRangepour 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 Localepour 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 DateRangeet Localepour 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 DateRangeTPpour 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 prefix.property_name. 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 sur 100.
  • Name défini sur null. La liaison de modèle s’attend à Instructor.Name car Instructor.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 ou Index adjacent(e) à une valeur de collection. La liaison de modèle tente d’utiliser index 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êteindex se lie au paramètre de méthode index et est également utilisé pour lier la collection de produits. Le changement de nom du paramètre index 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

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

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 :

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 :

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.Jsonpour 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 ObjectIdest 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 requête 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 :

  1. Champs de formulaire
  2. Corps de la requête (pour les contrôleurs ayant l’attribut [ApiController].)
  3. Données de routage
  4. Paramètres de chaîne de requête
  5. 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 ou IEnumerable<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 vouloir obtenir des données provenant de cookie 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 d’application comprend un exemple de fournisseur de valeurs et un exemple de fabrique, qui permet de récupérer les valeurs provenant de cookie. 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ètre int 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 tableaux byte[] qui ont une valeur null.

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 [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 :

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 le DateRange.

L’action de contrôleur suivante utilise la classe DateRangepour 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 Localepour 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 DateRangeet Localepour 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 DateRangeTPpour 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 prefix.property_name. 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 sur 100.
  • Name défini sur null. La liaison de modèle s’attend à Instructor.Name car Instructor.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 ou Index adjacent(e) à une valeur de collection. La liaison de modèle tente d’utiliser index 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êteindex se lie au paramètre de méthode index et est également utilisé pour lier la collection de produits. Le changement de nom du paramètre index 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

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

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 :

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 :

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.Jsonpour 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 ObjectIdest 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 requête 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 :

  1. Champs de formulaire
  2. Corps de la requête (pour les contrôleurs ayant l’attribut [ApiController].)
  3. Données de routage
  4. Paramètres de chaîne de requête
  5. 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 ou IEnumerable<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 vouloir obtenir des données provenant de cookie 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 d’application comprend un exemple de fournisseur de valeurs et un exemple de fabrique, qui permet de récupérer les valeurs provenant de cookie. 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ètre int 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 tableaux byte[] qui ont une valeur null.

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 [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 :

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 prefix.property_name. 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 sur 100.
  • Name défini sur null. La liaison de modèle s’attend à Instructor.Name car Instructor.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 ou Index adjacent(e) à une valeur de collection. La liaison de modèle tente d’utiliser index 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êteindex se lie au paramètre de méthode index et est également utilisé pour lier la collection de produits. Le changement de nom du paramètre index 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

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

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 :

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 :

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.Jsonpour 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 ObjectIdest 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 requête 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 :

  1. Champs de formulaire
  2. Corps de la requête (pour les contrôleurs ayant l’attribut [ApiController].)
  3. Données de routage
  4. Paramètres de chaîne de requête
  5. 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 ou IEnumerable<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 vouloir obtenir des données provenant de cookie 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 de cookie. 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ètre int 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 tableaux byte[] qui ont une valeur null.

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 [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 :

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 ou Index adjacent(e) à une valeur de collection. La liaison de modèle tente d’utiliser index 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êteindex se lie au paramètre de méthode index et est également utilisé pour lier la collection de produits. Le changement de nom du paramètre index 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

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

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 :

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.Jsonpour 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 ObjectIdest 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 requête 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.

Ressources supplémentaires