Après avoir lu un article sur la gestion des exceptions dans l'API Web ASP.NET, je ne sais pas trop quand lever une exception ou renvoyer une réponse d'erreur. Je me demande également s'il est possible de modifier la réponse lorsque votre méthode renvoie un modèle spécifique au domaine au lieu de HttpResponseMessage
...
Donc, pour récapituler, voici mes questions suivies d'un code avec des numéros de cas:
Des questions
Questions concernant le cas n ° 1
- Dois-je toujours utiliser à la
HttpResponseMessage
place d'un modèle de domaine concret, afin que le message puisse être personnalisé? - Le message peut-il être personnalisé si vous renvoyez un modèle de domaine concret?
Questions concernant le cas n ° 2,3,4
- Dois-je lancer une exception ou renvoyer une réponse d'erreur? Si la réponse est "cela dépend", pouvez-vous donner des situations / exemples sur le moment où utiliser l'un ou l'autre.
- Quelle est la différence entre lancer
HttpResponseException
vsRequest.CreateErrorResponse
? La sortie vers le client semble identique ... - Dois-je toujours utiliser
HttpError
pour "envelopper" les messages de réponse dans les erreurs (que l'exception soit levée ou que la réponse d'erreur soit renvoyée)?
Exemples de cas
// CASE #1
public Customer Get(string id)
{
var customer = _customerService.GetById(id);
if (customer == null)
{
var notFoundResponse = new HttpResponseMessage(HttpStatusCode.NotFound);
throw new HttpResponseException(notFoundResponse);
}
//var response = Request.CreateResponse(HttpStatusCode.OK, customer);
//response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
return customer;
}
// CASE #2
public HttpResponseMessage Get(string id)
{
var customer = _customerService.GetById(id);
if (customer == null)
{
var notFoundResponse = new HttpResponseMessage(HttpStatusCode.NotFound);
throw new HttpResponseException(notFoundResponse);
}
var response = Request.CreateResponse(HttpStatusCode.OK, customer);
response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
return response;
}
// CASE #3
public HttpResponseMessage Get(string id)
{
var customer = _customerService.GetById(id);
if (customer == null)
{
var message = String.Format("customer with id: {0} was not found", id);
var errorResponse = Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
throw new HttpResponseException(errorResponse);
}
var response = Request.CreateResponse(HttpStatusCode.OK, customer);
response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
return response;
}
// CASE #4
public HttpResponseMessage Get(string id)
{
var customer = _customerService.GetById(id);
if (customer == null)
{
var message = String.Format("customer with id: {0} was not found", id);
var httpError = new HttpError(message);
return Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
}
var response = Request.CreateResponse(HttpStatusCode.OK, customer);
response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
return response;
}
Mettre à jour
Pour aider à démontrer davantage les cas # 2, 3, 4, l'extrait de code suivant met en évidence plusieurs options qui "peuvent se produire" lorsqu'un client n'est pas trouvé ...
if (customer == null)
{
// which of these 4 options is the best strategy for Web API?
// option 1 (throw)
var notFoundMessage = new HttpResponseMessage(HttpStatusCode.NotFound);
throw new HttpResponseException(notFoundMessage);
// option 2 (throw w/ HttpError)
var message = String.Format("Customer with id: {0} was not found", id);
var httpError = new HttpError(message);
var errorResponse = Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
throw new HttpResponseException(errorResponse);
// option 3 (return)
var message = String.Format("Customer with id: {0} was not found", id);
return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
// option 4 (return w/ HttpError)
var message = String.Format("Customer with id: {0} was not found", id);
var httpError = new HttpError(message);
return Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
}
Réponses:
L'approche que j'ai adoptée consiste simplement à lancer des exceptions à partir des actions du contrôleur api et à enregistrer un filtre d'exception qui traite l'exception et définit une réponse appropriée sur le contexte d'exécution de l'action.
Le filtre expose une interface fluide qui fournit un moyen d'enregistrer des gestionnaires pour des types spécifiques d'exceptions avant d'enregistrer le filtre avec une configuration globale.
L'utilisation de ce filtre permet une gestion centralisée des exceptions au lieu de la répartir entre les actions du contrôleur. Il y a cependant des cas où je vais attraper des exceptions dans l'action du contrôleur et retourner une réponse spécifique si cela n'a pas de sens de centraliser la gestion de cette exception particulière.
Exemple d'enregistrement de filtre:
Classe UnhandledExceptionFilterAttribute:
Le code source peut également être trouvé ici .
la source
Si vous ne retournez pas HttpResponseMessage et que vous retournez directement des classes d'entité / modèle, une approche que j'ai trouvée utile consiste à ajouter la fonction utilitaire suivante à mon contrôleur
et appelez-le simplement avec le code d'état et le message appropriés
la source
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!"))
, mais dans Fiddler, il affiche le statut 500 (pas 400). Une idée pourquoi?Cas 1
Cas # 2-4
Celles-ci devraient être équivalentes; HttpResponseException encapsule un HttpResponseMessage, qui est ce qui est renvoyé en tant que réponse HTTP.
Par exemple, le cas n ° 2 pourrait être réécrit comme
... mais si la logique de votre contrôleur est plus compliquée, lever une exception peut simplifier le flux de code.
HttpError vous donne un format cohérent pour le corps de la réponse et peut être sérialisé en JSON / XML / etc, mais ce n'est pas obligatoire. par exemple, vous pouvez ne pas vouloir inclure un corps d'entité dans la réponse, ou vous pourriez vouloir un autre format.
la source
Ne lancez pas d'exception HttpResponseException ou ne renvoyez pas un HttpResponesMessage pour les erreurs - sauf si l'intention est de terminer la demande avec ce résultat exact .
Les exceptions HttpResponseException ne sont pas gérées de la même manière que les autres exceptions . Ils ne sont pas pris dans les filtres d'exception . Ils ne sont pas pris dans le gestionnaire d'exceptions . Ils sont un moyen sournois de se glisser dans un HttpResponseMessage tout en mettant fin au flux d'exécution du code actuel.
À moins que le code ne soit un code d'infrastructure reposant sur cette non-gestion spéciale, évitez d' utiliser le type HttpResponseException!
Les HttpResponseMessage ne sont pas des exceptions. Ils ne terminent pas le flux d'exécution du code actuel. Ils ne peuvent pas être filtrés comme exceptions. Ils ne peuvent pas être enregistrés comme exceptions. Ils représentent un résultat valide - même une réponse 500 est "une réponse valide sans exception"!
Simplifiez-vous la vie:
Lorsqu'il y a un cas exceptionnel / d'erreur, lancez une exception .NET normale - ou un type d'exception d'application personnalisé ( ne dérivant pas de HttpResponseException) avec les propriétés `` erreur / réponse http '' souhaitées, telles qu'un code d'état - comme par exception normale manipulation .
Utilisez les filtres d'exception / les gestionnaires d'exceptions / les enregistreurs d'exceptions pour faire quelque chose de approprié dans ces cas exceptionnels: modifier / ajouter des codes d'état? ajouter des identifiants de suivi? inclure des traces de pile? Journal?
En évitant HttpResponseException, la gestion des «cas exceptionnels» est uniformisée et peut être traitée dans le cadre du pipeline exposé! Par exemple, on peut transformer un 'NotFound' en un 404 et un 'ArgumentException' en un 400 et un 'NullReference' en un 500 facilement et uniformément avec des exceptions au niveau de l'application - tout en permettant l'extensibilité pour fournir des "bases" telles que la journalisation des erreurs.
la source
ArgumentException
s dans un contrôleur serait logiquement un 400, mais qu'en est-il deArgumentException
s plus profond dans la pile? Il ne serait pas nécessairement correct de les transformer en 400, mais si vous avez un filtre qui convertit tous lesArgumentException
s en 400, le seul moyen d'éviter cela est d'attraper l'exception dans le contrôleur et de relancer autre chose, ce qui semble pour contourner l'objectif de la gestion uniforme des exceptions dans un filtre ou similaire.Un autre cas d'utilisation à la
HttpResponseException
place deResponse.CreateResponse(HttpStatusCode.NotFound)
ou d'un autre code d'état d'erreur est si vous avez des transactions dans des filtres d'action et que vous souhaitez que les transactions soient annulées lors du renvoi d'une réponse d'erreur au client.L'utilisation
Response.CreateResponse
n'annulera pas la transaction, alors que la levée d'une exception le fera.la source
Je tiens à souligner que d'après mon expérience, si vous lancez une HttpResponseException au lieu de renvoyer un HttpResponseMessage dans une méthode webapi 2, si un appel est effectué immédiatement à IIS Express, il expirera ou renverra un 200 mais avec une erreur html dans la réponse. Le moyen le plus simple de tester cela est de faire un appel $ .ajax à une méthode qui lève une HttpResponseException et dans le errorCallBack en ajax faire un appel immédiat à une autre méthode ou même à une simple page http. Vous remarquerez que l'appel immédiat échouera. Si vous ajoutez un point d'arrêt ou un settimeout () dans l'erreur, rappelez pour retarder le deuxième appel d'une seconde ou deux en donnant au serveur le temps de récupérer, cela fonctionne correctement.Mettre à jour:La cause première du délai d'attente de connexion Ajax wierd est que si un appel ajax est effectué assez rapidement, la même connexion TCP est utilisée. Je levais un éther d'erreur 401 en renvoyant un HttpResonseMessage ou en lançant une exception HTTPResponseException qui a été renvoyée à l'appel ajax du navigateur. Mais avec cet appel, MS renvoyait une erreur d'objet non trouvé car dans Startup.Auth.vb app.UserCookieAuthentication était activé, il essayait donc de retourner intercepter la réponse et d'ajouter une redirection, mais il a commis une erreur avec Object pas Instance d'un objet. Cette erreur était HTML mais a été ajoutée à la réponse après coup, donc ce n'est que si l'appel ajax a été effectué assez rapidement et que la même connexion TCP utilisée a été renvoyée au navigateur, puis ajoutée au début de l'appel suivant. Pour une raison quelconque, Chrome vient d'expirer, Fiddler a pucked en raison du mélange de json et htm mais Firefox a retourné la vraie erreur. Donc, bizarre, mais le renifleur de paquets ou Firefox était le seul moyen de retrouver celui-ci.
Il convient également de noter que si vous utilisez l'aide de l'API Web pour générer une aide automatique et que vous renvoyez HttpResponseMessage, vous devez ajouter un
attribuer à la méthode pour que l'aide soit générée correctement. ensuite
ou sur erreur
J'espère que cela aide quelqu'un d'autre qui peut avoir un délai d'expiration aléatoire ou un serveur non disponible immédiatement après avoir lancé une exception HttpResponseException.
Le renvoi d'une HttpResponseException présente également l'avantage de ne pas provoquer l'interruption de Visual Studio sur une exception non gérée, utile lorsque l'erreur renvoyée est que AuthToken doit être actualisé dans une application de page unique.
Mise à jour: Je retire ma déclaration sur l'expiration du délai d'expiration d'IIS Express, cela s'est avéré être une erreur dans mon rappel ajax côté client, il s'avère que Ajax 1.8 retourne $ .ajax () et retourne $ .ajax. (). Then () les deux renvoient la promesse mais pas la même promesse enchaînée then () retourne une nouvelle promesse qui a rendu l'ordre d'exécution erroné. Ainsi, lorsque la promesse then () s'est terminée, c'était un délai d'expiration du script. Bizarre gotcha mais pas un problème express IIS un problème entre le clavier et la chaise.la source
Pour autant que je sache, que vous lanciez une exception ou que vous retourniez Request.CreateErrorResponse, le résultat est le même. Si vous regardez le code source de System.Web.Http.dll, vous en verrez autant. Jetez un œil à ce résumé général et à une solution très similaire que j'ai faite: Web Api, HttpError et le comportement des exceptions
la source
Dans les situations d'erreur, je voulais renvoyer une classe de détails d'erreur spécifique, dans le format demandé par le client à la place de l'objet happy path.
Je veux que mes méthodes de contrôleur renvoient l'objet happy path spécifique au domaine et lancer une exception dans le cas contraire.
Le problème que j'ai eu est que les constructeurs HttpResponseException n'autorisent pas les objets de domaine.
C'est ce que j'ai finalement trouvé
Result
est une classe qui contient des détails d'erreur, tandis queProviderCollection
mon résultat est heureux.la source
J'aime la réponse oppositionnelle
Quoi qu'il en soit, j'avais besoin d'un moyen d'attraper l'exception héritée et cette solution ne répond pas à tous mes besoins.
J'ai donc fini par changer la façon dont il gère OnException et c'est ma version
Le noyau est cette boucle où je vérifie si le type d'exception est une sous-classe d'un type enregistré.
my2cents
la source