Types de retour des actions des contrôleurs dans l’API web ASP.NET Core

Notes

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.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

ASP.NET Core fournit les options suivantes pour les types de retour d’action du contrôleur d’API web :

Cet article décrit quel type de retour est le plus adapté en fonction de chaque cas.

Type spécifique

L’action la plus simple retourne un type de données primitif ou complexe (par exemple, string ou un objet personnalisé). Considérez l’action suivante, qui retourne une collection d’objets Product personnalisés :

[HttpGet]
public Task<List<Product>> Get() =>
    _productContext.Products.OrderBy(p => p.Name).ToListAsync();

En l'absence de conditions connues contre lesquelles se prémunir, le renvoi d'un type spécifique pourrait suffire. L’action précédente n’acceptant aucun paramètre, la validation des contraintes de paramètre n’est pas nécessaire.

Lorsque plusieurs types de retour sont possibles, il est courant de mélanger un ActionResult type de retour avec le type de retour primitif ou complexe. IActionResult ou ActionResult<T>> sont nécessaires pour prendre en charge ce type d’action. Plusieurs exemples de plusieurs types de retour sont fournis dans cet article.

Retourner IEnumerable<T> ou IAsyncEnumerable<T>

Pour plus d’informations sur les performances, consultez Retour IEnumerable<T> ou IAsyncEnumerable<T>.

ASP.NET Core met en mémoire tampon le résultat des actions qui retournent IEnumerable<T> avant de les écrire dans la réponse. Envisagez de déclarer le type de retour de la signature d’action comme IAsyncEnumerable<T> pour garantir l’itération asynchrone. En fin de compte, le mode d’itération est basé sur le type concret sous-jacent retourné et le formateur sélectionné affecte la façon dont le résultat est traité :

  • Lors de l’utilisation du System.Text.Json formateur, MVC s’appuie sur la prise en charge ajoutée System.Text.Json pour diffuser le résultat.
  • Lorsque vous utilisez Newtonsoft.Json ou avec XML-based des formateurs, le résultat est mis en mémoire tampon.

Considérez l’action suivante, qui retourne les enregistrements de produits à prix de vente comme IEnumerable<Product> :

