HttpResponseException auslösen oder Request.CreateErrorResponse zurückgeben?

172

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

  1. Sollte ich immer HttpResponseMessageanstelle eines konkreten Domänenmodells verwenden, damit die Nachricht angepasst werden kann?
  2. Kann die Nachricht angepasst werden, wenn Sie ein konkretes Domänenmodell zurückgeben?

Fragen zu Fall Nr. 2,3,4

  1. 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.
  2. Was ist der Unterschied zwischen Werfen HttpResponseExceptionvs Request.CreateErrorResponse? Die Ausgabe an den Client scheint identisch zu sein ...
  3. 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);
}
zam6ak
quelle
6
@Mike Wasson Welchen Ansatz würden Sie als Autor des verlinkten Artikels verfolgen?
zam6ak

Antworten:

102

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:

GlobalConfiguration.Configuration.Filters.Add(
    new UnhandledExceptionFilterAttribute()
    .Register<KeyNotFoundException>(HttpStatusCode.NotFound)

    .Register<SecurityException>(HttpStatusCode.Forbidden)

    .Register<SqlException>(
        (exception, request) =>
        {
            var sqlException = exception as SqlException;

            if (sqlException.Number > 50000)
            {
                var response            = request.CreateResponse(HttpStatusCode.BadRequest);
                response.ReasonPhrase   = sqlException.Message.Replace(Environment.NewLine, String.Empty);

                return response;
            }
            else
            {
                return request.CreateResponse(HttpStatusCode.InternalServerError);
            }
        }
    )
);

UnhandledExceptionFilterAttribute-Klasse:

using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web.Http.Filters;

namespace Sample
{
    /// <summary>
    /// Represents the an attribute that provides a filter for unhandled exceptions.
    /// </summary>
    public class UnhandledExceptionFilterAttribute : ExceptionFilterAttribute
    {
        #region UnhandledExceptionFilterAttribute()
        /// <summary>
        /// Initializes a new instance of the <see cref="UnhandledExceptionFilterAttribute"/> class.
        /// </summary>
        public UnhandledExceptionFilterAttribute() : base()
        {

        }
        #endregion

        #region DefaultHandler
        /// <summary>
        /// Gets a delegate method that returns an <see cref="HttpResponseMessage"/> 
        /// that describes the supplied exception.
        /// </summary>
        /// <value>
        /// A <see cref="Func{Exception, HttpRequestMessage, HttpResponseMessage}"/> delegate method that returns 
        /// an <see cref="HttpResponseMessage"/> that describes the supplied exception.
        /// </value>
        private static Func<Exception, HttpRequestMessage, HttpResponseMessage> DefaultHandler = (exception, request) =>
        {
            if(exception == null)
            {
                return null;
            }

            var response            = request.CreateResponse<string>(
                HttpStatusCode.InternalServerError, GetContentOf(exception)
            );
            response.ReasonPhrase   = exception.Message.Replace(Environment.NewLine, String.Empty);

            return response;
        };
        #endregion

        #region GetContentOf
        /// <summary>
        /// Gets a delegate method that extracts information from the specified exception.
        /// </summary>
        /// <value>
        /// A <see cref="Func{Exception, String}"/> delegate method that extracts information 
        /// from the specified exception.
        /// </value>
        private static Func<Exception, string> GetContentOf = (exception) =>
        {
            if (exception == null)
            {
                return String.Empty;
            }

            var result  = new StringBuilder();

            result.AppendLine(exception.Message);
            result.AppendLine();

            Exception innerException = exception.InnerException;
            while (innerException != null)
            {
                result.AppendLine(innerException.Message);
                result.AppendLine();
                innerException = innerException.InnerException;
            }

            #if DEBUG
            result.AppendLine(exception.StackTrace);
            #endif

            return result.ToString();
        };
        #endregion

        #region Handlers
        /// <summary>
        /// Gets the exception handlers registered with this filter.
        /// </summary>
        /// <value>
        /// A <see cref="ConcurrentDictionary{Type, Tuple}"/> collection that contains 
        /// the exception handlers registered with this filter.
        /// </value>
        protected ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>> Handlers
        {
            get
            {
                return _filterHandlers;
            }
        }
        private readonly ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>> _filterHandlers = new ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>>();
        #endregion

        #region OnException(HttpActionExecutedContext actionExecutedContext)
        /// <summary>
        /// Raises the exception event.
        /// </summary>
        /// <param name="actionExecutedContext">The context for the action.</param>
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            if(actionExecutedContext == null || actionExecutedContext.Exception == null)
            {
                return;
            }

