using Dpz.Core.Web.Library.Middleware;
using Microsoft.AspNetCore.Mvc.Filters;
using OpenIddict.Client.AspNetCore;
namespace Dpz.Core.Web.Library.Filter;
public class ExceptionHandleAttribute(ILogger<ExceptionHandleAttribute> logger)
: ExceptionFilterAttribute
{
public override async Task OnExceptionAsync(ExceptionContext context)
{
await HandleException(context, () => base.OnExceptionAsync(context));
}
private Task HandleException(ExceptionContext context, Func<Task> _)
{
if (
context is
{
Exception: OperationCanceledException,
HttpContext.RequestAborted.IsCancellationRequested: true
}
)
{
context.ExceptionHandled = true;
return Task.CompletedTask;
}
if (context.Exception is SyntheticTimeoutException syntheticTimeoutException)
{
logger.LogInformation(syntheticTimeoutException, "timeout");
context.ExceptionHandled = true;
context.HttpContext.Response.Clear();
context.HttpContext.Response.StatusCode = StatusCodes.Status408RequestTimeout;
return Task.CompletedTask;
}
if (context.Exception is CurrentUserRequiredException currentUserRequiredException)
{
return HandleCurrentUserRequiredExceptionAsync(context, currentUserRequiredException);
}
if (context.Exception is BusinessException businessException)
{
logger.LogError(context.Exception, "business error");
context.Result = new JsonResult(new ResultInfo(businessException.Message));
context.HttpContext.Response.Clear();
context.HttpContext.Response.StatusCode = 200;
return Task.CompletedTask;
}
logger.LogError(context.Exception, "Uncaught exception");
var httpContext = context.HttpContext;
if (httpContext.Request.ExpectsJsonResponse())
{
context.ExceptionHandled = true;
context.Result = new JsonResult(new ResultInfo(context.Exception.Message));
context.HttpContext.Response.Clear();
context.HttpContext.Response.StatusCode = 200;
return Task.CompletedTask;
}
httpContext.Items[HtmlErrorPageMiddleware.ExceptionLoggedItemKey] = true;
return Task.CompletedTask;
}
private async Task HandleCurrentUserRequiredExceptionAsync(
ExceptionContext context,
CurrentUserRequiredException exception
)
{
logger.LogInformation(exception, "Current user is required");
var httpContext = context.HttpContext;
context.ExceptionHandled = true;
httpContext.Response.Clear();
if (httpContext.Request.ExpectsJsonResponse())
{
context.Result = new JsonResult(ResultInfo.ToFail(exception.Message))
{
StatusCode = StatusCodes.Status401Unauthorized,
};
httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
return;
}
await httpContext.SignOutAsync(Program.AuthorizeCookieName);
var returnUrl =
httpContext.Request.PathBase
+ httpContext.Request.Path
+ httpContext.Request.QueryString;
context.Result = new ChallengeResult(
OpenIddictClientAspNetCoreDefaults.AuthenticationScheme,
new AuthenticationProperties { RedirectUri = returnUrl }
);
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个 ASP.NET Core 的全局异常处理过滤器,用于统一处理应用程序中抛出的各种异常。
主要功能
1. 类定义
public class ExceptionHandleAttribute(ILogger<ExceptionHandleAttribute> logger)
: ExceptionFilterAttribute
- 继承自
ExceptionFilterAttribute,作为 MVC 的异常过滤器 - 使用主构造函数(C# 12 特性)注入日志记录器
- 可以作为特性(Attribute)应用到控制器或方法上
2. 核心异常处理逻辑
HandleException 方法按优先级处理不同类型的异常:
(1) 请求取消异常
if (context.Exception is OperationCanceledException &&
context.HttpContext.RequestAborted.IsCancellationRequested)
- 当客户端主动取消请求时触发
- 标记异常已处理,静默忽略
(2) 合成超时异常
if (context.Exception is SyntheticTimeoutException)
- 处理自定义的超时异常
- 返回 408 Request Timeout 状态码
- 记录信息日志
(3) 用户认证必需异常
if (context.Exception is CurrentUserRequiredException)
- 需要登录但用户未认证时抛出
- 调用专门的处理方法(见下文)
(4) 业务异常
if (context.Exception is BusinessException)
- 处理预期的业务逻辑异常
- 返回 200 状态码 + JSON 格式的错误信息
- 记录错误日志
(5) 未捕获的异常
logger.LogError(context.Exception, "Uncaught exception");
- 处理所有未预期的异常
- 如果是 JSON 请求(AJAX/API),返回 JSON 错误
- 如果是普通 HTTP 请求,标记异常供
HtmlErrorPageMiddleware处理
3. 认证异常专门处理
HandleCurrentUserRequiredExceptionAsync 方法:
private async Task HandleCurrentUserRequiredExceptionAsync(...)
处理流程:
- 记录信息日志
- 清空响应内容
- 如果是 JSON 请求:返回 401 未授权状态
- 如果是网页请求:
- 登出当前 Cookie 认证
- 保存当前请求 URL 作为
returnUrl - 重定向到 OpenIddict 登录页面(使用
ChallengeResult)
设计特点
✅ 分层异常处理:不同异常类型有不同的处理策略
✅ 支持 RESTful API:通过 ExpectsJsonResponse() 区分 JSON/HTML 请求
✅ 集成认证流程:与 OpenIddict 集成实现自动跳转登录
✅ 业务友好:业务异常返回 200 状态码(前端友好)
✅ 日志完备:不同级别的日志记录便于问题排查
使用场景
通常在 ASP.NET Core 应用中全局注册:
// Program.cs
builder.Services.AddControllers(options =>
{
options.Filters.Add<ExceptionHandleAttribute>();
});
或者单独应用到控制器:
[ExceptionHandle]
public class MyController : Controller { }
AI 正在分析代码…
评论加载中...