[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
    var products = _productContext.Products.OrderBy(p => p.Name).ToList();

    foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

L’équivalent IAsyncEnumerable<Product> de l’action précédente est :

[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
    var products = _productContext.Products.OrderBy(p => p.Name).AsAsyncEnumerable();

    await foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

Type IActionResult

Le type de retour IActionResult est adapté quand plusieurs types de retour ActionResult sont possibles dans une action. Les types ActionResult représentent différents codes d’état HTTP. Toute classe non abstraite dérivant de ActionResult est qualifiée de type de retour valide. Les types de retour les plus courants dans cette catégorie sont BadRequestResult (400), NotFoundResult (404), et OkObjectResult (200). Vous pouvez également utiliser des méthodes pratiques dans la classe ControllerBase pour retourner ActionResult des types à partir d’une action. Par exemple, return BadRequest(); est une forme abrégée de return new BadRequestResult();.

Étant donné que ce type d'action comporte plusieurs types de retour et de chemins, il est nécessaire d'utiliser l'attribut [ProducesResponseType] de manière libérale. Cet attribut génère des détails plus descriptifs de la réponse pour les pages d’aide de l’API web créées par des outils tels que Swagger. [ProducesResponseType] indique les types connus et les codes d’état HTTP que l’action doit retourner.

Action synchrone

Considérez l’action synchrone suivante pour laquelle il existe deux types de retour possibles :

[HttpGet("{id}")]
[ProducesResponseType<Product>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById_IActionResult(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : Ok(product);
}

Dans l’action précédente :

  • Un code d’état 404 est retourné quand le produit représenté par id n’existe pas dans le magasin de données sous-jacent. La NotFound méthode pratique est appelée en tant que raccourci pour return new NotFoundResult();.
  • Un code d’état 200 est retourné avec l’objet Product lorsque le produit existe. La Ok méthode pratique est appelée en tant que raccourci pour return new OkObjectResult(product);.

Action asynchrone

Considérez l’action asynchrone suivante pour laquelle il existe deux types de retour possibles :

[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync_IActionResult(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    return CreatedAtAction(nameof(CreateAsync_IActionResult), new { id = product.Id }, product);
}

Dans l’action précédente :

  • Un code d'état 400 est renvoyé lorsque la description du produit contient « XYZ Widget ». La BadRequest méthode pratique est appelée en tant que raccourci pour return new BadRequestResult();.

  • Un code d'état 201 est généré par la méthode de commodité CreatedAtAction lors de la création d'un produit. Le code suivant est une alternative à l’appel CreatedAtAction :

    return new CreatedAtActionResult(nameof(CreateAsync), 
                                    "Products", 
                                    new { id = product.Id }, 
                                    product);
    

    Dans le chemin de code précédent, l’objet Product est fourni dans le corps de la réponse. Un en-tête de réponse Location contenant l’URL du produit nouvellement créé est fourni.

Par exemple, le modèle suivant indique que les requêtes doivent inclure les propriétés Name et Description. Si vous n’indiquez pas Name et Description dans la requête, la validation du modèle échoue.

public class Product
{
    public int Id { get; set; }

    [Required]
    public string Name { get; set; } = string.Empty;

    [Required]
    public string Description { get; set; } = string.Empty;

    public bool IsOnSale { get; set; }
}

Si l'attribut [ApiController] est appliqué, les erreurs de validation du modèle se traduisent par un code d'état 400. Pour plus d’informations, consultez Réponses HTTP 400 automatiques.

ActionResult et IActionResult

La section suivante compare ActionResult à IActionResult

ActionResult<T> type

ASP.NET Core inclut le type de retour ActionResult<T> pour les actions du contrôleur d’API web. Il vous permet de retourner un type dérivant deActionResult ou de retourner un type spécifique. ActionResult<T> offre les avantages suivants par rapport au type IActionResult :

  • La propriété [ProducesResponseType] de l’attribut Type peut être exclue. Par exemple, [ProducesResponseType(200, Type = typeof(Product))] est simplifié en [ProducesResponseType(200)]. Le type de retour attendu pour l’action est déduit de T dans ActionResult<T>.
  • Des opérateurs de cast implicite prennent en charge la conversion de T et ActionResult en ActionResult<T>. T est converti en ObjectResult, ce qui signifie que return new ObjectResult(T); est simplifié en return T;.

C# ne prend pas en charge les opérateurs de cast implicite sur les interfaces. Par conséquent, ActionResult<T> nécessite une conversion de l’interface en un type concret. Par exemple, l’utilisation de IEnumerable dans l’exemple suivant ne fonctionne pas :

[HttpGet]
public ActionResult<IEnumerable<Product>> Get() =>
    _repository.GetProducts();

Pour réparer le code précédent, vous pouvez retourner _repository.GetProducts().ToList();.

La plupart des actions ont un type de retour spécifique. Des conditions inattendues peuvent se produire pendant l’exécution d’une action, auquel cas le type spécifique n’est pas retourné. Par exemple, le paramètre d’entrée d’une action peut entraîner l’échec de la validation du modèle. Dans ce cas, il est courant de retourner le type ActionResult approprié à la place du type spécifique.

Action synchrone

Considérez une action synchrone pour laquelle il existe deux types de retour possibles :

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById_ActionResultOfT(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : product;
}

Dans l’action précédente :

  • Un code d’état 404 est retourné quand le produit n’existe pas dans la base de données.
  • Un code d’état 200 est retourné avec l’objet Product correspondant lorsque le produit existe.

Action asynchrone

Considérez une action asynchrone pour laquelle il existe deux types de retour possibles :

[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync_ActionResultOfT(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    return CreatedAtAction(nameof(CreateAsync_ActionResultOfT), new { id = product.Id }, product);
}

Dans l’action précédente :

  • Un code d’état 400 (BadRequest) est retourné par le runtime ASP.NET Core dans les cas suivants :
    • L’attribut [ApiController] a été appliqué et la validation du modèle échoue.
    • La description de produit contient « XYZ Widget ».
  • Un code d'état 201 est généré par la méthode CreatedAtAction lors de la création d'un produit. Dans ce chemin de code, l’objet Product est fourni dans le corps de la réponse. Un en-tête de réponse Location contenant l’URL du produit nouvellement créé est fourni.

Type HttpResults

En plus des types de résultats intégrés spécifiques à MVC (IActionResultet ActionResult<T>), ASP.NET Core inclut les types HttpResults qui peuvent être utilisés dans les API Minimales et l’API web.

Différent des types de résultats propres à MVC, le HttpResults :

  • Il s’agit d’une implémentation de résultats qui est traitée par un appel à IResult.ExecuteAsync.

  • Ne tire pas parti des formateurs configurés. Ne pas tirer parti des formateurs configurés signifie :

    • Certaines fonctionnalités telles que Content negotiation ne sont pas disponibles.
    • Le produit Content-Type est décidé par l’implémentation HttpResults.

Le HttpResults peut être utile lors du partage de code entre les API minimales et l’API web.

Type IResult

L’espace de noms Microsoft.AspNetCore.Http.HttpResults contient des classes qui implémentent l’interface IResult. L’interface IResult définit un contrat qui représente le résultat d’un point de terminaison HTTP. La classe statique Results est utilisée pour créer différents objets IResult qui représentent différents types de réponses.

Le tableau des résultats intégrés affiche les aides de résultats courantes.

Prenez le code suivant :

[HttpGet("{id}")]
[ProducesResponseType<Product>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IResult GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? Results.NotFound() : Results.Ok(product);
}

Dans l’action précédente :

  • Un code d’état 404 est retourné quand le produit n’existe pas dans la base de données.
  • Un code d’état 200 est retourné avec l’objet correspondant Product lorsque le produit existe, généré par results.Ok<T>().

Prenez le code suivant :

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType<Product>(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IResult> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return Results.BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(CreateAsync), new { id = product.Id }) ?? $"/{product.Id}";
    return Results.Created(location, product);
}

Dans l’action précédente :

  • Un code d’état 400 est retourné dans les cas suivants :
    • L’attribut [ApiController] a été appliqué et la validation du modèle échoue.
    • La description de produit contient « XYZ Widget ».
  • Un code d'état 201 est généré par la méthode Results.Create lors de la création d'un produit. Dans ce chemin de code, l’objet Product est fourni dans le corps de la réponse. Un en-tête de réponse Location contenant l’URL du produit nouvellement créé est fourni.

Type Results<TResult1, TResultN>

La classe TypedResults statique retourne l’implémentation concrète IResult qui permet d’utiliser IResult comme type de retour. L’utilisation de l’implémentation concrète IResult offre les avantages suivants par rapport au type IResult :

  • Tous les attributs [ProducesResponseType] peuvent être exclus, car l’implémentation de HttpResult contribue automatiquement aux métadonnées du point de terminaison.

Lorsque plusieurs IResult types de retour sont nécessaires, le retour Results<TResult1, TResultN> est préférable au retour de IResult. Le retour Results<TResult1, TResultN> est préférable, car les types d’union génériques conservent automatiquement les métadonnées du point de terminaison.

Les types d’union Results<TResult1, TResultN> implémentent des opérateurs de cast implicites afin que le compilateur puisse convertir automatiquement les types spécifiés dans les arguments génériques en instance du type union. Cela offre l’avantage supplémentaire de fournir une vérification au moment de la compilation qu’un gestionnaire de routage retourne uniquement les résultats qu’il déclare. La tentative de retourner un type qui n’est pas déclaré comme l’un des arguments génériques vers Results<> entraîne une erreur de compilation.

Prenez le code suivant :

[HttpGet("{id}")]
public Results<NotFound, Ok<Product>> GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? TypedResults.NotFound() : TypedResults.Ok(product);
}

Dans l’action précédente :

  • Un code d’état 404 est retourné quand le produit n’existe pas dans la base de données.
  • Un code d’état 200 est retourné avec l’objet correspondant Product quand le produit existe, généré par typedResults.Ok<T>.
[HttpPost]
public async Task<Results<BadRequest, Created<Product>>> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return TypedResults.BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(CreateAsync), new { id = product.Id }) ?? $"/{product.Id}";
    return TypedResults.Created(location, product);
}

Dans l’action précédente :

  • Un code d’état 400 est retourné dans les cas suivants :
    • L’attribut [ApiController] a été appliqué et la validation du modèle échoue.
    • La description de produit contient « XYZ Widget ».
  • Un code d'état 201 est généré par la méthode TypedResults.Created lors de la création d'un produit. Dans ce chemin de code, l’objet Product est fourni dans le corps de la réponse. Un en-tête de réponse Location contenant l’URL du produit nouvellement créé est fourni.

Ressources supplémentaires

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

ASP.NET Core fournit les options suivantes pour les types de retour d’action du contrôleur d’API web :

Cet article décrit quel type de retour est le plus adapté en fonction de chaque cas.

Type spécifique

L’action la plus simple retourne un type de données primitif ou complexe (par exemple, string ou un objet personnalisé). Considérez l’action suivante, qui retourne une collection d’objets Product personnalisés :

[HttpGet]
public Task<List<Product>> Get() =>
    _productContext.Products.OrderBy(p => p.Name).ToListAsync();

En l'absence de conditions connues contre lesquelles se prémunir, le renvoi d'un type spécifique pourrait suffire. L’action précédente n’acceptant aucun paramètre, la validation des contraintes de paramètre n’est pas nécessaire.

Lorsque plusieurs types de retour sont possibles, il est courant de mélanger un ActionResult type de retour avec le type de retour primitif ou complexe. IActionResult ou ActionResult<T>> sont nécessaires pour prendre en charge ce type d’action. Plusieurs exemples de plusieurs types de retour sont fournis dans cet article.

Retourner IEnumerable<T> ou IAsyncEnumerable<T>

Pour plus d’informations sur les performances, consultez Retour IEnumerable<T> ou IAsyncEnumerable<T>.

ASP.NET Core met en mémoire tampon le résultat des actions qui retournent IEnumerable<T> avant de les écrire dans la réponse. Envisagez de déclarer le type de retour de la signature d’action comme IAsyncEnumerable<T> pour garantir l’itération asynchrone. En fin de compte, le mode d’itération est basé sur le type concret sous-jacent retourné et le formateur sélectionné affecte la façon dont le résultat est traité :

  • Lors de l’utilisation du System.Text.Json formateur, MVC s’appuie sur la prise en charge ajoutée System.Text.Json pour diffuser le résultat.
  • Lorsque vous utilisez Newtonsoft.Json ou avec XML-based des formateurs, le résultat est mis en mémoire tampon.

Considérez l’action suivante, qui retourne les enregistrements de produits à prix de vente comme IEnumerable<Product> :

[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
    var products = _productContext.Products.OrderBy(p => p.Name).ToList();

    foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

L’équivalent IAsyncEnumerable<Product> de l’action précédente est :

[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
    var products = _productContext.Products.OrderBy(p => p.Name).AsAsyncEnumerable();

    await foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

Type IActionResult

Le type de retour IActionResult est adapté quand plusieurs types de retour ActionResult sont possibles dans une action. Les types ActionResult représentent différents codes d’état HTTP. Toute classe non abstraite dérivant de ActionResult est qualifiée de type de retour valide. Les types de retour les plus courants dans cette catégorie sont BadRequestResult (400), NotFoundResult (404), et OkObjectResult (200). Vous pouvez également utiliser des méthodes pratiques dans la classe ControllerBase pour retourner ActionResult des types à partir d’une action. Par exemple, return BadRequest(); est une forme abrégée de return new BadRequestResult();.

Étant donné que ce type d'action comporte plusieurs types de retour et de chemins, il est nécessaire d'utiliser l'attribut [ProducesResponseType] de manière libérale. Cet attribut génère des détails plus descriptifs de la réponse pour les pages d’aide de l’API web créées par des outils tels que Swagger. [ProducesResponseType] indique les types connus et les codes d’état HTTP que l’action doit retourner.

Action synchrone

Considérez l’action synchrone suivante pour laquelle il existe deux types de retour possibles :

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Product))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById_IActionResult(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : Ok(product);
}

Dans l’action précédente :

  • Un code d’état 404 est retourné quand le produit représenté par id n’existe pas dans le magasin de données sous-jacent. La NotFound méthode pratique est appelée en tant que raccourci pour return new NotFoundResult();.
  • Un code d’état 200 est retourné avec l’objet Product lorsque le produit existe. La Ok méthode pratique est appelée en tant que raccourci pour return new OkObjectResult(product);.

Action asynchrone

Considérez l’action asynchrone suivante pour laquelle il existe deux types de retour possibles :

[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync_IActionResult(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    return CreatedAtAction(nameof(GetById_IActionResult), new { id = product.Id }, product);
}

Dans l’action précédente :

  • Un code d'état 400 est renvoyé lorsque la description du produit contient « XYZ Widget ». La BadRequest méthode pratique est appelée en tant que raccourci pour return new BadRequestResult();.

  • Un code d'état 201 est généré par la méthode de commodité CreatedAtAction lors de la création d'un produit. Le code suivant est une alternative à l’appel CreatedAtAction :

    return new CreatedAtActionResult(nameof(GetById), 
                                    "Products", 
                                    new { id = product.Id }, 
                                    product);
    

    Dans le chemin de code précédent, l’objet Product est fourni dans le corps de la réponse. Un en-tête de réponse Location contenant l’URL du produit nouvellement créé est fourni.

Par exemple, le modèle suivant indique que les requêtes doivent inclure les propriétés Name et Description. Si vous n’indiquez pas Name et Description dans la requête, la validation du modèle échoue.

public class Product
{
    public int Id { get; set; }

    [Required]
    public string Name { get; set; } = string.Empty;

    [Required]
    public string Description { get; set; } = string.Empty;

    public bool IsOnSale { get; set; }
}

Si l'attribut [ApiController] est appliqué, les erreurs de validation du modèle se traduisent par un code d'état 400. Pour plus d’informations, consultez Réponses HTTP 400 automatiques.

ActionResult et IActionResult

La section suivante compare ActionResult à IActionResult

ActionResult<T> type

ASP.NET Core inclut le type de retour ActionResult<T> pour les actions du contrôleur d’API web. Il vous permet de retourner un type dérivant deActionResult ou de retourner un type spécifique. ActionResult<T> offre les avantages suivants par rapport au type IActionResult :

  • La propriété [ProducesResponseType] de l’attribut Type peut être exclue. Par exemple, [ProducesResponseType(200, Type = typeof(Product))] est simplifié en [ProducesResponseType(200)]. Le type de retour attendu pour l’action est déduit de T dans ActionResult<T>.
  • Des opérateurs de cast implicite prennent en charge la conversion de T et ActionResult en ActionResult<T>. T est converti en ObjectResult, ce qui signifie que return new ObjectResult(T); est simplifié en return T;.

C# ne prend pas en charge les opérateurs de cast implicite sur les interfaces. Par conséquent, ActionResult<T> nécessite une conversion de l’interface en un type concret. Par exemple, l’utilisation de IEnumerable dans l’exemple suivant ne fonctionne pas :

[HttpGet]
public ActionResult<IEnumerable<Product>> Get() =>
    _repository.GetProducts();

Pour réparer le code précédent, vous pouvez retourner _repository.GetProducts().ToList();.

La plupart des actions ont un type de retour spécifique. Des conditions inattendues peuvent se produire pendant l’exécution d’une action, auquel cas le type spécifique n’est pas retourné. Par exemple, le paramètre d’entrée d’une action peut entraîner l’échec de la validation du modèle. Dans ce cas, il est courant de retourner le type ActionResult approprié à la place du type spécifique.

Action synchrone

Considérez une action synchrone pour laquelle il existe deux types de retour possibles :

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById_ActionResultOfT(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : product;
}

Dans l’action précédente :

  • Un code d’état 404 est retourné quand le produit n’existe pas dans la base de données.
  • Un code d’état 200 est retourné avec l’objet Product correspondant lorsque le produit existe.

Action asynchrone

Considérez une action asynchrone pour laquelle il existe deux types de retour possibles :

[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync_ActionResultOfT(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    return CreatedAtAction(nameof(GetById_ActionResultOfT), new { id = product.Id }, product);
}

Dans l’action précédente :

  • Un code d’état 400 (BadRequest) est retourné par le runtime ASP.NET Core dans les cas suivants :
    • L’attribut [ApiController] a été appliqué et la validation du modèle échoue.
    • La description de produit contient « XYZ Widget ».
  • Un code d'état 201 est généré par la méthode CreatedAtAction lors de la création d'un produit. Dans ce chemin de code, l’objet Product est fourni dans le corps de la réponse. Un en-tête de réponse Location contenant l’URL du produit nouvellement créé est fourni.

Type HttpResults

En plus des types de résultats intégrés spécifiques à MVC (IActionResultet ActionResult<T>), ASP.NET Core inclut les types HttpResults qui peuvent être utilisés dans les API Minimales et l’API web.

Différent des types de résultats propres à MVC, le HttpResults :

  • Il s’agit d’une implémentation de résultats qui est traitée par un appel à IResult.ExecuteAsync.

  • Ne tire pas parti des formateurs configurés. Ne pas tirer parti des formateurs configurés signifie :

    • Certaines fonctionnalités telles que Content negotiation ne sont pas disponibles.
    • Le produit Content-Type est décidé par l’implémentation HttpResults.

Le HttpResults peut être utile lors du partage de code entre les API minimales et l’API web.

Type IResult

L’espace de noms Microsoft.AspNetCore.Http.HttpResults contient des classes qui implémentent l’interface IResult. L’interface IResult définit un contrat qui représente le résultat d’un point de terminaison HTTP. La classe statique Results est utilisée pour créer différents objets IResult qui représentent différents types de réponses.

Le tableau des résultats intégrés affiche les aides de résultats courantes.

Prenez le code suivant :

[HttpGet("{id}")]
[ProducesResponseType(typeof(Product), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IResult GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? Results.NotFound() : Results.Ok(product);
}

Dans l’action précédente :

  • Un code d’état 404 est retourné quand le produit n’existe pas dans la base de données.
  • Un code d’état 200 est retourné avec l’objet correspondant Product lorsque le produit existe, généré par results.Ok<T>().

Prenez le code suivant :

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(typeof(Product), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IResult> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return Results.BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(GetById), new { id = product.Id }) ?? $"/{product.Id}";
    return Results.Created(location, product);
}

Dans l’action précédente :

  • Un code d’état 400 est retourné dans les cas suivants :
    • L’attribut [ApiController] a été appliqué et la validation du modèle échoue.
    • La description de produit contient « XYZ Widget ».
  • Un code d'état 201 est généré par la méthode Results.Create lors de la création d'un produit. Dans ce chemin de code, l’objet Product est fourni dans le corps de la réponse. Un en-tête de réponse Location contenant l’URL du produit nouvellement créé est fourni.

Type Results<TResult1, TResultN>

La classe TypedResults statique retourne l’implémentation concrète IResult qui permet d’utiliser IResult comme type de retour. L’utilisation de l’implémentation concrète IResult offre les avantages suivants par rapport au type IResult :

  • Tous les attributs [ProducesResponseType] peuvent être exclus, car l’implémentation de HttpResult contribue automatiquement aux métadonnées du point de terminaison.

Lorsque plusieurs IResult types de retour sont nécessaires, le retour Results<TResult1, TResultN> est préférable au retour de IResult. Le retour Results<TResult1, TResultN> est préférable, car les types d’union génériques conservent automatiquement les métadonnées du point de terminaison.

Les types d’union Results<TResult1, TResultN> implémentent des opérateurs de cast implicites afin que le compilateur puisse convertir automatiquement les types spécifiés dans les arguments génériques en instance du type union. Cela offre l’avantage supplémentaire de fournir une vérification au moment de la compilation qu’un gestionnaire de routage retourne uniquement les résultats qu’il déclare. La tentative de retourner un type qui n’est pas déclaré comme l’un des arguments génériques vers Results<> entraîne une erreur de compilation.

Prenez le code suivant :

[HttpGet("{id}")]
public Results<NotFound, Ok<Product>> GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? TypedResults.NotFound() : TypedResults.Ok(product);
}

Dans l’action précédente :

  • Un code d’état 404 est retourné quand le produit n’existe pas dans la base de données.
  • Un code d’état 200 est retourné avec l’objet correspondant Product quand le produit existe, généré par typedResults.Ok<T>.
[HttpPost]
public async Task<Results<BadRequest, Created<Product>>> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return TypedResults.BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(GetById), new { id = product.Id }) ?? $"/{product.Id}";
    return TypedResults.Created(location, product);
}

