ASP.NET Core全局异常中间件如何捕获异常发生的类与方法?
解决方案:替代ASP.NET Core重复try-catch的异常记录方案
首先明确:仅靠全局中间件无法直接获取异常发生的具体类和方法信息。因为全局中间件处于请求管道的外层,异常抛出后已经脱离了业务代码的执行上下文,反射_next只能拿到管道内的中间件类型,无法定位到实际抛出异常的业务类或方法,且不解析堆栈的话没有其他直接途径获取该信息。
以下是几种可行的替代方案:
1. 控制器级异常过滤器(ActionFilterAttribute)
针对MVC/WebAPI控制器的场景,自定义异常过滤器可以直接获取当前执行的控制器和Action信息,无需解析堆栈:
public class GlobalExceptionFilter : IExceptionFilter { private readonly ILogger<GlobalExceptionFilter> _logger; public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger) { _logger = logger; } public void OnException(ExceptionContext context) { var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; if (actionDescriptor != null) { string className = actionDescriptor.ControllerTypeInfo.FullName; string methodName = actionDescriptor.MethodInfo.Name; _logger.LogError(context.Exception, "异常发生在类:{ClassName},方法:{MethodName}", className, methodName); } else { // 处理非控制器Action的异常(如Razor Pages),可 fallback 到堆栈记录 _logger.LogError(context.Exception, "发生未明确定位的异常"); } // 可选:标记异常已处理,返回自定义响应 // context.ExceptionHandled = true; // context.Result = new ObjectResult("服务器内部错误") { StatusCode = StatusCodes.Status500InternalServerError }; } }
注册过滤器到控制器服务:
// Program.cs builder.Services.AddControllers(options => { options.Filters.Add<GlobalExceptionFilter>(); });
2. AOP拦截器(推荐用于非控制器的业务服务)
如果需要覆盖控制器之外的业务类方法(如Service层),可以用AOP框架(如AspectCore)实现全局异常拦截,直接获取目标类和方法信息:
public class ExceptionInterceptor : AbstractInterceptorAttribute { private readonly ILogger<ExceptionInterceptor> _logger; public ExceptionInterceptor(ILogger<ExceptionInterceptor> logger) { _logger = logger; } public override async Task Invoke(AspectContext context, AspectDelegate next) { try { await next(context); } catch (Exception ex) { string className = context.TargetType.FullName; string methodName = context.MethodInfo.Name; _logger.LogError(ex, "异常发生在类:{ClassName},方法:{MethodName}", className, methodName); throw; // 继续抛出异常,或根据需求处理 } } }
配置AspectCore动态代理:
// Program.cs builder.Services.ConfigureDynamicProxy(options => { // 全局拦截所有服务 options.Interceptors.AddTyped<ExceptionInterceptor>(); // 或仅拦截特定命名空间的类 // options.Interceptors.AddTyped<ExceptionInterceptor>(predicate: type => type.Namespace.StartsWith("YourNamespace.Services")); }); // 注意:如果是构造注入的服务,需要用CreateProxyOfService获取实例,或通过特性标记 // 比如在服务类上添加[ExceptionInterceptor]特性
3. 封装异常扩展方法(兼容全局中间件)
如果必须保留全局中间件,可以通过扩展方法简化原有try-catch的代码,将类和方法名存入异常的Data集合,中间件直接读取:
public static class ExceptionExtensions { public static Exception WithSourceInfo(this Exception ex, string className, string methodName) { ex.Data["ClassName"] = className; ex.Data["MethodName"] = methodName; return ex; } }
业务代码中使用:
public async Task DoSomething() { try { // 业务逻辑 } catch (Exception ex) { throw ex.WithSourceInfo(GetType().FullName, System.Reflection.MethodBase.GetCurrentMethod()?.Name); } }
全局中间件中读取:
public class GlobalExceptionMiddleware { private readonly RequestDelegate _next; private readonly ILogger<GlobalExceptionMiddleware> _logger; public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { string className = ex.Data["ClassName"]?.ToString() ?? "未知类"; string methodName = ex.Data["MethodName"]?.ToString() ?? "未知方法"; _logger.LogError(ex, "异常发生在类:{ClassName},方法:{MethodName}", className, methodName); // 处理响应 await HandleExceptionAsync(context, ex); } } private Task HandleExceptionAsync(HttpContext context, Exception ex) { context.Response.ContentType = "application/json"; context.Response.StatusCode = StatusCodes.Status500InternalServerError; return context.Response.WriteAsync(JsonSerializer.Serialize(new { Message = "服务器内部错误" })); } }
内容的提问来源于stack exchange,提问作者Carlos Jimenez Bermudez
相关产品推荐
相关产品推荐

