Nachdem ich einen Artikel über die Ausnahmebehandlung in der ASP.NET-Web-API gelesen habe, bin ich etwas verwirrt darüber, wann eine Ausnahme ausgelöst oder eine Fehlerantwort zurückgegeben werden soll. Ich frage mich auch, ob es möglich ist, die Antwort zu ändern, wenn Ihre Methode ein domänenspezifisches Modell anstelle von HttpResponseMessage
... zurückgibt.
Um es hier noch einmal zusammenzufassen: Meine Fragen, gefolgt von einem Code mit den Fallnummern:
Fragen
Fragen zu Fall 1
- Sollte ich immer
HttpResponseMessage
anstelle eines konkreten Domänenmodells verwenden, damit die Nachricht angepasst werden kann? - Kann die Nachricht angepasst werden, wenn Sie ein konkretes Domänenmodell zurückgeben?
Fragen zu Fall Nr. 2,3,4
- Sollte ich eine Ausnahme auslösen oder eine Fehlerantwort zurückgeben? Wenn die Antwort "es kommt darauf an" lautet, können Sie Situationen / Beispiele angeben, wann eine gegen die andere verwendet werden soll.
- Was ist der Unterschied zwischen Werfen
HttpResponseException
vsRequest.CreateErrorResponse
? Die Ausgabe an den Client scheint identisch zu sein ... - Sollte ich immer verwenden
HttpError
, um Antwortnachrichten in Fehler zu "verpacken" (ob die Ausnahme ausgelöst oder eine Fehlerantwort zurückgegeben wird)?
Fallbeispiele
// 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;
}
Aktualisieren
Um die Fälle Nr. 2,3,4 weiter zu demonstrieren, werden im folgenden Codeausschnitt verschiedene Optionen hervorgehoben, die "auftreten können", wenn ein Kunde nicht gefunden wird ...
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);
}
Antworten:
Der Ansatz, den ich gewählt habe, besteht darin, nur Ausnahmen von den API-Controller-Aktionen auszulösen und einen Ausnahmefilter zu registrieren, der die Ausnahme verarbeitet und eine entsprechende Antwort auf den Aktionsausführungskontext festlegt.
Der Filter stellt eine fließende Schnittstelle bereit, über die Handler für bestimmte Arten von Ausnahmen registriert werden können, bevor der Filter mit globaler Konfiguration registriert wird.
Die Verwendung dieses Filters ermöglicht die zentralisierte Ausnahmebehandlung, anstatt sie auf die Controller-Aktionen zu verteilen. Es gibt jedoch Fälle, in denen ich Ausnahmen innerhalb der Controller-Aktion abfange und eine bestimmte Antwort zurückgebe, wenn es nicht sinnvoll ist, die Behandlung dieser bestimmten Ausnahme zu zentralisieren.
Beispielregistrierung des Filters:
UnhandledExceptionFilterAttribute-Klasse:
Quellcode finden Sie auch hier .
quelle
Wenn Sie HttpResponseMessage nicht zurückgeben und stattdessen Entitäts- / Modellklassen direkt zurückgeben, ist es für mich nützlich, meinem Controller die folgende Dienstprogrammfunktion hinzuzufügen
und rufen Sie es einfach mit dem entsprechenden Statuscode und der entsprechenden Nachricht auf
quelle
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!"))
, aber in Fiddler wird der Status 500 (nicht 400) angezeigt. Irgendeine Idee warum?Fall 1
Fälle Nr. 2-4
Diese sollten gleichwertig sein; HttpResponseException kapselt eine HttpResponseMessage, die als HTTP-Antwort zurückgegeben wird.
Beispiel: Fall 2 könnte wie folgt umgeschrieben werden
... aber wenn Ihre Controller-Logik komplizierter ist, kann das Auslösen einer Ausnahme den Code-Fluss vereinfachen.
HttpError bietet Ihnen ein konsistentes Format für den Antworttext und kann in JSON / XML / etc serialisiert werden, ist jedoch nicht erforderlich. Beispielsweise möchten Sie möglicherweise keinen Entitätskörper in die Antwort aufnehmen, oder Sie möchten ein anderes Format.
quelle
Eine HttpResponseException oder gibt eine HttpResponesMessage für Fehler nicht werfen - außer wenn die Absicht ist es , die Anforderung zu beenden mit genau diesem Ergebnis .
HttpResponseException wird nicht wie andere Ausnahmen behandelt . Sie werden nicht in Ausnahmefiltern erfasst . Sie werden nicht im Exception Handler abgefangen . Sie sind eine schlauen Möglichkeit, eine HttpResponseMessage einzugeben, während der Ausführungsfluss des aktuellen Codes beendet wird.
Vermeiden Sie die Verwendung des HttpResponseException-Typs, es sei denn, der Code ist Infrastrukturcode, der auf dieser speziellen Nichtbehandlung basiert.
HttpResponseMessage sind keine Ausnahmen. Sie beenden den Ausführungsfluss des aktuellen Codes nicht. Sie können nicht als Ausnahmen gefiltert werden. Sie können nicht als Ausnahmen protokolliert werden. Sie stellen ein gültiges Ergebnis dar - sogar eine Antwort von 500 ist "eine gültige Antwort ohne Ausnahme"!
Machen Sie das Leben einfacher:
Wenn ein Ausnahme- / Fehlerfall vorliegt, lösen Sie gemäß der normalen Ausnahme eine normale .NET-Ausnahme aus - oder einen benutzerdefinierten Anwendungsausnahmetyp (der nicht von HttpResponseException abgeleitet ist) mit den gewünschten Eigenschaften für "http-Fehler / Antwort" wie z. B. einen Statuscode Handhabung .
Verwenden Sie Ausnahmefilter / Ausnahmebehandlungsroutinen / Ausnahmeprotokollierer, um mit diesen Ausnahmefällen etwas Passendes zu tun: Statuscodes ändern / hinzufügen? Tracking-IDs hinzufügen? Stapelspuren einschließen? Log?
Durch die Vermeidung von HttpResponseException wird die Behandlung von Ausnahmefällen vereinheitlicht und kann als Teil der exponierten Pipeline behandelt werden! Zum Beispiel kann man eine 'NotFound' in eine 404 und eine 'ArgumentException' in eine 400 und eine 'NullReference' in eine 500 mit Ausnahmen auf Anwendungsebene verwandeln - und gleichzeitig die Erweiterbarkeit ermöglichen, "Grundlagen" wie die Fehlerprotokollierung bereitzustellen.
quelle
ArgumentException
s in einem Controller logischerweise eine 400 ist, aber was ist mitArgumentException
s tiefer im Stapel? Es wäre nicht unbedingt richtig, diese in 400 umzuwandeln. Wenn Sie jedoch einen Filter haben, der alleArgumentException
s in 400 konvertiert , können Sie dies nur vermeiden, indem Sie die Ausnahme im Controller abfangen und etwas anderes erneut auslösen, wie es scheint den Zweck der einheitlichen Ausnahmebehandlung in einem Filter oder ähnlichem zu umgehen.Ein anderer Fall für die Verwendung
HttpResponseException
anstelle vonResponse.CreateResponse(HttpStatusCode.NotFound)
oder eines anderen Fehlerstatuscodes besteht darin, dass Sie Transaktionen in Aktionsfiltern haben und möchten, dass die Transaktionen zurückgesetzt werden, wenn eine Fehlerantwort an den Client zurückgegeben wird.Mit using
Response.CreateResponse
wird die Transaktion nicht zurückgesetzt, während mit einer Ausnahme eine Ausnahme ausgelöst wird.quelle
Ich möchte darauf hinweisen, dass ich die Erfahrung gemacht habe, dass beim Auslösen einer HttpResponseException anstelle der Rückgabe einer HttpResponseMessage in einer Webapi 2-Methode bei einem sofortigen Aufruf von IIS Express eine Zeitüberschreitung auftritt oder eine 200 zurückgegeben wird, jedoch mit einem HTML-Fehler in die Antwort. Der einfachste Weg, dies zu testen, besteht darin, eine Methode, die eine HttpResponseException auslöst, mit $ .ajax aufzurufen und im errorCallBack in ajax sofort eine andere Methode oder sogar eine einfache http-Seite aufzurufen. Sie werden feststellen, dass der sofortige Anruf fehlschlägt. Wenn Sie im Fehlerrückruf einen Haltepunkt oder ein Settimeout () hinzufügen, um den zweiten Aufruf um ein oder zwei Sekunden zu verzögern, damit der Server Zeit für die Wiederherstellung hat, funktioniert er ordnungsgemäß.Aktualisieren:Die Hauptursache für das seltsame Ajax-Verbindungs-Timeout ist, wenn ein Ajax-Aufruf schnell genug erfolgt, wird dieselbe TCP-Verbindung verwendet. Ich habe einen 401-Fehlerether ausgelöst, indem ich eine HttpResonseMessage zurückgegeben oder eine HTTPResponseException ausgelöst habe, die an den Ajax-Aufruf des Browsers zurückgegeben wurde. Aber zusammen mit diesem Aufruf gab MS einen Fehler "Objekt nicht gefunden" zurück, da in Startup.Auth.vb app.UserCookieAuthentication aktiviert war, sodass versucht wurde, die Antwort abzufangen und eine Umleitung hinzuzufügen, die jedoch mit "Objekt nicht Instanz eines Objekts" fehlerhaft war. Dieser Fehler war HTML, wurde aber nachträglich an die Antwort angehängt. Nur wenn der Ajax-Aufruf schnell genug erfolgte und dieselbe verwendete TCP-Verbindung verwendet wurde, wurde er an den Browser zurückgegeben und dann an die Vorderseite des nächsten Anrufs angehängt. Aus irgendeinem Grund ist Chrome nur abgelaufen. Fiddler puckte wegen der Mischung aus json und htm, aber Firefox drehte den wirklichen Fehler um. So seltsam, aber Paketschnüffler oder Firefox war der einzige Weg, diesen aufzuspüren.
Beachten Sie außerdem, dass Sie eine hinzufügen sollten, wenn Sie die Web-API-Hilfe zum Generieren der automatischen Hilfe verwenden und HttpResponseMessage zurückgeben
Attribut der Methode, damit die Hilfe korrekt generiert wird. Dann
oder auf Fehler
Ich hoffe, dies hilft jemand anderem, der möglicherweise eine zufällige Zeitüberschreitung oder einen Server erhält, der nicht sofort nach dem Auslösen einer HttpResponseException verfügbar ist.
Das Zurückgeben einer HttpResponseException hat den zusätzlichen Vorteil, dass Visual Studio bei einer nicht behandelten Ausnahme nicht beschädigt wird. Dies ist nützlich, wenn der zurückgegebene Fehler darin besteht, dass das AuthToken in einer einzelnen Seiten-App aktualisiert werden muss.
Update: Ich ziehe meine Aussage zum Zeitlimit für IIS Express zurück. Dies war zufällig ein Fehler in meinem clientseitigen Ajax-Rückruf. Es stellt sich heraus, dass Ajax 1.8 $ .ajax () und $ .ajax. () Zurückgibt.) Beide geben ein Versprechen zurück, aber nicht dasselbe verkettete Versprechen. () gibt dann ein neues Versprechen zurück, das dazu geführt hat, dass die Reihenfolge der Ausführung falsch war. Als das then () -Versprechen erfüllt war, war es eine Skript-Zeitüberschreitung. Seltsames Problem, aber kein IIS-Express-Problem, ein Problem zwischen Tastatur und Stuhl.quelle
Soweit ich das beurteilen kann, ist das Ergebnis dasselbe, ob Sie eine Ausnahme auslösen oder Request.CreateErrorResponse zurückgeben. Wenn Sie sich den Quellcode für System.Web.Http.dll ansehen, werden Sie genau das sehen. Schauen Sie sich diese allgemeine Zusammenfassung und eine sehr ähnliche Lösung an, die ich gemacht habe: Web-API, HttpError und das Verhalten von Ausnahmen
quelle
In Fehlersituationen wollte ich eine bestimmte Fehlerdetailklasse in einem vom Client angeforderten Format anstelle des Happy-Path-Objekts zurückgeben.
Ich möchte, dass meine Controller-Methoden das domänenspezifische Happy-Path-Objekt zurückgeben und andernfalls eine Ausnahme auslösen.
Das Problem, das ich hatte, war, dass die HttpResponseException-Konstruktoren keine Domänenobjekte zulassen.
Das habe ich mir schließlich ausgedacht
Result
ist eine Klasse, die Fehlerdetails enthält, währendProviderCollection
mein Happy Path-Ergebnis ist.quelle
Ich mag die oppositionelle Antwort
Wie auch immer, ich brauchte einen Weg, um die geerbte Ausnahme zu fangen, und diese Lösung erfüllt nicht alle meine Bedürfnisse.
Also habe ich geändert, wie er mit OnException umgeht, und dies ist meine Version
Der Kern ist diese Schleife, in der ich überprüfe, ob der Ausnahmetyp eine Unterklasse eines registrierten Typs ist.
my2cents
quelle