Wyrzucić HttpResponseException lub zwrócić Request.CreateErrorResponse?

172

Po przejrzeniu artykułu Obsługa wyjątków w ASP.NET Web API jestem trochę zdezorientowany, kiedy zgłosić wyjątek, a kiedy zwrócić odpowiedź na błąd. Zastanawiam się również, czy można zmodyfikować odpowiedź, gdy metoda zwraca model specyficzny dla domeny zamiast HttpResponseMessage...

Podsumowując, oto moje pytania, po których następuje kod z numerami przypadków:

pytania

Pytania dotyczące przypadku nr 1

  1. Czy należy zawsze używać HttpResponseMessagezamiast konkretnego modelu domeny, aby można było dostosować wiadomość?
  2. Czy można dostosować wiadomość, jeśli zwracasz konkretny model domeny?

Pytania dotyczące przypadku # 2, 3, 4

  1. Czy powinienem zgłaszać wyjątek lub zwracać odpowiedź na błąd? Jeśli odpowiedź brzmi „to zależy”, czy możesz podać sytuacje / przykłady, kiedy należy używać jednej lub drugiej.
  2. Jaka jest różnica między rzucaniem HttpResponseExceptiona rzucaniem Request.CreateErrorResponse? Dane wyjściowe dla klienta wydają się identyczne ...
  3. Czy powinienem zawsze HttpError„zawijać” komunikaty odpowiedzi błędami (niezależnie od tego, czy zostanie zgłoszony wyjątek, czy zwrócona odpowiedź na błąd)?

Przykłady przypadków

// 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;
}

Aktualizacja

Aby lepiej przedstawić przypadki # 2, 3, 4, poniższy fragment kodu podkreśla kilka opcji, które „mogą się zdarzyć”, gdy klient nie zostanie znaleziony ...

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
źródło
6
@Mike Wasson Jakie podejście byś wybrał jako autor połączonego artykułu?
zam6ak

Odpowiedzi:

102

Podejście, które podjąłem, polega na rzucaniu wyjątków z akcji kontrolera interfejsu API i zarejestrowaniu filtru wyjątków, który przetwarza wyjątek i ustawia odpowiednią odpowiedź w kontekście wykonywania akcji.

Filtr udostępnia płynny interfejs, który umożliwia rejestrowanie programów obsługi dla określonych typów wyjątków przed zarejestrowaniem filtru w konfiguracji globalnej.

Użycie tego filtru umożliwia scentralizowaną obsługę wyjątków zamiast rozpraszania ich na akcje kontrolera. Są jednak przypadki, w których wyłapię wyjątki w akcji kontrolera i zwrócę określoną odpowiedź, jeśli nie ma sensu scentralizowanie obsługi tego konkretnego wyjątku.

Przykładowa rejestracja filtra:

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, klasa:

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
    }
}

Kod źródłowy można również znaleźć tutaj .

Opozycyjny
źródło
łał! :) To może być trochę za dużo dla mniejszych projektów, ale nadal bardzo fajne ... BTW, dlaczego CreateResponse zamiast CreateErrorResponse w DefaultHandler?
zam6ak
Próbowałem oddzielić szczegóły błędu (serializowane w treści) od frazy powodu; ale z pewnością możesz użyć CreateErrorResponse, jeśli ma to większy sens, jak w przypadku wiązania modelu.
Oppositional
1
Ponieważ możesz zarejestrować filtr za pomocą tylko jednej linii kodu, myślę, że jest odpowiedni dla prawie każdego typu projektu. Mamy filtr w bibliotece klas opublikowanej w naszym wewnętrznym kanale NuGet, dzięki czemu jest łatwy w użyciu dla deweloperów.
Opozycyjny
Czego używasz dla strażników (własnych lub zewnętrznych)?
zam6ak
Hoemgrown, usunąłem jego użycie w powyższym przykładzie. Klasa Guard udostępnia zestaw metod statycznych, które chronią przed spełnieniem asercji lub sprawdzają, czy została ona spełniona. Zobacz codepaste.net/5oc1if (Guard) i codepaste.net/nsrsei (DelegateInfo), jeśli chcesz implementację.
Opozycyjny
23

Jeśli nie zwracasz HttpResponseMessage i zamiast tego zwracasz bezpośrednio klasy jednostki / modelu, podejście, które uznałem za przydatne, polega na dodaniu następującej funkcji narzędzia do mojego kontrolera

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

