问题描述
在传统的ASP.NET Web Api 应用程序开发中,我们处理全局异常的方法通常是实现一个ExceptionFilterAttribute
的子类,如下:
public classErrorHandlingFilter : ExceptionFilterAttribute { public override voidOnException(ExceptionContext context) { HandleExceptionAsync(context); context.ExceptionHandled = true; } private static voidHandleExceptionAsync(ExceptionContext context) { var exception =context.Exception; if (exception isMyNotFoundException) SetExceptionResult(context, exception, HttpStatusCode.NotFound); else if (exception isMyUnauthorizedException) SetExceptionResult(context, exception, HttpStatusCode.Unauthorized); else if (exception isMyException) SetExceptionResult(context, exception, HttpStatusCode.BadRequest); elseSetExceptionResult(context, exception, HttpStatusCode.InternalServerError); } private static voidSetExceptionResult( ExceptionContext context, Exception exception, HttpStatusCode code) { context.Result = new JsonResult(newApiResponse(exception)) { StatusCode = (int)code }; } }
然后,在Startup
过滤器中注册这个ErrorHandlingFilter
自定义的错误处理属性类,如:
config.Filter.Add(new ErrorHandlingFilter());
这样,在ASP.NET Web Api 应用程序就可以捕获全局的错误并进行处理。
但在ASP.NET Core Web Api的应用程序开发开,以上的解决方案就不起作用了。ASP.NET Core Web Api的全局错误捕获与处理方式与ASP.NET Web Api应用程序的处理方式并非一样。
所以,我们得使用适合ASP.NET Core Web Api的全局错误捕获与处理的解决方案。那么,有哪些方案可以捕获和处理ASP.NET Core Web Api应用程序的全局错误呢?
方案一
一个比较好并且推荐的做法是使用中间件(middleware)来处理。在ASP.NET Core Web Api应用程序开发中,中间件是最好的解决方案。它不仅可以处理应用程序的异常,同时也可以捕获过滤器的异常并且也能创建自定义的响应返回结果(比如json的响应返回结果),以下是一个基于ASP.NET Core Web Api的异常处理的中间件:
public classErrorHandlingMiddleware { private readonlyRequestDelegate next; publicErrorHandlingMiddleware(RequestDelegate next) { this.next =next; } public async Task Invoke(HttpContext context /*other dependencies */) { try{ awaitnext(context); } catch(Exception ex) { awaitHandleExceptionAsync(context, ex); } } private staticTask HandleExceptionAsync(HttpContext context, Exception exception) { var code = HttpStatusCode.InternalServerError; //500 if unexpected if (exception is MyNotFoundException) code =HttpStatusCode.NotFound; else if (exception is MyUnauthorizedException) code =HttpStatusCode.Unauthorized; else if (exception is MyException) code =HttpStatusCode.BadRequest; var result = JsonConvert.SerializeObject(new { error =exception.Message }); context.Response.ContentType = "application/json"; context.Response.StatusCode = (int)code; returncontext.Response.WriteAsync(result); } }
使用方法,Startup.cs
类文件中,在调用MVC之前注册错误处理中间件ErrorHandlingMiddleware
,如:
app.UseMiddleware(typeof(ErrorHandlingMiddleware)); app.UseMvc();
如果中间件捕获到应用程序的异常,则会返回类似如下的响应结果:
{ "error": "Authentication token is not valid." }
在中间件ErrorHandlingMiddleware
的方法HandleExceptionAsync
中,可以对当前上下文的错误进行任意处理(比如,跟踪详细的错误信息,记录错误日志等等)。
方案二
在ASP.NET Core 2或者以上的.NET Core 版本中,我们可以使用UseExceptionHandler
扩展方法来捕获并处理ASP.NET Core的全局错误。
首页,在ASP.NET Core 2的Startup.cs
文件中注册错误处理扩展方法并指定一个错误处理页面(这里是/Error
),如下:
public voidConfigure(IApplicationBuilder app, IHostingEnvironment env) { if(env.IsDevelopment()) { //开发模式... } else{ app.UseStatusCodePagesWithReExecute("/Error"); app.UseExceptionHandler("/Error"); } //其他... }
接着,定义一个可以指定响应状态码(HttpStatusCode)的异常类,如下:
public classHttpException : Exception { public HttpException(HttpStatusCode statusCode) { StatusCode =statusCode; } public HttpStatusCode StatusCode { get; private set; } }
最后,在控制器中创建上文提到的/Error
错误处理页面,ASP.NET Core 2捕获到的错误将转到这个错误处理页面。在这个页面,我们就可以处理这些错误/异常,如下:
[AllowAnonymous] publicIActionResult Error() { //捕获状态码 var statusCode = HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error is HttpException httpEx ?httpEx.StatusCode : (HttpStatusCode)Response.StatusCode; //如果是ASP.NET Core Web Api 应用程序,直接返回状态码(不跳转到错误页面,这里假设所有API接口的路径都是以/api/开始的) if (HttpContext.Features.Get<IHttpRequestFeature>().RawTarget.StartsWith("/api/", StringComparison.Ordinal)) return StatusCode((int)statusCode); //创建一个友好的错误处理页面视图模型. string text = null; switch(statusCode) { case HttpStatusCode.NotFound: text = "Page not found."; break; //其他详细信息 } return View("Error", new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier, ErrorText =text }); }
注: 如果需要让 API 接口返回json对象,则可以使用
return Json(...)
来替换return StatusCode(...)
方案三
在ASP.NET Core 2.1+版本中,我们可以使用中间件Community.AspNetCore.ExceptionHandling.Mvc[1]
来处理,如下:
public voidConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddExceptionHandlingPolicies(options =>{ options.For<InitializationException>().Rethrow(); options.For<SomeTransientException>().Retry(ro => ro.MaxRetryCount = 2).NextPolicy(); options.For<SomeBadRequestException>() .Response(e => 400) .Headers((h, e) => h["X-MyCustomHeader"] =e.Message) .WithBody((req,sw, exception) =>{ byte[] array =Encoding.UTF8.GetBytes(exception.ToString()); return sw.WriteAsync(array, 0, array.Length); }) .NextPolicy(); //确保所有的异常均被捕获 options.For<Exception>() .Log(lo =>{ lo.EventIdFactory = (c, e) => new EventId(123, "UnhandlerException"); lo.Category = (context, exception) => "MyCategory"; }) .Response(null, ResponseAlreadyStartedBehaviour.GoToNextHandler) .ClearCacheHeaders() .WithObjectResult((r, e) => new { msg = e.Message, path =r.Path }) .Handled(); }); } public voidConfigure(IApplicationBuilder app, IHostingEnvironment env) { app.UseExceptionHandlingPolicies(); app.UseMvc(); }