Dans l’action précédente :

  • Un code d’état 400 est retourné dans les cas suivants :
    • L’attribut [ApiController] a été appliqué et la validation du modèle échoue.
    • La description de produit contient « XYZ Widget ».
  • Un code d'état 201 est généré par la méthode TypedResults.Create lors de la création d'un produit. Dans ce chemin de code, l’objet Product est fourni dans le corps de la réponse. Un en-tête de réponse Location contenant l’URL du produit nouvellement créé est fourni.

Ressources supplémentaires

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

ASP.NET Core offre les options suivantes pour les types de retour des actions du contrôleur dans l’API web :

Ce document décrit le moment le plus approprié pour utiliser chaque type de retour.

Type spécifique

L’action la plus simple retourne un type de données primitif ou complexe (par exemple, string ou un type d’objet personnalisé). Considérez l’action suivante, qui retourne une collection d’objets Product personnalisés :

[HttpGet]
public List<Product> Get() =>
    _repository.GetProducts();

Sans conditions connues contre lesquelles une protection est nécessaire pendant l’exécution de l’action, le retour d’un type spécifique peut suffire. L’action précédente n’acceptant aucun paramètre, la validation des contraintes de paramètre n’est pas nécessaire.

Lorsque plusieurs types de retour sont possibles, il est courant de mélanger un ActionResult type de retour avec le type de retour primitif ou complexe. IActionResult ou ActionResult<T>> sont nécessaires pour prendre en charge ce type d’action. Plusieurs exemples de plusieurs types de retour sont fournis dans ce document.