            var type    = actionExecutedContext.Exception.GetType();

            Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> registration = null;

            if (this.Handlers.TryGetValue(type, out registration))
            {
                var statusCode  = registration.Item1;
                var handler     = registration.Item2;

                var response    = handler(
                    actionExecutedContext.Exception.GetBaseException(), 
                    actionExecutedContext.Request
                );

                // Use registered status code if available
                if (statusCode.HasValue)
                {
                    response.StatusCode = statusCode.Value;
                }

                actionExecutedContext.Response  = response;
            }
            else
            {
                // If no exception handler registered for the exception type, fallback to default handler
                actionExecutedContext.Response  = DefaultHandler(
                    actionExecutedContext.Exception.GetBaseException(), actionExecutedContext.Request
                );
            }
        }
        #endregion

        #region Register<TException>(HttpStatusCode statusCode)
        /// <summary>
        /// Registers an exception handler that returns the specified status code for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to register a handler for.</typeparam>
        /// <param name="statusCode">The HTTP status code to return for exceptions of type <typeparamref name="TException"/>.</param>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception handler has been added.
        /// </returns>
        public UnhandledExceptionFilterAttribute Register<TException>(HttpStatusCode statusCode) 
            where TException : Exception
        {

            var type    = typeof(TException);
            var item    = new Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>(
                statusCode, DefaultHandler
            );

            if (!this.Handlers.TryAdd(type, item))
            {
                Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> oldItem = null;

                if (this.Handlers.TryRemove(type, out oldItem))
                {
                    this.Handlers.TryAdd(type, item);
                }
            }

            return this;
        }
        #endregion

        #region Register<TException>(Func<Exception, HttpRequestMessage, HttpResponseMessage> handler)
        /// <summary>
        /// Registers the specified exception <paramref name="handler"/> for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to register the <paramref name="handler"/> for.</typeparam>
        /// <param name="handler">The exception handler responsible for exceptions of type <typeparamref name="TException"/>.</param>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception <paramref name="handler"/> 
        /// has been added.
        /// </returns>
        /// <exception cref="ArgumentNullException">The <paramref name="handler"/> is <see langword="null"/>.</exception>
        public UnhandledExceptionFilterAttribute Register<TException>(Func<Exception, HttpRequestMessage, HttpResponseMessage> handler) 
            where TException : Exception
        {
            if(handler == null)
            {
              throw new ArgumentNullException("handler");
            }

            var type    = typeof(TException);
            var item    = new Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>(
                null, handler
            );

            if (!this.Handlers.TryAdd(type, item))
            {
                Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> oldItem = null;

                if (this.Handlers.TryRemove(type, out oldItem))
                {
                    this.Handlers.TryAdd(type, item);
                }
            }

            return this;
        }
        #endregion

        #region Unregister<TException>()
        /// <summary>
        /// Unregisters the exception handler for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to unregister handlers for.</typeparam>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception handler 
        /// for exceptions of type <typeparamref name="TException"/> has been removed.
        /// </returns>
        public UnhandledExceptionFilterAttribute Unregister<TException>()
            where TException : Exception
        {
            Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> item = null;

            this.Handlers.TryRemove(typeof(TException), out item);

            return this;
        }
        #endregion
    }
}

Quellcode finden Sie auch hier .

Oppositionell
quelle
Beeindruckend! :) Das mag für kleinere Projekte ein bisschen viel sein, ist aber trotzdem sehr schön ... Übrigens, warum CreateResponse anstelle von CreateErrorResponse in DefaultHandler?
zam6ak
Ich habe versucht, die Fehlerdetails (im Text serialisiert) von der Grundphrase zu trennen. Sie könnten jedoch sicherlich CreateErrorResponse verwenden, wenn dies sinnvoller wäre als bei der Modellbindung.
Oppositionelle
1
Da Sie den Filter mit nur einer Codezeile registrieren können, ist er meiner Meinung nach für fast jeden Projekttyp geeignet. Wir haben den Filter in einer Klassenbibliothek, die in unserem internen NuGet-Feed veröffentlicht wird, sodass Entwickler ihn problemlos verwenden können.
Oppositionelle
Was verwenden Sie für Wachen (einheimische oder Drittanbieter)?
Zam6ak
Hoemgrown, ich habe seine Verwendung im obigen Beispiel entfernt. Die Guard-Klasse bietet eine Reihe statischer Methoden, die verhindern oder überprüfen, ob eine Zusicherung erfüllt wurde. Siehe codepaste.net/5oc1if (Guard) und codepaste.net/nsrsei (DelegateInfo), wenn Sie die Implementierung wünschen.
Oppositionelle
23

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

