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
- Czy należy zawsze używać
HttpResponseMessage
zamiast konkretnego modelu domeny, aby można było dostosować wiadomość? - Czy można dostosować wiadomość, jeśli zwracasz konkretny model domeny?
Pytania dotyczące przypadku # 2, 3, 4
- 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.
- Jaka jest różnica między rzucaniem
HttpResponseException
a rzucaniemRequest.CreateErrorResponse
? Dane wyjściowe dla klienta wydają się identyczne ... - 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);
}
Odpowiedzi:
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:
UnhandledExceptionFilterAttribute, klasa:
Kod źródłowy można również znaleźć tutaj .
źródło
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
i po prostu zadzwoń do niego, podając odpowiedni kod statusu i komunikat
źródło
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!"))
, ale w Fiddlera pokazuje status 500 (nie 400). Każdy pomysł, dlaczego?Przypadek 1
Przypadki # 2-4
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
... ale jeśli logika kontrolera jest bardziej skomplikowana, zgłoszenie wyjątku może uprościć przepływ kodu.
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.
źródło
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 nie są obsł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.
źródło
ArgumentException
w kontrolerze logicznie byłoby 400, ale co zArgumentException
głębszymi w stosie? Niekoniecznie byłoby poprawne zamienianie ich na 400, ale jeśli masz filtr, który konwertuje wszystkieArgumentException
s 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.Innym przypadkiem, w którym należy użyć
HttpResponseException
zamiastResponse.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.CreateResponse
nie spowoduje wycofania transakcji, podczas gdy zgłoszenie wyjątku spowoduje.źródło
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ć
atrybut do metody, aby pomoc była generowana poprawnie. Następnie
lub w przypadku błędu
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.źródło
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
źródło
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
Result
to klasa, która zawiera szczegóły błędu, aProviderCollection
wynik mojej szczęśliwej ścieżki.źródło
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
Rdzeniem jest ta pętla, w której sprawdzam, czy typ wyjątku jest podklasą zarejestrowanego typu.
my2cents
źródło