Retourner IEnumerable<T> ou IAsyncEnumerable<T>

ASP.NET Core met en mémoire tampon le résultat des actions qui retournent IEnumerable<T> avant de les écrire dans la réponse. Envisagez de déclarer le type de retour de la signature d’action comme IAsyncEnumerable<T> pour garantir l’itération asynchrone. En fin de compte, le mode d’itération est basé sur le type concret sous-jacent retourné. MVC met automatiquement en mémoire tampon tout type concret qui implémente IAsyncEnumerable<T>.

Considérez l’action suivante, qui retourne les enregistrements de produits à prix de vente comme IEnumerable<Product> :

[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
    var products = _repository.GetProducts();

    foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

L’équivalent IAsyncEnumerable<Product> de l’action précédente est :

[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
    var products = _repository.GetProductsAsync();

    await foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

Type IActionResult

Le type de retour IActionResult est adapté quand plusieurs types de retour ActionResult sont possibles dans une action. Les types ActionResult représentent différents codes d’état HTTP. Toute classe non abstraite dérivant de ActionResult est qualifiée de type de retour valide. Les types de retour les plus courants dans cette catégorie sont BadRequestResult (400), NotFoundResult (404), et OkObjectResult (200). Vous pouvez également utiliser des méthodes pratiques dans la classe ControllerBase pour retourner ActionResult des types à partir d’une action. Par exemple, return BadRequest(); est une forme abrégée de return new BadRequestResult();.

Étant donné que ce type d'action comporte plusieurs types de retour et de chemins, il est nécessaire d'utiliser l'attribut [ProducesResponseType] de manière libérale. Cet attribut génère des détails plus descriptifs de la réponse pour les pages d’aide de l’API web créées par des outils tels que Swagger. [ProducesResponseType] indique les types connus et les codes d’état HTTP que l’action doit retourner.

Action synchrone

Considérez l’action synchrone suivante pour laquelle il existe deux types de retour possibles :

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Product))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById(int id)
{
    if (!_repository.TryGetProduct(id, out var product))
    {
        return NotFound();
    }

    return Ok(product);
}