i po prostu zadzwoń do niego, podając odpowiedni kod statusu i komunikat

Joe King
źródło
4
To jest właściwa odpowiedź, ma format „Wiadomość” jako para klucz-wartość w treści. W ten sposób widzę, jak robią to inne frameworki i języki
MobileMon
Mam małe pytanie dotyczące tego podejścia. Konsumuję wiadomość przy użyciu składni {{}} na stronie angularJS. Jeśli zostawię powroty karetki, w wiadomości pojawią się jako n \ r \. Jaki jest właściwy sposób ich zachowania?
Naomi
Próbowałem tego podejścia. Zrobiłem throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!")), ale w Fiddlera pokazuje status 500 (nie 400). Każdy pomysł, dlaczego?
Sam,
różnica jest funkcją macierzystą błędu. Oto wyjątek ThrowResponseException dla każdego wyjątku w aplikacji. Ale to powinna być prawdziwa funkcja rzucająca wyjątek ...
Serge
15

Przypadek 1

  1. Niekoniecznie, istnieją inne miejsca w potoku do modyfikowania odpowiedzi (filtry akcji, programy obsługi wiadomości).
  2. Zobacz powyżej - ale jeśli akcja zwraca model domeny, nie możesz zmodyfikować odpowiedzi wewnątrz akcji.

Przypadki # 2-4

  1. Głównymi powodami rzucania HttpResponseException są:
    • jeśli zwracasz model domeny, ale musisz obsłużyć przypadki błędów,
    • uprościć logikę kontrolera, traktując błędy jako wyjątki
  2. Powinny one być równoważne; HttpResponseException hermetyzuje HttpResponseMessage, czyli to, co jest zwracane jako odpowiedź HTTP.

    np. przypadek nr 2 można przepisać jako

    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;
    }

    ... ale jeśli logika kontrolera jest bardziej skomplikowana, zgłoszenie wyjątku może uprościć przepływ kodu.

  3. HttpError zapewnia spójny format treści odpowiedzi i może być serializowany do JSON / XML / etc, ale nie jest to wymagane. np. możesz nie chcieć dołączać treści encji do odpowiedzi lub możesz chcieć użyć innego formatu.

Mike Wasson
źródło
Podejście, które zastosowałem, polega na rzucaniu wyjątków z akcji kontrolera interfejsu API i zarejestrowałem filtr wyjątków, który przetwarza wyjątek i ustawia odpowiednią odpowiedź na kontekst wykonywania akcji. Filtr jest „podłączalny”, tak że mogę zarejestrować programy obsługi dla określonych typów wyjątków przed zarejestrowaniem filtru w konfiguracji globalnej. To pozwala mi na scentralizowaną obsługę wyjątków zamiast rozprzestrzeniania jej na kontrolery.
Oppositional
@Oppositional Czy jest szansa, że ​​zechcesz udostępnić swój filtr wyjątków? Może jako Gist lub w witrynie udostępniania kodu, takiej jak CodePaste?
Paige Cook
@Mike Wasson, czy powiedziałbyś, że „zwracanie odpowiedzi na błąd” jest bardziej powszechnym podejściem niż „rzut wyjątku”? Rozumiem, że funkcjonalnie wynik końcowy może być (jest?) Taki sam, ale zastanawiam się, dlaczego nie uwzględnić po prostu całej logiki kontrolera w odpowiedzi na błąd try / catch i zwróć odpowiednio?
zam6ak
15

Nie zgłaszaj HttpResponseException ani nie zwracaj HttpResponesMessage w przypadku błędów - z wyjątkiem sytuacji, gdy celem jest zakończenie żądania z tym dokładnym wynikiem .

Wyjątki HttpResponseException nieobsługiwane tak samo, jak inne wyjątki . Nie są przechwytywane przez filtry wyjątków . Nie są przechwytywane w programie obsługi wyjątków . Są przebiegłym sposobem na wślizgnięcie się w HttpResponseMessage podczas kończenia przepływu wykonywania bieżącego kodu.

O ile kod nie jest kodem infrastruktury zależnym od tej specjalnej nieobsługiwanej obsługi, unikaj używania typu HttpResponseException!

HttpResponseMessage nie są wyjątkami. Nie przerywają wykonywania bieżącego kodu. Mogą one nie być filtrowane jako wyjątki. Mogą one nie być zalogowany jako wyjątki. Reprezentują prawidłowy wynik - nawet odpowiedź 500 jest „prawidłową odpowiedzią bez wyjątku”!


