2012-07-09 5 views
14

Supponiamo di avere un costruttore in cui è possibile che l'inizializzazione generi un'eccezione a causa di motivi che sfuggono al mio controllo.Come devo gestire le eccezioni all'interno di un costruttore di controller in WebAPI?

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

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

Di solito, quando un'azione di controllo in WebAPI genera un'eccezione posso gestire tramite un 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; 
     }); 
    } 

Quindi, se ho un avere un metodo BoardPlane API che getta un BubonicPlagueException, poi il mio CustomerErrorHandler chiuderà le porte in Madagascar e lo registrerà come un errore come previsto. In altri casi, quando non è molto serio, visualizzo solo un messaggio user friendly e restituisco un 500 InternalServerError.

Ma in quei casi in cui DoSomeInitialization genera un'eccezione, questo non fa assolutamente nulla. Come posso gestire le eccezioni nei costruttori di controller WebAPI?

+0

Una caratteristica interessante di WebApi è che è possibile personalizzare facilmente il modo in cui l'eccezione viene restituita al client. Errore di stato e Html all'interno dello stesso metodo di azione. Ovviamente perdi tutto questo se viene lanciata un'eccezione all'interno del costruttore. Penso che dovresti evitare che ciò accada. La maggior parte della logica dovrebbe essere inserita nel metodo webapi non nel costruttore. Detto questo penso che dovresti usare la gestione degli errori standard di asp.net, ovvero configurare le pagine di errore all'interno di Web.Config, o intercettare l'evento onerror nell'asax globale –

risposta

13

I controller WebApi vengono creati e quindi i costruttori chiamati tramite HttpControllerActivators. L'attivatore predefinito è System.Web.Http.Dispatcher.DefaultHttpControllerActivator.

esempi molto grezzi per le opzioni 1 & 2 su GitHub qui https://github.com/markyjones/StackOverflow/tree/master/ControllerExceptionHandling/src

Opzione 1 che funziona abbastanza bene implica l'uso di un contenitore DI (si può ben essere utilizzato uno già). Ho usato Ninject per il mio esempio e ho usato "Interceptors" Read More per intercettare e provare/catturare le chiamate al metodo Create sul DefaultHttpControllerActivator. So di almeno autofac e Ninject che può fare qualcosa per simlar al seguente:

Creare l'intercettore

Non so che cosa la portata durata del vostro Madagascar e vuoto Log sono ma potrebbero ben essere iniettato nel vostro Interceptor

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

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

Ma mantenendo l'esempio nella tua domanda dove Log e Madagascar sono una sorta di statico globale

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

FINALMENTE Registrare l'intercettore In ASax globale o App_Start (NinjectWebCommon)

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

Opzione 2 è quello di implementare il proprio controller Activator implementando l'interfaccia IHttpControllerActivator e gestire l'errore nella creazione del controller nella Creare un metodoÈ possibile utilizzare il modello decorator per avvolgere la 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; 
     } 

    } 
} 

Una volta che avete la vostra attivatore personalizzato l'attivatore di default può essere switched out nel ASax globale:

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

Opzione 3 Naturalmente se la tua inizializzazione nel costruttore non ha bisogno di accedere ai metodi, alle proprietà ecc. dei controllori attuali ... cioè supponendo che possa essere rimossa dal costruttore ... allora sarebbe molto più semplice spostare semplicemente l'inizializzazione su un filtro, ad es.

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


}