22 | 异常处理中间件:区分真异常与逻辑异常
这一节我们来讲解一下错误处理的最佳实践
系统里面异常处理,ASP.NET Core 提供了四种方式
1、异常处理页
2、异常处理匿名委托方法
3、IExceptionFilter
4、ExceptionFilterAttribute
源码链接: https://github.com/witskeeper/geektime/tree/master/samples/ExceptionDemo
Startup 的 Configure 方法
代码语言:javascript复制if (env.IsDevelopment())
{
// 开发环境下的异常处理页
app.UseDeveloperExceptionPage();
}
控制器抛出异常
代码语言:javascript复制throw new Exception("报个错");
启动程序,可以看到一个错误页
这个错误页会输出我们当前请求的详细信息和错误的详细信息,这种页面是不适合给用户看到的,所以这样的错误页在生产环境是需要关闭的
以下是正常处理错误页的方式:
代码语言:javascript复制// 第一种方式就是定义错误页的方式
app.UseExceptionHandler("/error");
定义一个接口 IKnownException
代码语言:javascript复制namespace ExceptionDemo.Exceptions
{
public interface IKnownException
{
public string Message { get; }
public int ErrorCode { get; }
public object[] ErrorData { get; }
}
}
默认实现 KnownException
代码语言:javascript复制namespace ExceptionDemo.Exceptions
{
public class KnownException : IKnownException
{
public string Message { get; private set; }
public int ErrorCode { get; private set; }
public object[] ErrorData { get; private set; }
public readonly static IKnownException Unknown = new KnownException { Message = "未知错误", ErrorCode = 9999 };
public static IKnownException FromKnownException(IKnownException exception)
{
return new KnownException { Message = exception.Message, ErrorCode = exception.ErrorCode, ErrorData = exception.ErrorData };
}
}
}
为什么需要定义这样一个类型呢?
因为通常情况下我们系统里面的异常和我们业务逻辑的异常是不同的,业务逻辑上面的判断异常,比如说输入的参数,订单的状态不符合条件,当前账户余额不足,这样子的信息我们有两种处理方式:
一种处理方式就是对不同的逻辑输出不同的业务对象
还有一种方式就是对于异常的这种业务逻辑,输出一个异常,用异常来承载逻辑的特殊分支,这个时候就需要识别出来哪些是业务的异常,哪些是不确定的未知的异常,比如说网络的请求出现了异常,MySql 的连接闪断了,Redis 的连接出现了异常
接着通过定义一个错误页来承载错误信息,比如我们的 ErrorController,它只有一个页面,它的作用就是输出错误信息
代码语言:javascript复制namespace ExceptionDemo.Controllers
{
[AllowAnonymous]
public class ErrorController : Controller
{
[Route("/error")]
public IActionResult Index()
{
// 获取当前上下文里面报出的异常信息
var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
var ex = exceptionHandlerPathFeature?.Error;
// 特殊处理,尝试转换为 IKnownException
var knownException = ex as IKnownException;
// 对于未知异常,我们并不应该把错误异常完整地输出给客户端,而是应该定义一个特殊的信息 Unknown 传递给用户
// Unknown 其实也是一个 IKnownException 的实现,它的 Message = "未知错误", ErrorCode = 9999
// 也就是说我们在控制器 throw new Exception("报个错"); 就会看到错误信息
if (knownException == null)
{
var logger = HttpContext.RequestServices.GetService<ILogger<MyExceptionFilterAttribute>>();
// 我们看到的信息是未知错误,但是在我们的日志系统里面,我们还是记录的原有的异常信息
logger.LogError(ex, ex.Message);
knownException = KnownException.Unknown;
}
else// 当识别到异常是已知的业务异常时,输出已知的异常,包括异常消息,错误状态码和错误信息,就是在 IKnownException 中的定义
{
knownException = KnownException.FromKnownException(knownException);
}
return View(knownException);
}
}
}
View
代码语言:javascript复制@model ExceptionDemo.Exceptions.IKnownException
@{
ViewData["Title"] = "Index";
}
<h1>错误信息</h1>
<div>Message:<label>@Model.Message</label></div>
<div>ErrorCode<label>@Model.ErrorCode</label></div>
启动程序之后可以看到自定义的错误页已经成功渲染出来了
这就是第一种处理错误的方式