Behandeln von ASP.NET-Ausnahmen mit IRO.Mvc.MvcExceptionHandler



Wenn Sie ein C # -Backend-Entwickler sind, müssen Sie wahrscheinlich früher oder später einen einheitlichen Weg finden, um mit Ausnahmesituationen umzugehen. Auch wenn Sie mit dem 500-Code in der Antwort zufrieden sind, hilft dieser Artikel, Ihre Methode zu verbessern, ohne etwas zum Umschreiben zu zwingen.

Wir werden über die ASP.NET-Bibliothek sprechen, mit der Sie dieses Problem so elegant wie möglich lösen können. Für diejenigen, die zu faul sind, um einen langen Artikel zu lesen - die Readme-Datei und die Bibliothek selbst sind hier , ein Beispiel ist hier . Verfügbar auf nuget.org und ich werde mich nur freuen, wenn es jemandem zugute kommt. Fahren wir also mit dem Code fort. Schauen wir uns zunächst die Alternativen an.

Eines der ersten Dinge, die Ihnen in den Sinn kommen könnten, ist das Erstellen eines DTO (Datenübertragungsobjekts) für die Behandlung von Ausnahmen, das Abfangen einer Ausnahme im Controller (obwohl es nicht erforderlich ist, dass es sich um eine Ausnahme handelt, ist es möglich, nur auf Null oder ähnliches zu prüfen). Füllen Sie die Daten im DTO aus und senden Sie sie an den Client. Der Code für diese Methode könnte ungefähr so ​​aussehen:

