官方建议用app.UseExceptionHandler("/error")来集中处理异常,本例是一个具体的应用。
比如项目中有一个ViewModel,要求Name最大长度为5
/// <summary>/// 用户模型/// </summary>public class UserModel{/// <summary>/// ID/// </summary>public int ID { get; set; }/// <summary>///名称///</summary>[MaxLength(5, ErrorMessage = "长度不能超过5")]public string Name { get; set; }}
在TestController中有两个Action,都有异常的机率,Get方法中,一个异常是系统内置的0被整除,另一个是我们自定义的业务层级异常(.NET架构小技巧(8)中有涉及);AddUser是有Model验证有可能Name超过5个字符后报异常。Error方法一个错误处理Action,根据上下文的异常来分流系统内置异常,还是自定业务异常。
///<summary>/// get接口/// </summary>/// <returns></returns>[HttpGet]public IActionResult Get(){var ran = new Random();switch (ran.Next(1, 4)){case 1:int i = 0;var j = 10 / i;return Ok();case 2:throw new RegisteredException("这是一个错误");default:return Ok();}}/// <summary>///添加用户接口/// </summary>/// <param name="user"></param>/// <returns></returns>[HttpPost("/adduser")]public IActionResult AddUser([FromBody] UserModel user){return Ok(user);}///<summary>/// 错误处理页/// </summary> /// <returns></returns>[HttpGet("/error")]public IActionResult Error(){var context = HttpContext.Features.Get<IExceptionHandlerFeature>();//如果是业务自定义异常,进行特殊处理if (context.Error is DaMeiException){return Problem(detail: context.Error.StackTrace, title: $"{context.Error.Message}", type: "HIS");}else{return Problem(detail: context.Error.StackTrace, title: context.Error.Message);}}
层级异常类
usingSystem;namespace WebApiError{/// <summary>/// 产品异常/// </summary>public class DaMeiException : ApplicationException{/// <summary>/// /// </summary>/// <param name="message"></param>public DaMeiException(string message) : base(message){}}/// <summary>/// His项目异常/// </summary>public class HisException : DaMeiException{/// <summary>/// /// </summary>/// <param name="message"></param>public HisException(string message) : base(message){}}/// <summary>/// Lis项目异常/// </summary>public class LisException : DaMeiException{/// <summary>/// /// </summary>/// <param name="message"></param>public LisException(string message) : base(message){}}/// <summary>/// 模块异常/// </summary>public class RegisteredException : HisException{/// <summary>/// /// </summary>/// <param name="message"></param>public RegisteredException(string message) : base(message){}}}
Error的Action之所有调用到,是因为Configure中添加如下代码,把所有异常交给"/error"来处理。
app.UseExceptionHandler("/error");
添加DaMeiProblemDetailsFactory来兼容各类异常,自定义异常和Model验证异常
using Microsoft.AspNetCore.Http;using Microsoft.AspNetCore.Mvc;using Microsoft.AspNetCore.Mvc.Infrastructure;using Microsoft.AspNetCore.Mvc.ModelBinding;using System.Collections.Generic;namespace WebApiError{///<summary>/// /// </summary>public class DaMeiProblemDetailsFactory : ProblemDetailsFactory{/// <summary>///处理业务错误/// </summary>/// <param name="httpContext"></param>/// <param name="statusCode"></param>/// <param name="title"></param>/// <param name="type"></param>/// <param name="detail"></param>/// <param name="instance"></param>/// <returns></returns>public override ProblemDetails CreateProblemDetails(HttpContext httpContext, int? statusCode = null, string title = null, string type = null, string detail = null, string instance = null){var problem = new ProblemDetails(){Title = string.IsNullOrEmpty(type) ? title : $"业务异常错误:{title}",Detail = detail,Status = statusCode,Instance = instance,Type=type};return problem;}/// <summary>/// 处理model验证错误/// </summary>/// <param name="httpContext"></param>/// <param name="modelStateDictionary"></param>/// <param name="statusCode"></param>/// <param name="title"></param>/// <param name="type"></param>/// <param name="detail"></param>/// <param name="instance"></param>/// <returns></returns>public override ValidationProblemDetails CreateValidationProblemDetails(HttpContext httpContext, ModelStateDictionary modelStateDictionary, int? statusCode = null, string title = null, string type = null, string detail = null, string instance = null){var problem = new ValidationProblemDetails(){Title = "Model验证错误",Detail = detail,Status = statusCode,Instance = instance,Type = type};foreach (var a in modelStateDictionary){var errorList = new List<string>();foreach (var error in a.Value.Errors){errorList.Add(error.ErrorMessage);}problem.Errors.Add(new KeyValuePair<string, string[]>(a.Key, errorList.ToArray()));}return problem;}}}
在ConfigureServices中需要注入DaMeiProblemDetailsFactory
services.AddTransient<ProblemDetailsFactory, DaMeiProblemDetailsFactory>();
其实还可以用Action过滤器来统一管理异常
using Microsoft.AspNetCore.Mvc;using Microsoft.AspNetCore.Mvc.Filters;namespace WebApiError{/// <summary>/// 自定义过滤器处理异常/// </summary>public class DaMeiExceptionFilter : IActionFilter, IOrderedFilter{/// <summary>/// /// </summary>public int Order { get; } = int.MaxValue - 10;/// <summary>/// /// </summary>/// <param name="context"></param>public void OnActionExecuting(ActionExecutingContext context) { }/// <summary>/// /// </summary>/// <param name="context"></param>public void OnActionExecuted(ActionExecutedContext context){if (context?.Exception != null){context.Result = new ObjectResult(context.Exception.Message){StatusCode = 500};context.ExceptionHandled = true;} }} }
另外一种方式是通过Action过滤器来处理
using Microsoft.AspNetCore.Mvc;using Microsoft.AspNetCore.Mvc.Filters;namespace WebApiError{/// <summary>/// 自定义过滤器处理异常/// </summary>public class DaMeiExceptionFilter : IActionFilter, IOrderedFilter{/// <summary>/// /// </summary>public int Order { get; } = int.MaxValue - 10;/// <summary>/// /// </summary>/// <param name="context"></param>public void OnActionExecuting(ActionExecutingContext context){}/// <summary>/// /// </summary>/// <param name="context"></param>public void OnActionExecuted(ActionExecutedContext context){if (context?.Exception != null){if (context.Exception is DaMeiException){context.Result = new ObjectResult(context.Exception.Message){Value = $"业务异常:{ context.Exception.Message}",StatusCode = 500};}else{context.Result = new ObjectResult(context.Exception.Message){Value = context.Exception.Message,StatusCode = 500};}context.ExceptionHandled = true;}}}}
添加全局过滤器
services.AddControllers(options =>{options.Filters.Add(newDaMeiExceptionFilter());});