如何为C#MVC4 WebAPI应用程序全局记录所有异常?

时间:2013-03-01 22:28:28

标签: c# asp.net-web-api error-handling

背景

我正在为客户开发API服务层,并且我被要求全局捕获并记录所有错误。

因此,通过使用ELMAH或通过向Global.asax添加类似内容来轻松处理类似未知端点(或操作)的内容:

protected void Application_Error()
{
     Exception unhandledException = Server.GetLastError();
     //do more stuff
}

。 。 。不会记录与路由无关的未处理错误。例如:

public class ReportController : ApiController
{
    public int test()
    {
        var foo = Convert.ToInt32("a");//Will throw error but isn't logged!!
        return foo;
    }
}

我还尝试通过注册此过滤器来全局设置[HandleError]属性:

filters.Add(new HandleErrorAttribute());

但这也不会记录所有错误。

问题/疑问

如何拦截上面调用/test生成的错误,以便我可以记录它们?似乎这个答案应该是显而易见的,但我已经尝试了迄今为止我能想到的一切。

理想情况下,我想在错误记录中添加一些内容,例如请求用户的IP地址,日期,时间等。我还希望能够在遇到错误时自动通过电子邮件发送支持人员。所有这些我都可以做到,只要我能在它们发生时拦截这些错误!

解决!

感谢Darin Dimitrov,我接受了他的回答,我明白这一点。 WebAPI 以与常规MVC控制器相同的方式处理错误。

这是有效的:

1)在命名空间中添加自定义过滤器:

public class ExceptionHandlingAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        if (context.Exception is BusinessException)
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = new StringContent(context.Exception.Message),
                ReasonPhrase = "Exception"
            });

        }

        //Log Critical errors
        Debug.WriteLine(context.Exception);

        throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
        {
            Content = new StringContent("An error occurred, please try again or contact the administrator."),
            ReasonPhrase = "Critical Exception"
        });
    }
}

2)现在在 WebApiConfig 类中全局注册过滤器:

public static class WebApiConfig
{
     public static void Register(HttpConfiguration config)
     {
         config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{action}/{id}", new { id = RouteParameter.Optional });
         config.Filters.Add(new ExceptionHandlingAttribute());
     }
}

OR 您可以跳过注册,只使用[ExceptionHandling]属性装饰一个控制器。

5 个答案:

答案 0 :(得分:76)

作为以前答案的补充。

昨天,ASP.NET Web API 2.1正式released 它提供了另一个处理全球异常的机会 详细信息在sample中给出。

简而言之,您添加全局异常记录器和/或全局异常处理程序(仅一个) 您将它们添加到配置:

public static void Register(HttpConfiguration config)
{
  config.MapHttpAttributeRoutes();

  // There can be multiple exception loggers.
  // (By default, no exception loggers are registered.)
  config.Services.Add(typeof(IExceptionLogger), new ElmahExceptionLogger());

  // There must be exactly one exception handler.
  // (There is a default one that may be replaced.)
  config.Services.Replace(typeof(IExceptionHandler), new GenericTextExceptionHandler());
}

他们的实现:

public class ElmahExceptionLogger : ExceptionLogger
{
  public override void Log(ExceptionLoggerContext context)
  {
    ...
  }
}

public class GenericTextExceptionHandler : ExceptionHandler
{
  public override void Handle(ExceptionHandlerContext context)
  {
    context.Result = new InternalServerErrorTextPlainResult(
      "An unhandled exception occurred; check the log for more information.",
      Encoding.UTF8,
      context.Request);
  }
}

答案 1 :(得分:55)

如果您的Web API托管在ASP.NET应用程序中,则会为代码中的所有未处理异常调用Application_Error事件,包括您显示的测试操作中的异常。因此,您所要做的就是在Application_Error事件中处理此异常。在您显示的示例代码中,您只处理HttpException类型的异常,而Convert.ToInt32("a")代码显然不是这种情况。因此,请确保记录并处理其中的所有异常:

protected void Application_Error()
{
    Exception unhandledException = Server.GetLastError();
    HttpException httpException = unhandledException as HttpException;
    if (httpException == null)
    {
        Exception innerException = unhandledException.InnerException;
        httpException = innerException as HttpException;
    }

    if (httpException != null)
    {
        int httpCode = httpException.GetHttpCode();
        switch (httpCode)
        {
            case (int)HttpStatusCode.Unauthorized:
                Response.Redirect("/Http/Error401");
                break;

            // TODO: don't forget that here you have many other status codes to test 
            // and handle in addition to 401.
        }
        else
        {
            // It was not an HttpException. This will be executed for your test action.
            // Here you should log and handle this case. Use the unhandledException instance here
        }
    }
}

<击>

Web API中的异常处理可以在各个级别完成。这是detailed article解释不同的可能性:

  • 自定义异常过滤器属性,可以注册为全局异常过滤器

    [AttributeUsage(AttributeTargets.All)]
    public class ExceptionHandlingAttribute : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext context)
        {
            if (context.Exception is BusinessException)
            {
                throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
                {
                    Content = new StringContent(context.Exception.Message),
                    ReasonPhrase = "Exception"
                });
            }
    
            //Log Critical errors
            Debug.WriteLine(context.Exception);
    
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = new StringContent("An error occurred, please try again or contact the administrator."),
                ReasonPhrase = "Critical Exception"
            });
        }
    }
    
  • 自定义动作调用程序

    public class MyApiControllerActionInvoker : ApiControllerActionInvoker
    {
        public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
        {
            var result = base.InvokeActionAsync(actionContext, cancellationToken);
    
            if (result.Exception != null && result.Exception.GetBaseException() != null)
            {
                var baseException = result.Exception.GetBaseException();
    
                if (baseException is BusinessException)
                {
                    return Task.Run<HttpResponseMessage>(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)
                    {
                        Content = new StringContent(baseException.Message),
                        ReasonPhrase = "Error"
    
                    });
                }
                else
                {
                    //Log critical error
                    Debug.WriteLine(baseException);
    
                    return Task.Run<HttpResponseMessage>(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)
                    {
                        Content = new StringContent(baseException.Message),
                        ReasonPhrase = "Critical Error"
                    });
                }
            }
    
            return result;
        }
    }
    

答案 2 :(得分:7)

为什么要重新抛出?这有效,它将使服务返回状态为500等

public class LogExceptionFilter : ExceptionFilterAttribute
{
    private static readonly ILog log = LogManager.GetLogger(typeof (LogExceptionFilter));

    public override void OnException(HttpActionExecutedContext actionExecutedContext)
    {
        log.Error("Unhandeled Exception", actionExecutedContext.Exception);
        base.OnException(actionExecutedContext);
    }
}

答案 3 :(得分:2)

您是否考虑过像

这样的句柄错误操作过滤器
[HandleError]
public class BaseController : Controller {...}

您还可以创建[HandleError]的自定义版本,您可以使用该版本编写错误信息和所有其他详细信息以进行记录

答案 4 :(得分:1)

将整个事物包装在try / catch中并记录未处理的异常,然后将其传递给它。除非有更好的内置方式来实现它。

以下是参考Catch All (handled or unhandled) Exceptions

(编辑:哦API)