Dans l’action précédente :

  • Un code d’état 404 est retourné quand le produit représenté par id n’existe pas dans le magasin de données sous-jacent. La NotFound méthode pratique est appelée en tant que raccourci pour return new NotFoundResult();.
  • Un code d’état 200 est retourné avec l’objet Product lorsque le produit existe. La Ok méthode pratique est appelée en tant que raccourci pour return new OkObjectResult(product);.

Action asynchrone

Considérez l’action asynchrone suivante pour laquelle il existe deux types de retour possibles :

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    await _repository.AddProductAsync(product);

    return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}

Dans l’action précédente :

  • Un code d'état 400 est renvoyé lorsque la description du produit contient « XYZ Widget ». La BadRequest méthode pratique est appelée en tant que raccourci pour return new BadRequestResult();.
  • Un code d'état 201 est généré par la méthode de commodité CreatedAtAction lors de la création d'un produit. Une alternative à l’appel CreatedAtAction est return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);. Dans ce chemin de code, l’objet Product est fourni dans le corps de la réponse. Un en-tête de réponse Location contenant l’URL du produit nouvellement créé est fourni.

Par exemple, le modèle suivant indique que les requêtes doivent inclure les propriétés Name et Description. Si vous n’indiquez pas Name et Description dans la requête, la validation du modèle échoue.