private void ThrowResponseException(HttpStatusCode statusCode, string message)
{
    var errorResponse = Request.CreateErrorResponse(statusCode, message);
    throw new HttpResponseException(errorResponse);
}

und rufen Sie es einfach mit dem entsprechenden Statuscode und der entsprechenden Nachricht auf

Joe King
quelle
4
Dies ist die richtige Antwort. Sie wird mit dem Format "Nachricht" als Schlüsselwertpaar im Hauptteil geliefert. So sehe ich normalerweise andere Frameworks und Sprachen
MobileMon
Ich habe eine kleine Frage zu diesem Ansatz. Ich verwende die Nachricht mit der {{}} -Syntax auf der angleJS-Seite. Wenn ich Wagenrückläufe verlasse, werden sie in der Nachricht als n \ r \ angezeigt. Was ist der richtige Weg, um sie zu erhalten?
Naomi
Ich habe diesen Ansatz ausprobiert. Ich habe es getan throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!")), aber in Fiddler wird der Status 500 (nicht 400) angezeigt. Irgendeine Idee warum?
Sam
Der Unterschied ist die übergeordnete Funktion des Fehlers. Hier ist die ThrowResponseException für jede Ausnahme in der Anwendung. Aber sollte die eigentliche Funktion sein, die die Ausnahme
Serge
15

Fall 1

  1. Es gibt nicht unbedingt andere Stellen in der Pipeline, an denen die Antwort geändert werden kann (Aktionsfilter, Nachrichtenhandler).
  2. Siehe oben - Wenn die Aktion jedoch ein Domänenmodell zurückgibt, können Sie die Antwort innerhalb der Aktion nicht ändern .

Fälle Nr. 2-4

  1. Die Hauptgründe, um HttpResponseException auszulösen, sind:
    • Wenn Sie ein Domänenmodell zurückgeben, aber Fehlerfälle behandeln müssen,
    • um Ihre Controller-Logik zu vereinfachen, indem Sie Fehler als Ausnahmen behandeln
  2. Diese sollten gleichwertig sein; HttpResponseException kapselt eine HttpResponseMessage, die als HTTP-Antwort zurückgegeben wird.

    Beispiel: Fall 2 könnte wie folgt umgeschrieben werden

    public HttpResponseMessage Get(string id)
    {
        HttpResponseMessage response;
        var customer = _customerService.GetById(id);
        if (customer == null)
        {
            response = new HttpResponseMessage(HttpStatusCode.NotFound);
        }
        else
        {
            response = Request.CreateResponse(HttpStatusCode.OK, customer);
            response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
        }
        return response;
    }

    ... aber wenn Ihre Controller-Logik komplizierter ist, kann das Auslösen einer Ausnahme den Code-Fluss vereinfachen.

  3. 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.

Mike Wasson
quelle
Der Ansatz, den ich gewählt habe, besteht darin, nur Ausnahmen von den API-Controller-Aktionen auszulösen. Ich habe einen Ausnahmefilter registriert, der die Ausnahme verarbeitet und eine entsprechende Antwort auf den Aktionsausführungskontext festlegt. Der Filter ist steckbar, sodass ich Handler für bestimmte Arten von Ausnahmen registrieren kann, bevor ich den Filter mit globaler Konfiguration registriere. Auf diese Weise kann ich eine zentralisierte Ausnahmebehandlung durchführen, anstatt sie auf die Controller zu verteilen.
Oppositionelle
@Oppositional Gibt es eine Chance, dass Sie bereit sind, Ihren Ausnahmefilter zu teilen? Vielleicht als Gist oder auf einer Code-Share-Site wie CodePaste?
Paige Cook
@ Mike Wasson Würden Sie sagen, dass "Rückgabefehlerantwort" häufiger vorkommt als "Ausnahme auslösen"? Ich verstehe funktional, dass das Endergebnis das gleiche sein kann (ist?), Aber ich frage mich, warum nicht einfach die gesamte Steuerungslogik in die Try / Catch- und Return-Fehlerantwort einbezogen wird.
Zam6ak
15

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.

