2012-07-09 20 views
14

Digamos que tengo un constructor en el que su inicialización puede arrojar una excepción por razones que escapan a mi control.¿Cómo debo manejar las excepciones dentro de un constructor de controlador en WebAPI?

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

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

Normalmente, cuando una acción de controlador en WebAPI lanza una excepción que puedo manejarlo a través de 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; 
     }); 
    } 

Así que si tengo un tener un método BoardPlane API que arroja un BubonicPlagueException, a continuación, mi CustomerErrorHandler cerrará los puertos a Madagascar y lo registrará como un error como se esperaba. En otros casos, cuando no es realmente grave, solo muestro un mensaje fácil de usar y devuelvo 500 InternalServerError.

Pero en los casos en que DoSomeInitialization arroja una excepción, esto no hace absolutamente nada. ¿Cómo puedo manejar las excepciones en los constructores de controlador WebAPI?

+0

Una característica interesante de WebApi es que puede personalizar fácilmente la forma en que se devuelve la excepción al cliente. Estado y error Html dentro del mismo método de acción. Obviamente, pierdes todo esto si se lanza una excepción dentro del constructor. Creo que deberías evitar que esto suceda. La mayor parte de la lógica debería incluirse en el método webapi, no en el constructor. Dicho esto, creo que debería usar el manejo de errores estándar de asp.net, es decir, configurar páginas de error dentro de Web.Config o interceptar el evento onerror en el asax global. –

Respuesta

13

Se crean los controladores WebApi y, por lo tanto, se llaman constructores a través de HttpControllerActivators. El activador predeterminado es System.Web.Http.Dispatcher.DefaultHttpControllerActivator.

Ejemplos muy ásperos para las opciones 1 & 2 en GitHub aquí https://github.com/markyjones/StackOverflow/tree/master/ControllerExceptionHandling/src

Opción 1 que funciona bastante bien implica el uso de un contenedor de DI (que bien puede estar usando uno ya). He usado Ninject para mi ejemplo y he usado "Interceptores" Read More para interceptar y probar/capturar llamadas al método Create en DefaultHttpControllerActivator. Sé de al menos autofac y Ninject que puede hacer algo para simlar a lo siguiente:

Crear el interceptor

No sé cuál es el alcance de su tiempo de vida de Madagascar y elementos de registro son pero podrían así ser inyectado dentro de su interceptor

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

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

Pero mantener al ejemplo de la pregunta donde log y Madagascar son una especie de estática mundial

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

Register, finalmente, el interceptor En GLOBAL.ASAX o App_Start (NinjectWebCommon)

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

Opción 2 es implementar su propio activador controlador que implementa la interfaz IHttpControllerActivator y gestionar el error en la creación del controlador en el Crear métodoSe podría utilizar el patrón decorador para envolver el 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 vez que tenga su propio activador de costumbre el activador predeterminado puede ser switched out en el GLOBAL.ASAX:

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

Opción 3 Por supuesto, si su inicialización en el constructor no necesita acceder a los métodos, propiedades, etc. reales de los Controladores ... es decir, suponiendo que pueda eliminarse del constructor ... entonces sería mucho más fácil simplemente mover la inicialización a un filtro, por ejemplo

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


} 
Cuestiones relacionadas