2012-07-09 21 views
14

Powiedzmy, że mam konstruktora, w którym inicjalizacja może potencjalnie rzucić wyjątek z przyczyn od niego niezależnych.Jak powinienem obsługiwać wyjątki w konstruktorze kontrolera w WebAPI?

FantasticApiController(IAwesomeGenerator awesome, 
    IBusinessRepository repository, IIceCreamFactory factory) 
{ 
     Awesome = awesome; 
     Repository = repository; 
     IceCream = factory.MakeIceCream(); 

     DoSomeInitialization(); // this can throw an exception 
} 

Zwykle, gdy działanie kontrolera w WebAPI zgłasza wyjątek mogę poradzić poprzez csutom ExceptionFilterAttribute:

public class CustomErrorHandler 
{ 
    public override void OnException(HttpActionExecutedContext context) 
    { 
     // Critical error, this is real bad. 
     if (context.Exception is BubonicPlagueException) 
     { 
      Log.Error(context.Exception, "CLOSE EVERYTHING!"); 
      Madagascar.ShutdownAllPorts(); 
     } 

     // No big deal, just show something user friendly 
     throw new HttpResponseException(new HttpResponseMessage 
     { 
      Content = new StringContent("Hey something bad happened. " + 
             "Not closing the ports though"), 
      StatusCode = HttpStatusCode.InternalServerError; 
     }); 
    } 

Więc jeśli mam mieć metodę BoardPlane API, który rzuca BubonicPlagueException, a następnie mój CustomerErrorHandler zamknie porty na Madagaskarze i zarejestruje je jako błąd zgodnie z oczekiwaniami. W innych przypadkach, gdy nie jest to naprawdę poważne, wyświetlam komunikat przyjazny dla użytkownika i zwracam 500 InternalServerError.

Ale w tych przypadkach, gdy DoSomeInitialization zgłasza wyjątek, nie robi to absolutnie nic. Jak obsługiwać wyjątki w konstruktorach kontrolerów WebAPI?

+0

Jedną z interesujących funkcji WebApi jest to, że można łatwo dostosować sposób, w jaki wyjątek jest zwracany do klienta. Błąd statusu i HTML w ramach tej samej metody działania. Oczywiście stracisz wszystko, jeśli wyjątek zostanie zgłoszony w konstruktorze. Myślę, że powinieneś tego unikać. Większość logiki należy umieścić w metodzie webapi, a nie w konstruktorze. Powiedziałem, że powinieneś użyć standardowej obsługi błędów asp.net, czyli konfigurowania stron błędów w Web.Config, lub przechwytywania zdarzenia onerror w globalnym asaxie –

Odpowiedz

13

Kontrolery WebApi są tworzone, a zatem konstruktory wywoływane za pośrednictwem HttpControllerActivators. Domyślnym aktywatorem jest System.Web.Http.Dispatcher.DefaultHttpControllerActivator.

Bardzo szorstki przykłady dla opcji 1 & 2 na github tutaj https://github.com/markyjones/StackOverflow/tree/master/ControllerExceptionHandling/src

Wariant 1 który działa całkiem dobrze wiąże się z wykorzystaniem kontenera DI (można używać jednego już dobrze). Użyłem Ninject do mojego przykładu i użyłem "Interceptorów" Read More do przechwytywania i próbowania/wychwytywania wywołań do metody Create na DefaultHttpControllerActivator. Znam przynajmniej AutoFac i Ninject że można zrobić coś podobny do przykładu z poniższym:

Tworzenie przechwytywacza

nie wiem co zakres żywotność swojej Madagaskarze oraz pozycje dziennika są jednak mogli dobrze być wstrzykiwany do swojego Interceptor

public class ControllerCreationInterceptor : Ninject.Extensions.Interception.IInterceptor 
{ 
    private ILog _log; 
    private IMadagascar _madagascar; 

    public ControllerCreationInterceptor(ILog log, IMadagascar madagascar) 
    { 
     _log = log; 
     _madagascar = madagascar; 
    } 

Ale trzymając się przykładowo w swoim pytaniu gdzie Log i Madagaskar są jakieś statyczne globalnego

public class ControllerCreationInterceptor : Ninject.Extensions.Interception.IInterceptor 
{ 

    public void Intercept(Ninject.Extensions.Interception.IInvocation invocation) 
    { 
     try 
     { 
      invocation.Proceed(); 
     } 
     catch(InvalidOperationException e) 
     { 
      if (e.InnerException is BubonicPlagueException) 
      { 
       Log.Error(e.InnerException, "CLOSE EVERYTHING!"); 
       Madagascar.ShutdownAllPorts(); 
       //DO SOMETHING WITH THE ORIGIONAL ERROR! 
      } 
      //DO SOMETHING WITH THE ORIGIONAL ERROR! 
     } 
    } 
} 

WRESZCIE Zarejestruj przechwytywacza w Global asax lub App_Start (NinjectWebCommon)

kernel.Bind<System.Web.Http.Dispatcher.IHttpControllerActivator>() 
      .To<System.Web.Http.Dispatcher.DefaultHttpControllerActivator>().Intercept().With<ControllerCreationInterceptor>(); 

Opcja 2 jest wdrożenie własnego kontrolera Activator wykonawczych interfejs IHttpControllerActivator i obsługi błędu w tworzeniu sterownika w Utwórz metodę.Można użyć deseniu dekorator zawinąć DefaultHttpControllerActivator:

public class YourCustomControllerActivator : IHttpControllerActivator 
{ 
    private readonly IHttpControllerActivator _default = new DefaultHttpControllerActivator(); 

    public YourCustomControllerActivator() 
    { 

    } 

    public System.Web.Http.Controllers.IHttpController Create(System.Net.Http.HttpRequestMessage request, System.Web.Http.Controllers.HttpControllerDescriptor controllerDescriptor, Type controllerType) 
    { 
     try 
     { 
      return _default.Create(request, controllerDescriptor, controllerType); 
     } 
     catch (InvalidOperationException e) 
     { 
      if (e.InnerException is BubonicPlagueException) 
      { 
       Log.Error(e.InnerException, "CLOSE EVERYTHING!"); 
       Madagascar.ShutdownAllPorts(); 
       //DO SOMETHING WITH THE ORIGIONAL ERROR! 
      } 
      //DO SOMETHING WITH THE ORIGIONAL ERROR! 
      return null; 
     } 

    } 
} 

Gdy masz swój własny aktywator domyślny aktywator może być switched out w globalnej asax:

GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new YourCustomControllerActivator()); 

Option 3 Oczywiście jeśli Twoja inicjalizacja w konstruktorze nie wymaga dostępu do rzeczywistych metod, właściwości itd. Kontrolera, tzn. zakładając, że można go usunąć z konstruktora ... wtedy znacznie łatwiej byłoby po prostu przenieść inicjalizację do filtra np.

public class MadagascarFilter : AbstractActionFilter 
{ 
    public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) 
    { 
    try{ 
      DoSomeInitialization(); // this can throw an exception 
     } 
     catch(BubonicPlagueException e){ 
    Log.Error(e, "CLOSE EVERYTHING!"); 
     Madagascar.ShutdownAllPorts(); 
      //DO SOMETHING WITH THE ERROR       
     } 

     base.OnActionExecuting(actionContext); 
    } 

public override void OnActionExecuted(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext) 
    { 
     base.OnActionExecuted(actionExecutedContext); 
    } 

    public override bool AllowMultiple 
    { 
     get { return false; } 
    } 


} 
Powiązane problemy