【问题标题】:How should I handle exceptions within a controller constructor in WebAPI?我应该如何在 WebAPI 的控制器构造函数中处理异常?
【发布时间】:2012-07-09 20:57:18
【问题描述】:

假设我有一个构造函数,由于我无法控制的原因,它的初始化可能会引发异常。

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 控制器构造函数中的异常?

【问题讨论】:

  • WebApi 的一个有趣特性是您可以轻松自定义异常返回给客户端的方式。同一操作方法中的状态和 Html 错误。显然,如果在构造函数中抛出异常,您将失去所有这些。我认为你应该避免这种情况发生。大多数逻辑应该放在 webapi 方法而不是构造函数中。也就是说我认为你应该使用标准的 asp.net 错误处理,即在 Web.Config 中配置错误页面,或者在全局 asax 中拦截 onerror 事件

标签: exception-handling asp.net-web-api asp.net-4.5


【解决方案1】:

WebApi 控制器被创建,因此构造函数通过 HttpControllerActivators 调用。默认激活器是 System.Web.Http.Dispatcher.DefaultHttpControllerActivator。

github 上选项 1 和 2 的非常粗略的示例https://github.com/markyjones/StackOverflow/tree/master/ControllerExceptionHandling/src

选项 1 非常好用,它涉及使用 DI 容器(您可能已经在使用一个)。我在示例中使用了 Ninject,并使用了“拦截器”Read More 来拦截和尝试/捕获对 DefaultHttpControllerActivator 上的 Create 方法的调用。我至少知道 AutoFac 和 Ninject 可以做与以下类似的事情:

创建拦截器

我不知道您的 Madagascar 和 Log 项目的生命周期范围是多少,但它们很可能被注入到您的拦截器中

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

最后注册拦截器在全局 asax 或 App_Start (NinjectWebCommon) 中

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

方案二是实现自己的ControllerActivator实现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 当然,如果您在构造函数中的初始化不需要访问实际的控制器方法、属性等...即假设它可以从构造函数中删除...那么它将初始化移动到过滤器会容易得多,例如

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


}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-09-26
    • 1970-01-01
    • 2018-06-19
    • 2016-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多