413 votes

Meilleure pratique pour renvoyer des erreurs dans ASP.NET Web API

J'ai des préoccupations concernant la façon dont nous renvoyons les erreurs au client.

Est-ce que nous renvoyons immédiatement une erreur en lançant une HttpResponseException lorsque nous rencontrons une erreur :

public void Post(Customer customer)
{
    if (string.IsNullOrEmpty(customer.Name))
    {
        throw new HttpResponseException("Le nom du client ne peut pas être vide", HttpStatusCode.BadRequest) 
    }
    if (customer.Accounts.Count == 0)
    {
         throw new HttpResponseException("Le client n'a pas de compte", HttpStatusCode.BadRequest) 
    }
}

Ou bien accumulons-nous toutes les erreurs pour les renvoyer au client :

public void Post(Customer customer)
{
    List erreurs = new List();
    if (string.IsNullOrEmpty(customer.Name))
    {
        erreurs.Add("Le nom du client ne peut pas être vide"); 
    }
    if (customer.Accounts.Count == 0)
    {
         erreurs.Add("Le client n'a pas de compte"); 
    }
    var messageResponse = new HttpResponseMessage>(erreurs, HttpStatusCode.BadRequest);
    throw new HttpResponseException(messageResponse);
}

Ce n'est qu'un exemple de code, peu importe s'il s'agit d'erreurs de validation ou d'erreurs serveur, je voudrais juste connaître les bonnes pratiques, les avantages et inconvénients de chaque approche.

1 votes

Voir stackoverflow.com/a/22163675/200442 vous devriez utiliser ModelState.

1 votes

Notez que les réponses ici ne couvrent que les exceptions qui sont lancées dans le contrôleur lui-même. Si votre API renvoie un IQueryable qui n'a pas encore été exécuté, l'exception n'est pas dans le contrôleur et n'est pas attrapée...

4 votes

Très belle question mais je ne trouve pas de surcharge de constructeur de la classe HttpResponseException qui prend deux paramètres comme mentionné dans votre publication - HttpResponseException("Le nom du client ne peut pas être vide", HttpStatusCode.BadRequest) c'est-à-dire HttpResponseException(string, HttpStatusCode).

313voto

gdp Points 2061

Pour ma part, je renvoie généralement une HttpResponseException et je définis le code d'état en fonction de l'exception lancée et si l'exception est fatale ou non, cela déterminera si je renvoie immédiatement l'exception HttpResponseException.

À la fin de la journée, c'est une API qui renvoie des réponses et non des vues, donc je pense qu'il est acceptable d'envoyer un message avec l'exception et le code d'état au consommateur. Je n'ai actuellement pas eu besoin d'accumuler les erreurs et de les renvoyer car la plupart des exceptions sont généralement dues à des paramètres incorrects ou à des appels incorrects, etc.

Un exemple dans mon application est que parfois le client demande des données, mais il n'y a pas de données disponibles, alors je lance une exception personnalisée NoDataAvailableException et je laisse remonter l'exception vers l'application Web API, où ensuite dans mon filtre personnalisé qui la capture, j'envoie un message approprié avec le bon code d'état.

Je ne suis pas sûr à 100% de quelle est la meilleure pratique à suivre, mais cela fonctionne pour moi actuellement, donc c'est ce que je fais.

Mise à jour:

Depuis que j'ai répondu à cette question, quelques articles de blog ont été écrits sur le sujet :

https://weblogs.asp.net/fredriknormen/asp-net-web-api-exception-handling

(celui-ci présente de nouvelles fonctionnalités dans les versions nightly) https://learn.microsoft.com/archive/blogs/youssefm/error-handling-in-asp-net-webapi

Mise à jour 2

