假设我有一个构造函数,由于我无法控制的原因,它的初始化可能会引发异常。
FantasticApiController(IAwesomeGenerator awesome,
IBusinessRepository repository, IIceCreamFactory factory)
{
Awesome = awesome;
Repository = repository;
IceCream = factory.MakeIceCream();
DoSomeInitialization(); // this can throw an exception
}
通常,当WebAPI中的Controller操作抛出异常时,我可以通过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;
});
}
因此,如果我有一个BoardPlane
API方法会抛出BubonicPlagueException
,那么我的CustomerErrorHandler
将关闭到马达加斯加的端口,并按预期将其记录为错误。在其他情况下,当它不是很严重时,我只会显示一些用户友好的消息并返回500 InternalServerError
。
但是在DoSomeInitialization
抛出异常的情况下,这绝对没有任何意义。 如何在WebAPI控制器构造函数中处理异常?
答案 0 :(得分:14)
创建WebApi控制器,从而通过HttpControllerActivators调用构造函数。默认激活器是System.Web.Http.Dispatcher.DefaultHttpControllerActivator。
选项1&的非常粗略的例子2在github上https://github.com/markyjones/StackOverflow/tree/master/ControllerExceptionHandling/src
选项1 非常适用于使用DI容器(您可能已经使用了一个)。我已经使用Ninject作为我的示例,并使用“拦截器”Read More拦截并尝试/捕获对DefaultHttpControllerActivator上的Create方法的调用。我知道至少AutoFac和Ninject可以做以下事情:
创建拦截器
我不知道您的马达加斯加和日志物品的生命范围是什么,但它们很可能被注入您的拦截器
public class ControllerCreationInterceptor : Ninject.Extensions.Interception.IInterceptor
{
private ILog _log;
private IMadagascar _madagascar;
public ControllerCreationInterceptor(ILog log, IMadagascar madagascar)
{
_log = log;
_madagascar = madagascar;
}
但请坚持你的问题中的例子,其中Log和Madagascar是某种静态全局
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!
}
}
}
FINALLY在全局asax或App_Start(NinjectWebCommon)中注册拦截器
kernel.Bind<System.Web.Http.Dispatcher.IHttpControllerActivator>()
.To<System.Web.Http.Dispatcher.DefaultHttpControllerActivator>().Intercept().With<ControllerCreationInterceptor>();
选项2 是实现您自己的Controller Activator实现IHttpControllerActivator接口,并在Create方法中处理创建Controller时的错误。您可以使用装饰器模式来包装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;
}
}
}
一旦拥有自己的自定义激活器,默认激活器可以是全局asax中的switched out:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new YourCustomControllerActivator());
选项3 当然,如果你在构造函数中的初始化不需要访问实际的Controllers方法,属性等...即假设它可以从构造函数中删除...那么它将初始化移动到过滤器会更容易,例如
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; }
}
}