public class Product
{
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public string Description { get; set; }

    public bool IsOnSale { get; set; }
}

Si l'attribut [ApiController] est appliqué, les erreurs de validation du modèle se traduisent par un code d'état 400. Pour plus d’informations, consultez Réponses HTTP 400 automatiques.

ActionResult et IActionResult

La section suivante compare ActionResult à IActionResult

ActionResult<T> type

ASP.NET Core inclut le type de retour ActionResult<T> pour les actions du contrôleur d’API web. Il vous permet de retourner un type dérivant de ActionResult ou de retourner un type spécifique. ActionResult<T> offre les avantages suivants par rapport au type IActionResult :

  • La propriété [ProducesResponseType] de l’attribut Type peut être exclue. Par exemple, [ProducesResponseType(200, Type = typeof(Product))] est simplifié en [ProducesResponseType(200)]. Le type de retour attendu pour l’action est déduit de T dans ActionResult<T>.
  • Des opérateurs de cast implicite prennent en charge la conversion de T et ActionResult en ActionResult<T>. T est converti en ObjectResult, ce qui signifie que return new ObjectResult(T); est simplifié en return T;.

C# ne prend pas en charge les opérateurs de cast implicite sur les interfaces. Par conséquent, ActionResult<T> nécessite une conversion de l’interface en un type concret. Par exemple, l’utilisation de IEnumerable dans l’exemple suivant ne fonctionne pas :

