官方建议用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);
}
}
层级异常类
using System;
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(new DaMeiExceptionFilter());
});