Uprość życie:

Gdy wystąpi wyjątek / błąd, wyślij normalny wyjątek .NET - lub niestandardowy typ wyjątku aplikacji ( nie pochodzący z HttpResponseException) z żądanymi właściwościami „błąd / odpowiedź http”, takimi jak kod stanu - zgodnie z normalnym wyjątkiem obsługa .

Użyj filtrów wyjątków / programów obsługi wyjątków / rejestratorów wyjątków, aby zrobić coś odpowiedniego w tych wyjątkowych przypadkach: zmienić / dodać kody statusu? dodać identyfikatory śledzenia? dołączyć ślady stosu? log?

Unikając HttpResponseException, obsługa „wyjątkowych przypadków” jest jednolita i może być obsługiwana jako część odsłoniętego potoku! Na przykład można łatwo i jednolicie zmienić „NotFound” w 404, a „ArgumentException” w 400, a „NullReference” w 500 z wyjątkami na poziomie aplikacji - jednocześnie umożliwiając rozszerzalność w celu zapewnienia „podstaw”, takich jak rejestrowanie błędów.

user2864740
źródło
2
Rozumiem, dlaczego ArgumentExceptionw kontrolerze logicznie byłoby 400, ale co z ArgumentExceptiongłębszymi w stosie? Niekoniecznie byłoby poprawne zamienianie ich na 400, ale jeśli masz filtr, który konwertuje wszystkie ArgumentExceptions na 400, jedynym sposobem uniknięcia tego jest złapanie wyjątku w kontrolerze i ponowne wrzucenie czegoś innego, co wydaje się aby pokonać cel jednolitej obsługi wyjątków w filtrze lub podobnym.
cmeeren
@cmeeren W kodzie, z którym miałem do czynienia, większość z nich wychwyciła wyjątek i przekształciła go w HttpResponse [Exception / Message] w każdej metodzie sieciowej. Oba przypadki są takie same , pod tym względem, że jeśli chodzi o zrobienie czegoś innego z wewnętrznymi wyjątkami, co jest wykonywane „coś” z przechwyconym wewnętrznym wyjątkiem: zalecam, aby wynikiem było zgłoszenie odpowiedniego wyjątku opakowującego, który jest nadal obsługiwany wyżej stos.
user2864740
@cmeeren Po aktualizacjach większość naszych punktów wejścia w sieci WWW generuje specjalne pochodne (inne niż HttpResponseException, które mają i lub są mapowane na odpowiednie kody odpowiedzi) na błędy użytkowania. Program obsługi munduru mógłby przeprowadzić inspekcję stosu (nieprzyjemne, ale działa z pewną ostrożnością), aby określić, z jakiego poziomu pochodzi wyjątek - tj. obejmują 99% spraw, które nie wymagają bardziej dopracowanej obsługi - lub po prostu odpowiadają 500 za błędy wewnętrzne. Istotą HttpResponseException jest to, że omija przydatne przetwarzanie potoków.
user2864740
9

Innym przypadkiem, w którym należy użyć HttpResponseExceptionzamiast Response.CreateResponse(HttpStatusCode.NotFound)lub innego kodu statusu błędu, jest sytuacja , gdy masz transakcje w filtrach akcji i chcesz, aby transakcje zostały wycofane podczas zwracania odpowiedzi błędu do klienta.

Użycie Response.CreateResponsenie spowoduje wycofania transakcji, podczas gdy zgłoszenie wyjątku spowoduje.

Rob Grey
źródło
3

Chcę podkreślić, że z mojego doświadczenia wynika, że ​​jeśli wyrzucę HttpResponseException zamiast zwracać HttpResponseMessage w metodzie webapi 2, to jeśli wywołanie zostanie wykonane natychmiast do IIS Express, przekroczy limit czasu lub zwróci 200, ale z błędem HTML w odpowiedź. Najłatwiejszym sposobem sprawdzenia tego jest wywołanie metody $ .ajax do metody, która zgłasza wyjątek HttpResponseException, aw errorCallBack w Ajax wykonuje natychmiastowe wywołanie innej metody lub nawet prostej strony http. Zauważysz, że natychmiastowe połączenie się nie powiedzie. Jeśli dodasz punkt przerwania lub settimeout () w wywołaniu błędu, aby opóźnić drugie wywołanie o sekundę lub dwie, dając serwerowi czas na odzyskanie, działa poprawnie.