public IActionResult Get() { try { //Code with exception. } catch (Exception ex) { return new JsonResult( new ErrorDto { IsError = true, Message = ex.Message }); } } 

Eine andere Möglichkeit ist die Verwendung von HTTP-Statuscodes.

 public IActionResult Get() { try { //Code with exception. } catch (Exception ex) { return BadRequest(); } } 

Eine ziemlich gängige Praxis, die jedoch ihre Nachteile hat: Es kann schwierig sein, das Wesentliche Ihrer Situation mit einem der Standardcodes zu beschreiben, weshalb derselbe Code auch auf demselben System unterschiedlich interpretiert werden kann und dem Entwickler zu wenig Informationen zum Debuggen bietet.

Und hier können einige sogar anfangen, diese beiden Methoden in unterschiedlichen Anteilen zu kombinieren. Irgendwo werden sie vergessen, das DTO zu senden, irgendwo wird der Code nicht gesendet oder der falsche wird gesendet, aber irgendwo im Allgemeinen wird er mit den falschen JSON-Einstellungen serialisiert und gibt nicht das zurück, was benötigt wird.

Angesichts des oben Gesagten versuchen viele, dieses Problem mit app.UseExceptionHandler () zu lösen . durch den Umgang mit Ausnahmen. Dies ist ein guter Versuch, aber Sie können das Problem nicht so leicht vergessen. Erstens werden Sie immer noch vor dem Problem stehen, ein DTO für Ausnahmen auszuwählen. Zweitens erlaubt ein solcher Handler nicht, http-Fehlercodes zu verarbeiten, die von Controllern zurückgegeben wurden, weil Eine Ausnahme ist nicht aufgetreten. Drittens ist es auf diese Weise unpraktisch, das Problem der Fehlerklassifizierung zu lösen. Sie müssen viel Code schreiben, um jeder Ausnahme eine Nachricht, einen http-Code oder etwas hinzuzufügen. Und viertens verlieren Sie die Möglichkeit, AspA DeveloperExceptionPage zu verwenden, was für das Debuggen sehr unpraktisch ist. Selbst wenn Sie dieses Problem irgendwie lösen, müssen alle Entwickler in diesem Projekt die Spezifikationen genau befolgen, die Fehlerbehandlung speziell für Ausnahmen erstellen und ihre DTOs nicht zurückgeben, da sonst die Fehler in Ihrer API von Methode zu Methode unterschiedlich aussehen können.

Ausgewählte Ausnahmebehandlungsoption


Bevor ich zeige, wie Sie mit IRO.Mvc.MvcExceptionHandler Ausnahmen behandeln können, beschreibe ich zunächst, wie ich die ideale Ausnahmebehandlung sehe. Dazu legen wir eine Reihe von Anforderungen fest:

  1. Es sollte ein DTO sein, aber wir lehnen auch keine http-Codes ab, weil Für viele Fehler sind sie immer noch gut geeignet, können überall und in einem alten Projekt verwendet werden, das Sie ebenfalls unterstützen müssen, und sie sind einfach universell. Das Standard-DTO enthält das IsError-Feld (das das Schreiben einer universellen Fehlerbehandlung auf dem Client ermöglicht). Es sollte auch den ErrorKey-Zeichenfolgenfehlercode enthalten, den der Entwickler sofort nur durch Betrachten erkennen kann und der weitere Informationen enthält. Darüber hinaus können Sie bei Bedarf einen Link zu einer Seite mit einer Beschreibung dieses Fehlers hinzufügen.
  2. Das ist alles in der Prod. Im Entwicklungsmodus sollte dieses DTO eine Stapelverfolgung zurückgeben und Daten anfordern: Cookies, Header, Parameter. Mit Blick auf die Zukunft gibt die im Artikel beschriebene Middleware sogar einen Link zur generierten DeveloperExceptionPage zurück, mit der Sie die Ausnahmeausnahme in einer praktischen Form anzeigen können, aber dazu später mehr.
  3. Der Entwickler kann die Ausnahme, den http-Fehlercode und den ErrorKey zusammenbinden. Dies bedeutet, dass, wenn der Entwickler 403-Code vom Controller sendet, der DTO mit diesem Code zurückgegeben wird, wenn der Entwickler einen bestimmten Fehlerschlüssel daran angehängt hat. Und umgekehrt, wenn eine UnauthorizedAccessException auftritt, wird sie an den http-Code und den ErrorKey gebunden.

Dies ist das Standardformat, das in der Bibliothek verwendet wird:

 { "__IsError": true, "ErrorKey": "ClientException", "InfoUrl": "https://iro.com/errors/ClientException" } 

Ich muss sofort sagen, dass die Form, in der die Daten an den Kunden übertragen werden, absolut beliebig sein kann. Dies ist nur eine der Optionen.

IRO.Mvc.MvcExceptionHandler


Jetzt werde ich zeigen, wie ich dieses Problem für mich selbst gelöst habe, indem ich die IRO.Mvc.MvcExceptionHandler-Bibliothek geschrieben habe.

Wir verbinden einen Ausnahmebehandler wie jede andere Middleware - in der Startup-Klasse.

 app.UseMvcExceptionHandler((s) => { //Settings... }); 

Innerhalb des zu delegierenden Delegaten müssen wir unsere Middleware konfigurieren. Es ist notwendig, Ausnahmen http-Codes und ErrorKey zuzuordnen (zu binden). Unten finden Sie die einfachste Einrichtungsoption.

  s.Mapping((builder) => { builder.RegisterAllAssignable<Exception>( httpCode: 500, errorKeyPrefix: "Ex_" ); }); 

Wie ich den faulsten Hardcore-Entwicklern versprochen habe, die es nicht gewohnt sind, mit Ausnahmen umzugehen, muss nichts weiter getan werden. Dieser Code bindet alle Ausnahmen in der ASP.NET-Pipeline an das gemeinsame DTO mit Code 500, und der Name der Ausnahme wird in ErrorKey geschrieben.

Es ist zu verstehen, dass die RegisterAllAssignable-Methode nicht nur eine Ausnahme des angegebenen Typs registriert, sondern auch alle seine Nachkommen. Wenn Sie nur Informationen zu bestimmten Ausnahmen an den Client senden möchten, ist es eine völlig vernünftige Entscheidung, Ihre ClientException zu erstellen und nur diese zuzuordnen. Wenn Sie einen http-Code für ClientException und einen anderen für dessen Nachfolger SpecialClientException festlegen, wird gleichzeitig der Code SpecialClientException für alle Nachkommen verwendet, wobei die ClientException-Einstellung ignoriert wird. All dies wird zwischengespeichert, sodass keine Leistungsprobleme auftreten.

Sie können Ihren ErrorKey- und http-Code für eine bestimmte Ausnahme optimieren und registrieren:

  s.Mapping((builder) => { //By exception, custom error key. builder.Register<ArgumentNullException>( httpCode: 555, errorKey: "CustomErrorKey" ); //By http code. builder.Register( httpCode: 403, errorKey: "Forbidden" ); //By exception, default ErrorKey and http code. builder.Register<NullReferenceException>(); //Alternative registration method. builder.Register((ErrorInfo) new ErrorInfo() { ErrorKey = "MyError", ExceptionType = typeof(NotImplementedException), HttpCode = 556 }); }); 

Neben dem Mapping lohnt es sich, Mittelgewichte zu konfigurieren. Sie können die JSON-Serialisierungseinstellungen, die Adresse Ihrer Site, einen Link zur Fehlerbeschreibungsseite und die Funktionsweise der Middleware über IsDebug, den Standard-http-Code für nicht behandelte Ausnahmen, angeben.

  s.ErrorDescriptionUrlHandler = new FormattedErrorDescriptionUrlHandler("https://iro.com/errors/{0}"); s.IsDebug = isDebug; s.DefaultHttpCode = 500; s.JsonSerializerSettings.Formatting = Formatting.Indented; s.Host="https://iro.com"; s.CanBindByHttpCode = true; 

Die letzte Eigenschaft gibt an, ob es möglich ist, unser DTO über den http-Code zu binden.
Sie können auch angeben, wie Situationen mit internen Ausnahmen behandelt werden sollen, z. B. TaskCanceledException mit einem internen registrierten Fehler aufgrund von .Wait (). Hier ist zum Beispiel ein Standard-Resolver, der interne Ausnahmen von solchen Ausnahmen entfernt und bereits mit ihnen arbeitet:

  s.InnerExceptionsResolver = InnerExceptionsResolvers.InspectAggregateException; 

Wenn Sie die Serialisierung optimieren müssen, können Sie die FilterAfterDTO-Methode festlegen. Geben Sie true zurück, um die Standardverarbeitung zu deaktivieren und errorContext.ErrorDTO nach Ihren Wünschen zu serialisieren. Es besteht Zugriff auf den HttpContext und den Fehler selbst.

  s.FilterAfterDTO = async (errorContext) => { //Custom error handling. Return true if MvcExceptionHandler must ignore current error, //because it was handled. return false; }; 

DeveloperExceptionPage und andere Vorteile des Debug-Modus


Wir haben die Einstellungen herausgefunden. Lassen Sie uns nun herausfinden, wie Sie alles debuggen können. Im DTO-Produkt ist die Antwort in der Antwort einfach und ich habe sie bereits oben gezeigt. Jetzt werde ich zeigen, wie dasselbe DTO im Debug-Modus aussieht:



Wie Sie sehen können, gibt es hier viel mehr Informationen, es gibt Stackrace- und Anforderungsdaten. Es ist jedoch noch bequemer, einfach dem Link im Feld DebugUrl zu folgen und die Fehlerdaten ohne Anstrengung anzuzeigen:



Es war ziemlich schwierig, diese Funktion zu implementieren DeveloperExceptionPage ist einfach nicht für die Verwendung durch Entwickler von Drittanbietern vorgesehen. Anfangs war es nicht möglich, den Link in einem Browser mit einer anderen Sitzung zu öffnen. Der Inhalt wurde nach einem Neustart nicht mehr angezeigt. All dies konnte nur durch Zwischenspeichern der HTML-Antwort dieses Middlevers gelöst werden. Jetzt können Sie zumindest den Ausnahmelink an Ihren Teamkollegen übergeben, wenn Sie einen gemeinsam genutzten dedizierten Server verwenden.

Fazit


Ich hoffe, die Entwickler, die diesen Artikel lesen, haben ein interessantes und nützliches Tool für sich gefunden. Für mich ist dieser Artikel teilweise ein Test für die Nützlichkeit seiner Entwicklungen und Artikel darüber. Ich habe einige fertigere, coolere Projekte, von denen ich der Habr-Community erzählen möchte.

Source: https://habr.com/ru/post/de469731/


All Articles