Mise à jour concernant notre processus de gestion des erreurs, nous avons deux cas :

  1. Pour les erreurs générales telles que non trouvées, ou des paramètres invalides étant passés à une action, nous renvoyons une HttpResponseException pour arrêter immédiatement le traitement. De plus, pour les erreurs de modèle dans nos actions, nous transmettons le dictionnaire d'état du modèle à l'extension Request.CreateErrorResponse et l'emballons dans une HttpResponseException. L'ajout du dictionnaire d'état du modèle donne une liste des erreurs du modèle envoyées dans le corps de la réponse.

  2. Pour les erreurs qui se produisent dans des couches supérieures, des erreurs serveur, nous laissons l'exception remonter vers l'application Web API, ici nous avons un filtre d'exception global qui examine l'exception, la journalise avec ELMAH et tente de lui donner un sens en définissant le bon code d'état HTTP et un message d'erreur amical pertinent en tant que corps à nouveau dans une HttpResponseException. Pour les exceptions auxquelles nous ne nous attendons pas, le client recevra l'erreur interne du serveur 500 par défaut, mais un message générique pour des raisons de sécurité.

Mise à jour 3

Récemment, après avoir adopté Web API 2, pour renvoyer des erreurs générales maintenant nous utilisons l'interface IHttpActionResult, en particulier les classes intégrées dans l'espace de noms System.Web.Http.Results telles que NotFound, BadRequest quand elles conviennent, si elles ne le font pas nous les étendons, par exemple un résultat NotFound avec un message de réponse :

public class NotFoundWithMessageResult : IHttpActionResult
{
    private string message;

    public NotFoundWithMessageResult(string message)
    {
        this.message = message;
    }

    public Task ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.NotFound);
        response.Content = new StringContent(message);
        return Task.FromResult(response);
    }
}

1 votes

Merci pour votre réponse, geepie, c'est une bonne expérience, préférez-vous envoyer une exception immédiatement?

1 votes

Comme je l'ai dit, cela dépend vraiment de l'exception. Une exception fatale, comme par exemple si l'utilisateur passe à l'API Web un paramètre invalide à un point de terminaison, alors je créerais une HttpResponseException et la renverrais immédiatement à l'application consommatrice.

1 votes

Les exceptions dans la question concernent vraiment plus la validation, voir stackoverflow.com/a/22163675/200442.

197voto

Manish Jain Points 1505

ASP.NET Web API 2 l'a vraiment simplifié. Par exemple, le code suivant :

public HttpResponseMessage GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        var message = string.Format("Produit avec l'id = {0} introuvable", id);
        HttpError err = new HttpError(message);
        return Request.CreateResponse(HttpStatusCode.NotFound, err);
    }
    else
    {
        return Request.CreateResponse(HttpStatusCode.OK, item);
    }
}

renvoie le contenu suivant au navigateur lorsque l'élément n'est pas trouvé :

HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
Date: Thu, 09 Aug 2012 23:27:18 GMT
Content-Length: 51

{
  "Message": "Produit avec l'id = 12 introuvable"
}

Suggestion : Ne pas générer d'erreur HTTP 500 sauf en cas d'erreur catastrophique (par exemple, l'exception de défaut de WCF). Choisissez un code d'état HTTP approprié qui représente l'état de vos données. (Voir le lien apigee ci-dessous.)

Liens :

4 votes

Je pousserais même plus loin et je déclencherais une ResourceNotFoundException depuis la DAL/Repo que je vérifie dans le gestionnaire d'exceptions de Web Api 2.2 pour le type ResourceNotFoundException, puis je renverrais "Produit avec l'identifiant xxx non trouvé". De cette manière, c'est de manière générique ancré dans l'architecture plutôt que dans chaque action.

1 votes

Y a-t-il une utilisation spécifique pour le return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState); Quelle est la différence entre CreateResponse et CreateErrorResponse

13 votes

Selon, w3.org/Protocols/rfc2616/rfc2616-sec10.html, une erreur client est un code de niveau 400 et une erreur serveur est un code de niveau 500. Ainsi, un code d'erreur 500 pourrait être très approprié dans de nombreux cas pour une API Web, pas seulement pour des erreurs "catastrophiques".

80voto

Daniel Little Points 4451

