2014-06-13 13 views
14

Zajmuję się tworzeniem aplikacji Web API 2 i obecnie staram się formatować odpowiedzi błędów w jednolity sposób (aby konsument wiedział również, jaki obiekt danych/strukturę, którą mogą sprawdzić, aby uzyskać więcej informacji o błędach). To, co mam do tej pory:Jednolite, spójne odpowiedzi na błędy z ASP.Net Web API 2

{ 
    "Errors": 
    [ 
     { 
      "ErrorType":5003, 
      "Message":"Error summary here", 
      "DeveloperAction":"Some more detail for API consumers (in some cases)", 
      "HelpUrl":"link to the docs etc." 
     } 
    ] 
} 

Działa to dobrze dla wyjątków rzucanych przez samego wniosku (tj wewnątrz kontrolerów). Jeśli jednak użytkownik zażąda złego URI (i otrzyma 404) lub użyje niewłaściwego czasownika (i otrzyma 405) itp., Web Api 2 wypisze domyślny komunikat o błędzie, np.

{ 
    Message: "No HTTP resource was found that matches the request URI 'http://localhost/abc'." 
} 

Czy jest jakiś sposób na schwytanie tego rodzaju błędów (404, 405 itd) i formatowanie ich w odpowiedzi błędu w pierwszym przykładzie powyżej?

Do tej pory próbowałem:

  • niestandardowy ExceptionAttribute inherting ExceptionFilterAttribute
  • niestandardowy ControllerActionInvoker inherting ApiControllerActionInvoker
  • IExceptionHandler (nowa globalna Error Handling z funkcji Web API 2,1)

jednak żadne z tych podejść nie jest w stanie uchwycić tego rodzaju błędów (404, 405 itd.). Wszelkie pomysły na to, jak/jeśli można to osiągnąć?

... lub, czy podążam w tym kierunku w niewłaściwy sposób? Czy powinienem sformatować tylko odpowiedzi na błędy w moim konkretnym stylu dla błędów aplikacji/użytkowników i polegać na domyślnych odpowiedziach na błędy dla rzeczy takich jak 404?

+1

'Jeżeli tylko ja formacie odpowiedzi na błędy w moim konkretnym stylu dla błędów aplikacji/poziomu użytkownika i polegać na domyślnych odpowiedziach na błędy dla rzeczy takich jak 404? "... moja opinia brzmi tak –

+0

Coraz bardziej przechodzę do tego podejścia. Dziękuję zarówno za wspaniałe odpowiedzi/komentarze. –

Odpowiedz

8

Można zastąpić klasę abstrakcji DelegatingHandler i przechwycić odpowiedź do klienta. To da ci możliwość zwrotu tego, co chcesz.

Oto kilka informacji na ten temat. http://msdn.microsoft.com/en-us/library/system.net.http.delegatinghandler(v=vs.118).aspx

Oto plakat z potoku Web Api, który pokazuje, co można przesłonić. http://www.asp.net/posters/web-api/asp.net-web-api-poster.pdf

Utwórz klasę Handler tak aby zastąpić odpowiedź

public class MessageHandler1 : DelegatingHandler 
{ 
    protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken) 
    { 
     Debug.WriteLine("Process request"); 
     // Call the inner handler. 
     var response = base.SendAsync(request, cancellationToken); 

     Debug.WriteLine("Process response"); 
     if (response.Result.StatusCode == HttpStatusCode.NotFound) 
     { 
      //Create new HttpResponseMessage message 
     } 
     ; 
     return response; 
    } 
} 

W swojej klasie WebApiConfig.cs dodanie obsługi.

config.MessageHandlers.Add(new MessageHandler1()); 

UPDATE Jak Kiran wspomina w komentarzach można użyć OwinMiddleware przechwycić odpowiedź wraca do klienta. To działałoby dla MVC i Web Api działającego na dowolnym hoście.

Oto przykład, jak uzyskać odpowiedź i zmienić ją w zależności od klienta.

public class Startup 
{ 
    public void Configuration(IAppBuilder app) 
    { 
     app.Use(typeof(MyMiddleware)); 
    } 
} 

public class MyMiddleware : OwinMiddleware 
{ 
    public MyMiddleware(OwinMiddleware next) : base(next) { } 

    public override async Task Invoke(IOwinContext context) 
    { 
     await Next.Invoke(context); 
     if(context.Response.StatusCode== 404) 
     { 
      context.Response.StatusCode = 403; 
      context.Response.ReasonPhrase = "Blah"; 
     } 
    } 
} 
+0

Samo to nie wystarczy, ponieważ w niektórych przypadkach żądania nie mogą nawet wejść w interfejs Web API z powodu braku dopasowań trasy Web API (ten problem dotyczy scenariuszy hostowanych w IIS). Ten problem można obejść poprzez przechwycenie całej trasy na końcu tras Web API, które może wysłać niestandardowy komunikat o błędzie 404 ... Inne możliwe rozwiązania to posiadanie OwinMiddleware, które znajduje się wcześniej w potoku i w ten sposób masz wszystkie twoja logika w jednym miejscu (nie grałem z tym sam, ale domyślam się, że to powinno zadziałać) –

+0

Będzie trafiał DelegatingHandler dla wszystkich wywołań api, nawet jeśli trasa jest niepoprawna, ponieważ HttpRoutingDispatcher zostaje wywołany po DelegatingHandlers. Masz rację, że jeśli żądanie nie jest żądaniem API, nie trafi tego handler'a, ponieważ połączenia non-api są obsługiwane przez procedury obsługi nazw System.Web.Mvc. Przykład: http: // localhost/WebApplication1/api/invalid uderzy w ten moduł obsługi, ale http: // localhost/WebApplication1/invalid nie trafi tego handler'a ... zakładając, że wszystkie wywołania api są skonfigurowane tak, aby zaczynały się od http: // localhost/WebApplication1/api/ –

+0

Żeby było jasne ... Dopasowywanie tras w 'HttpRoutingDispatcher' dzieje się tylko w scenariuszach SelfHost i In-Memory ... –

2

mam zrobić w taki sam sposób jak @Dan H wspomniano

public class ApiGatewayHandler : DelegatingHandler 
{ 
    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 
    { 
     try 
     { 
      var response = await base.SendAsync(request, cancellationToken); 
      if (response.StatusCode == HttpStatusCode.NotFound) 
      { 
       var objectContent = response.Content as ObjectContent; 
       return await Task.FromResult(new ApiResult(HttpStatusCode.NotFound, VmsStatusCodes.RouteNotFound, "", objectContent == null ? null : objectContent.Value).Response()); 
      } 
      return response; 
     } 
     catch (System.Exception ex) 
     { 
      return await Task.FromResult(new ApiResult(HttpStatusCode.BadRequest, VmsStatusCodes.UnHandledError, ex.Message, "").Response()); 
     } 

    } 
} 

Dodany routingu jak poniżej, a teraz uderza catch spróbować nieprawidłowego adresu URL

config.Routes.MapHttpRoute(name: "DefaultApi",routeTemplate: "api/{controller}/{id}",defaults: new { id = RouteParameter.Optional }); 
     config.Routes.MapHttpRoute(name: "NotFound", routeTemplate: "api/{*paths}", defaults: new { controller = "ApiError", action = "NotFound" }); 
+0

Czy są jakieś zalety wydajności? –