[HttpGet]
public ActionResult<IEnumerable<Product>> Get() =>
    _repository.GetProducts();

Pour réparer le code précédent, vous pouvez retourner _repository.GetProducts().ToList();.

La plupart des actions ont un type de retour spécifique. Des conditions inattendues peuvent se produire pendant l’exécution d’une action, auquel cas le type spécifique n’est pas retourné. Par exemple, le paramètre d’entrée d’une action peut entraîner l’échec de la validation du modèle. Dans ce cas, il est courant de retourner le type ActionResult approprié à la place du type spécifique.

Action synchrone

Considérez une action synchrone pour laquelle il existe deux types de retour possibles :

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById(int id)
{
    if (!_repository.TryGetProduct(id, out var product))
    {
        return NotFound();
    }

    return product;
}

Dans l’action précédente :

  • Un code d’état 404 est retourné quand le produit n’existe pas dans la base de données.
  • Un code d’état 200 est retourné avec l’objet Product correspondant lorsque le produit existe.

Action asynchrone

Considérez une action asynchrone pour laquelle il existe deux types de retour possibles :

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    await _repository.AddProductAsync(product);

    return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}

Dans l’action précédente :

  • Un code d’état 400 (BadRequest) est retourné par le runtime ASP.NET Core dans les cas suivants :
    • L’attribut [ApiController] a été appliqué et la validation du modèle échoue.
    • La description de produit contient « XYZ Widget ».
  • Un code d'état 201 est généré par la méthode CreatedAtAction lors de la création d'un produit. Dans ce chemin de code, l’objet Product est fourni dans le corps de la réponse. Un en-tête de réponse Location contenant l’URL du produit nouvellement créé est fourni.

Ressources supplémentaires