Il semble que vous ayez plus de difficultés avec la Validation qu'avec les erreurs/exceptions donc je vais en dire un peu sur les deux.

Validation

Les actions du contrôleur devraient généralement prendre des modèles d'entrée où la validation est déclarée directement sur le modèle.

public class Customer
{ 
    [Require]
    public string Name { get; set; }
}

Ensuite, vous pouvez utiliser un ActionFilter qui envoie automatiquement des messages de validation au client.

public class ValidationActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var modelState = actionContext.ModelState;

        if (!modelState.IsValid) {
            actionContext.Response = actionContext.Request
                 .CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
        }
    }
} 

Pour plus d'informations à ce sujet, consultez http://ben.onfabrik.com/posts/automatic-modelstate-validation-in-aspnet-mvc

Gestion des erreurs

Il est préférable de renvoyer un message au client qui représente l'exception survenue (avec le code d'état pertinent).

Par défaut, vous devez utiliser Request.CreateErrorResponse(HttpStatusCode, message) si vous souhaitez spécifier un message. Cependant, cela lie le code à l'objet Request, ce que vous ne devriez pas avoir à faire.

Je crée généralement mon propre type d'exception "sécurisé" que je m'attends à ce que le client sache gérer et j'enveloppe toutes les autres avec une erreur générique 500.

L'utilisation d'un filtre d'actions pour gérer les exceptions ressemblerait à ceci :

public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        var exception = context.Exception as ApiException;
        if (exception != null) {
            context.Response = context.Request.CreateErrorResponse(exception.StatusCode, exception.Message);
        }
    }
}

Ensuite, vous pouvez l'enregistrer globalement.

GlobalConfiguration.Configuration.Filters.Add(new ApiExceptionFilterAttribute());

Il s'agit de mon type d'exception personnalisé.

using System;
using System.Net;

namespace WebApi
{
    public class ApiException : Exception
    {
        private readonly HttpStatusCode statusCode;

        public ApiException (HttpStatusCode statusCode, string message, Exception ex)
            : base(message, ex)
        {
            this.statusCode = statusCode;
        }

        public ApiException (HttpStatusCode statusCode, string message)
            : base(message)
        {
            this.statusCode = statusCode;
        }

        public ApiException (HttpStatusCode statusCode)
        {
            this.statusCode = statusCode;
        }

        public HttpStatusCode StatusCode
        {
            get { return this.statusCode; }
        }
    }
}

Un exemple d'exception que mon API peut générer.

public class NotAuthenticatedException : ApiException
{
    public NotAuthenticatedException()
        : base(HttpStatusCode.Forbidden)
    {
    }
}

0 votes

J'ai un problème lié à la gestion des erreurs dans la définition de la classe ApiExceptionFilterAttribute. Dans la méthode OnException, le statut de l'exception n'est pas valide car l'exception est une WebException. Que puis-je faire dans ce cas?

1 votes

@razp26 si vous faites référence au code var exception = context.Exception as WebException; qui était une faute de frappe, il aurait dû être ApiException

2 votes

Pouvez-vous s'il vous plaît ajouter un exemple de comment la classe ApiExceptionFilterAttribute serait utilisée?

36voto

tartakynov Points 361

Vous pouvez lancer une HttpResponseException

HttpResponseMessage réponse = 
    this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "votre message");
throw new HttpResponseException(réponse);

0 votes

Ces classes/méthodes ne semblent pas être disponibles dans .NET 5. Peut-être que BadHttpRequestException remplace ces dernières.

24voto

Mick Points 554

Pour Web API 2, mes méthodes retournent systématiquement IHttpActionResult, donc j'utilise...

public IHttpActionResult Save(MyEntity entity)
{
    ....
    if (...erreurs....)
        return ResponseMessage(
            Request.CreateResponse(
                HttpStatusCode.BadRequest, 
                validationErrors));

    // sinon succès
    return Ok(returnData);
}

0 votes

Cette réponse est correcte, bien que vous devriez ajouter une référence à System.Net.Http

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X