user2864740
quelle
2
Ich verstehe, warum ArgumentExceptions in einem Controller logischerweise eine 400 ist, aber was ist mit ArgumentExceptions tiefer im Stapel? Es wäre nicht unbedingt richtig, diese in 400 umzuwandeln. Wenn Sie jedoch einen Filter haben, der alle ArgumentExceptions 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.
Cmeeren
@cmeeren In dem Code, mit dem ich es zu tun hatte, hat das meiste die Ausnahme abgefangen und sie in jeder Webmethode in die HttpResponse [Ausnahme / Nachricht] umgewandelt. Beide Fälle sind insofern gleich , als es darum geht, mit internen Ausnahmen etwas anderes zu tun, als mit der abgefangenen internen Ausnahme "etwas" zu tun: Ich empfehle, eine geeignete Umbruchausnahme auszulösen, die noch weiter oben behandelt wird Stapel.
user2864740
@cmeeren Nach Aktualisierungen lösen die meisten unserer Web-Einstiegspunkte eine spezielle Ableitung (Nicht-HttpResponseException, die entsprechenden Antwortcodes zugeordnet ist und / oder diesen zugeordnet ist) auf Verwendungsfehler aus. Der Uniform-Handler könnte eine Stapelinspektion durchführen (icky, aber es funktioniert mit einiger Sorgfalt), um festzustellen, von welcher Ebene die Ausnahme kam - dh. decken 99% der Fälle ab, in denen die Behandlung nicht verfeinert wurde - oder antworten Sie einfach mit 500 auf interne Fehler. Der springende Punkt bei HttpResponseException ist, dass die nützliche Pipeline-Verarbeitung umgangen wird.
user2864740
9

Ein anderer Fall für die Verwendung HttpResponseExceptionanstelle von Response.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.CreateResponsewird die Transaktion nicht zurückgesetzt, während mit einer Ausnahme eine Ausnahme ausgelöst wird.

Rob Gray
quelle
3

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

[System.Web.Http.Description.ResponseType(typeof(CustomReturnedType))] 

Attribut der Methode, damit die Hilfe korrekt generiert wird. Dann

return Request.CreateResponse<CustomReturnedType>(objCustomeReturnedType) 

oder auf Fehler

return Request.CreateErrorResponse( System.Net.HttpStatusCode.InternalServerError, new Exception("An Error Ocurred"));

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.

Andrew DeVries
quelle
0

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

Andy Cohen
quelle
0

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

public ProviderCollection GetProviders(string providerName)
{
   try
   {
      return _providerPresenter.GetProviders(providerName);
   }
   catch (BadInputValidationException badInputValidationException)
   {
     throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest,
                                          badInputValidationException.Result));
   }
}

Resultist eine Klasse, die Fehlerdetails enthält, während ProviderCollectionmein Happy Path-Ergebnis ist.

NickBeaugié
quelle
0

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

public override void OnException(HttpActionExecutedContext actionExecutedContext) {
   if (actionExecutedContext == null || actionExecutedContext.Exception == null) {
      return;
   }

   var type = actionExecutedContext.Exception.GetType();

   Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> registration = null;

   if (!this.Handlers.TryGetValue(type, out registration)) {
      //tento di vedere se ho registrato qualche eccezione che eredita dal tipo di eccezione sollevata (in ordine di registrazione)
      foreach (var item in this.Handlers.Keys) {
         if (type.IsSubclassOf(item)) {
            registration = this.Handlers[item];
            break;
         }
      }
   }

   //se ho trovato un tipo compatibile, uso la sua gestione
   if (registration != null) {
      var statusCode = registration.Item1;
      var handler = registration.Item2;

      var response = handler(
         actionExecutedContext.Exception.GetBaseException(),
         actionExecutedContext.Request
      );

      // Use registered status code if available
      if (statusCode.HasValue) {
         response.StatusCode = statusCode.Value;
      }

      actionExecutedContext.Response = response;
   }
   else {
      // If no exception handler registered for the exception type, fallback to default handler
      actionExecutedContext.Response = DefaultHandler(actionExecutedContext.Exception.GetBaseException(), actionExecutedContext.Request
      );
   }
}

Der Kern ist diese Schleife, in der ich überprüfe, ob der Ausnahmetyp eine Unterklasse eines registrierten Typs ist.

foreach (var item in this.Handlers.Keys) {
    if (type.IsSubclassOf(item)) {
        registration = this.Handlers[item];
        break;
    }
}

my2cents

Nicht wichtig
quelle