Aktualizacja:Główną przyczyną dziwnego limitu czasu połączenia Ajax jest to, że jeśli wywołanie AJAX jest wykonane wystarczająco szybko, używane jest to samo połączenie TCP. Podnosiłem eter błędu 401, zwracając HttpResonseMessage lub rzucając HTTPResponseException, który został zwrócony do wywołania ajax w przeglądarce. Ale wraz z tym wywołaniem MS zwracał błąd Object Not Found Error, ponieważ w aplikacji Startup.Auth.vb została włączona funkcja.UserCookieAuthentication, więc próbowała zwrócić przechwycić odpowiedź i dodać przekierowanie, ale wystąpił błąd: Object nie Instance of an Object. Ten błąd był w formacie html, ale został dołączony do odpowiedzi po fakcie, więc tylko wtedy, gdy wywołanie Ajax zostało wykonane wystarczająco szybko i to samo używane połączenie TCP, zostało zwrócone do przeglądarki, a następnie zostało dołączone na początku następnego wywołania. Z jakiegoś powodu Chrome właśnie przekroczył limit czasu, Fiddler pucked ze względu na mieszankę json i htm, ale firefox zwrócił prawdziwy błąd. To dziwne, ale sniffer pakietów lub firefox był jedynym sposobem na wyśledzenie tego.

Należy również zauważyć, że jeśli używasz pomocy interfejsu API sieci Web do generowania automatycznej pomocy i zwracasz HttpResponseMessage, powinieneś dodać

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

atrybut do metody, aby pomoc była generowana poprawnie. Następnie

return Request.CreateResponse<CustomReturnedType>(objCustomeReturnedType) 

lub w przypadku błędu

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

Mam nadzieję, że pomoże to komuś, kto może uzyskać losowy limit czasu lub serwer nie jest dostępny natychmiast po wyrzuceniu HttpResponseException.

Również zwrócenie HttpResponseException ma tę dodatkową zaletę, że nie powoduje przerwania programu Visual Studio w przypadku nieobsługiwanego wyjątku użytecznego, gdy zwracany błąd to AuthToken musi zostać odświeżony w aplikacji z jedną stroną.

Aktualizacja: Wycofuję moje oświadczenie o przekroczeniu limitu czasu w IIS Express, to był błąd w wywołaniu zwrotnym Ajax po stronie klienta, okazuje się, że Ajax 1.8 zwraca $ .ajax () i zwraca $ .ajax. (). Then () obie obietnice zwrotu, ale nie ta sama obietnica łańcuchowa then () zwraca nową obietnicę, która spowodowała, że ​​kolejność wykonania była błędna. Więc kiedy obietnica then () zakończyła się, upłynął limit czasu skryptu. Dziwny problem, ale nie problem z wyrażeniem IIS, problem między klawiaturą a krzesłem.

Andrew DeVries
źródło
0

O ile wiem, czy zgłosisz wyjątek, czy zwrócisz Request.CreateErrorResponse, wynik jest taki sam. Jeśli spojrzysz na kod źródłowy System.Web.Http.dll, zobaczysz to samo. Spójrz na to ogólne podsumowanie i bardzo podobne rozwiązanie, które stworzyłem: Web Api, HttpError i zachowanie wyjątków

Andy Cohen
źródło
0

W sytuacjach błędów chciałem zwrócić określoną klasę szczegółów błędu, w jakimkolwiek formacie żądanym przez klienta zamiast obiektu happy path.

Chcę, aby moje metody kontrolera zwracały obiekt happy path specyficzny dla domeny i zgłaszały wyjątek w przeciwnym razie.

Problem polegał na tym, że konstruktory HttpResponseException nie zezwalają na obiekty domeny.

Oto, co ostatecznie wymyśliłem

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

Resultto klasa, która zawiera szczegóły błędu, a ProviderCollectionwynik mojej szczęśliwej ścieżki.

NickBeaugié
źródło
0

Podoba mi się odpowiedź opozycji

W każdym razie potrzebowałem sposobu, aby złapać odziedziczony wyjątek, a to rozwiązanie nie zaspokaja wszystkich moich potrzeb.

W końcu zmieniłem sposób, w jaki obsługuje OnException i to jest moja wersja

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
      );
   }
}

Rdzeniem jest ta pętla, w której sprawdzam, czy typ wyjątku jest podklasą zarejestrowanego typu.

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

my2cents

Nieważne
źródło