C#.NET 全局异常到底怎么做?最完整的实战指南

作者:唐青枫日期:2025/11/17

简介

全局异常拦截是构建健壮企业级应用的关键基础设施,它能统一处理系统中未捕获的异常,提供友好的错误响应,同时记录完整的异常信息。

背景和作用

ASP.NET Core 应用中,异常可能在控制器、数据库操作或中间件中发生。如果每个动作方法都手动处理异常(如 try-catch),代码会变得冗长且难以维护。全局异常拦截器解决了以下问题:

  • 统一错误处理:集中捕获所有未处理异常,返回标准化的错误响应。
  • 标准化响应:符合 RESTful API 规范(如 RFC 7807 Problem Details)。
  • 日志记录:记录异常详情,便于调试和监控。
  • 用户体验:返回友好的错误信息,而非默认错误页面或堆栈跟踪。
  • 性能优化:减少重复的异常处理代码,提升开发效率。

主要功能

  • 捕获异常:捕获控制器、服务层或其他代码中的未处理异常。
  • 标准化响应:返回 JSON 格式的错误详情(如状态码、错误消息)。
  • 日志记录:记录异常信息(包括堆栈跟踪)到日志系统。
  • 自定义处理:根据异常类型返回不同状态码或消息(如 400、409、500)。
  • 异步支持:兼容 async/await,适合异步操作。
  • DI 集成:通过依赖注入访问服务(如日志、缓存)。

常见实现方式

异常处理中间件(推荐)

ASP.NET Core 管道最前端注册一个中间件,捕获所有后续中间件/终结点抛出的异常。

1public class ExceptionHandlingMiddleware
2{
3    private readonly RequestDelegate _next;
4    private readonly ILogger<ExceptionHandlingMiddleware> _logger;
5
6    public ExceptionHandlingMiddleware(RequestDelegate next,
7        ILogger<ExceptionHandlingMiddleware> logger)
8    {
9        _next = next;
10        _logger = logger;
11    }
12
13    public async Task InvokeAsync(HttpContext ctx)
14    {
15        try
16        {
17            await _next(ctx);
18        }
19        catch (Exception ex)
20        {
21            _logger.LogError(ex, "Unhandled exception");
22            await HandleExceptionAsync(ctx, ex);
23        }
24    }
25
26    private static Task HandleExceptionAsync(HttpContext ctx, Exception ex)
27    {
28        ctx.Response.ContentType = "application/problem+json";
29        ctx.Response.StatusCode = ex switch
30        {
31            ArgumentException _ => StatusCodes.Status400BadRequest,
32            KeyNotFoundException _ => StatusCodes.Status404NotFound,
33            _ => StatusCodes.Status500InternalServerError
34        };
35
36        var problem = new ProblemDetails
37        {
38            Title = ex.Message,
39            Status = ctx.Response.StatusCode,
40            Detail  = ctx.Response.StatusCode == 500 ? "请稍后重试或联系管理员" : null,
41            Instance = ctx.Request.Path
42        };
43        var json = JsonSerializer.Serialize(problem);
44        return ctx.Response.WriteAsync(json);
45    }
46}
47

注册中间件

Program.cs(或 Startup.cs)中最早添加:

1app.UseMiddleware<ExceptionHandlingMiddleware>();
2
3// 或更简洁的扩展方法
4app.UseExceptionHandling();  // 需自行实现 UseExceptionHandling 扩展
5

优势

  • 捕获范围最大:包括所有 MVC、Minimal API、静态文件等。
  • 性能开销小,代码集中清晰。
  • 易于与依赖注入、日志系统联动。

全局异常过滤器(IExceptionFilter / IAsyncExceptionFilter)

如果只想拦截 MVC Controller 的异常,可实现异常过滤器。

1public class GlobalExceptionFilter : IExceptionFilter
2{
3    private readonly ILogger<GlobalExceptionFilter> _logger;
4    public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
5        => _logger = logger;
6
7    public void OnException(ExceptionContext context)
8    {
9        var ex = context.Exception;
10        _logger.LogError(ex, "Unhandled exception in controller");
11
12        var problem = new ProblemDetails
13        {
14            Title = "请求失败",
15            Status = StatusCodes.Status500InternalServerError,
16            Detail = ex.Message
17        };
18        context.Result = new ObjectResult(problem)
19        {
20            StatusCode = problem.Status
21        };
22        context.ExceptionHandled = true;
23    }
24}
25

注册过滤器

1services.AddControllers(options =>
2{
3    options.Filters.Add<GlobalExceptionFilter>();
4});
5

局限

  • 只拦截通过 MVC 管道执行的 Action 抛出的异常。
  • 不会捕获例如中间件或 Minimal API 的异常。

.NET 7+ 新增的 IExceptionHandler(推荐)

1public class GlobalExceptionHandler : IExceptionHandler
2{
3    private readonly ILogger<GlobalExceptionHandler> _logger;
4    private readonly IProblemDetailsService _problemDetailsService;
5
6    public GlobalExceptionHandler(
7        ILogger<GlobalExceptionHandler> logger,
8        IProblemDetailsService problemDetailsService)
9    {
10        _logger = logger;
11        _problemDetailsService = problemDetailsService;
12    }
13
14    public async ValueTask<bool> TryHandleAsync(
15        HttpContext httpContext,
16        Exception exception,
17        CancellationToken cancellationToken)
18    {
19        _logger.LogError(exception, "全局异常: {Message}", exception.Message);
20        
21        var statusCode = GetStatusCode(exception);
22        var problemContext = new ProblemDetailsContext
23        {
24            HttpContext = httpContext,
25            Exception = exception,
26            ProblemDetails = new ProblemDetails
27            {
28                Title = "服务器错误",
29                Status = statusCode,
30                Detail = httpContext.RequestServices.GetRequiredService<IWebHostEnvironment>().IsDevelopment() 
31                    ? exception.ToString() 
32                    : "请稍后再试",
33                Type = $"https://httpstatuses.io/{statusCode}"
34            }
35        };
36        
37        // 添加自定义扩展
38        problemContext.ProblemDetails.Extensions.Add("requestId", httpContext.TraceIdentifier);
39        
40        await _problemDetailsService.WriteAsync(problemContext);
41        return true; // 标记为已处理
42    }
43}
44

注册服务:

1builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
2builder.Services.AddProblemDetails(); // 添加ProblemDetails支持
3
4var app = builder.Build();
5app.UseExceptionHandler(); // 启用异常处理中间件
6

内置 UseExceptionHandler

ASP.NET Core 自带的异常处理终结点:

1if (!app.Environment.IsDevelopment())
2{
3    app.UseExceptionHandler(errorApp =>
4    {
5        errorApp.Run(async ctx =>
6        {
7            var feature = ctx.Features.Get<IExceptionHandlerFeature>();
8            var ex = feature?.Error;
9            // 记录日志…
10            ctx.Response.StatusCode = 500;
11            await ctx.Response.WriteAsJsonAsync(new { message = "服务器内部错误" });
12        });
13    });
14}
15else
16{
17    app.UseDeveloperExceptionPage();
18}
19
  • 优点:无需自定义 Middleware,框架内置。
  • 注意:要在其他中间件之前注册,并在生产/开发环境中分开处理。

资源和文档


C#.NET 全局异常到底怎么做?最完整的实战指南》 是转载文章,点击查看原文


相关推荐


c文件编译
芝麻馅汤圆儿2025/11/16

随笔记录 目录 1.背景 2. 编译 2.1 创建 .c 文件 2.2 编译->执行 1.背景 c文件在编译为可执行程序 2. 编译 2.1 创建 .c 文件 已知c 文件源码,创建 .c 文件并编译为可执行程序 1. creat *.c 文件 [root@localhost magx]# vim udp_nobind_sendto.c [root@localhost magx]# [root@localhost magx]# cat udp_nobind_


一句话把 Excalidraw 跑起来?SOLO Coder 把我以前踩过的坑都帮我填了
不惑_2025/11/14

Excalidraw 是一个开源、类手绘风格的虚拟白板工具,支持无限画布、暗色模式、图形库、导出 PNG/SVG、自由绘制、多语言、快捷键与绑定箭头等丰富能力。其官方应用原生支持 PWA 离线、本地优先存储、纯前端架构,非常适合私有化部署或内网使用。 要是你之前动手部署过开源项目,你应该懂那种感觉:教程看着简单,结果一动手就开始打怪,git clone → 解压 → 安装依赖 → 处理网络代理 → 启动服务 → 校验端口 → 本地预览。而这些步骤每一步都可能踩到网络、依赖、端口占用等坑。我以前搭


李飞飞「世界模型」正式开放,人人可用! Pro 版首月仅 7 元
新智元2025/11/13

「【新智元导读】只用一张图,一句话,就能创造出随便乱逛的 3D 世界——李飞飞这次不是在讲故事,而是真的给你「造梦神器」。今天起,全球上线,人人可用。」 李飞飞的「创世神器」今天终于正式上线了!人人可用。 这个由 WorldLabs 推出,名为 Marble 的网站,用世界模型可以生成瑰丽梦幻的「想象世界」~ 体验网址:marble.worldlabs.ai/ 这波属实是李飞飞自己联动自己了,前两天她的一篇万字长文火爆硅谷,定义 AI 的下一个十年是「空间智能」。 文章中,她为真正具备空间智


进入职场第四课—产出
Mapbarfront2025/11/12

新入职一家公司观察、融入、立足之后,要做的第四件事就是产出。 观察和融入帮你顺利度过新手期,立足则让你在团队中站稳脚跟,被大家真正接受,接下来你要做的是保持稳定,持续不断的产出,让团队感受到你究竟有多大能量。 易经乾卦中的终日乾乾,说的就是这个意思,终日指的是持续不断,意味着不能三天打鱼两天晒网,有了开头没有结尾,乾乾代表着产出,要有价值、有思考、有进阶,不能只做重复工作,毕竟在易经中,乾代表天,想完成立足到产出的关键跃升,以下这4个动作一定要做好。 1、产出有价值的成果。 立足期是你独立承担任


SpringBoot实现隐式参数注入
风象南2025/11/10

前言:一个痛点 想象一下这样的场景:用户请求带着 JWT Token 进入你的系统,Filter 层面解析 Token 得到用户 ID,接下来需要: 在 Controller 层获取用户信息 在 Service 层进行权限验证 在某些业务逻辑中记录操作日志 每一个环节都需要知道"当前用户是谁",看看目前常用的解决方案。 传统方案的"缺陷" 方案一:ThreadLocal // 看起来很"Hack" private static final ThreadLocal<Long> currentU


Python实战:用高德地图API批量获取地址所属街道并写回Excel
程序员爱钓鱼2025/11/8

在日常的数据处理工作中,我们经常需要根据公司、事件或门店的注册地址,批量获取其所在的街道信息,例如“浦东新区张江镇”“徐汇区龙华街道”等。 手动查询显然低效,而借助 Python + 高德地图API,我们可以轻松实现自动化批量查询并将结果写入 Excel 文件中。 本文将完整展示一个从 Excel 读取地址 → 调用高德API → 获取街道 → 写回Excel的实用脚本,并讲解实现细节与优化思路。 一、功能概述 这段脚本的功能可以总结为四步: 从 Excel 文件中读取地址数据; 调用高德


HTML中JS监听输入框值的即时变化
雨过天晴而后无语2025/11/5

一、说明         上一篇文章中提到了需要监听页面的一些组件内容变化,以便于更好的判断页面是否有更改,而控制“确定”按钮。         本里以JS监听输入框值的即时变化为引子,对这样的需求进行一个示例。 二、示例代码 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmln


pdf文件上传下载记录
家有两宝,感恩遇见2025/10/31

一:上传单个pdf文件并用hash重命名及判断文件是不是已经上传过,可以参考hash图片 @PostMapping("/uploadPdfFileSingle")     public String uploadPdfFileSingle(@RequestPart("file") MultipartFile file) {         System.err.println("开始文件上传");         if (file.isEmpty()) {             System


Redis(91)Redis的访问控制列表(ACL)是如何工作的?
Victor3562025/10/29

Redis 6.0及以上版本支持访问控制列表(ACL),这允许更细粒度地控制不同用户的权限。ACL使得可以定义多个用户,每个用户有自己的一组权限,这些权限可以控制哪些命令可以执行,哪些键可以访问等。以下是Redis ACL的详细工作原理和示例代码。 1. 基本概念 用户:Redis的每一个用户都有一个唯一的名字。 权限:每个用户可以被授予一组权限,这些权限定义了用户可以执行哪些命令和访问哪些键。 认证:用户在连接Redis时需要提供用户名和密码进行认证。 2. 配置ACL 可以在Redis配


Java Stream流两大实战陷阱:并行流Parallel误用、List转Map时重复键异常
IT橘子皮2025/10/26

在Java 8引入的Stream API极大地简化了集合操作,让函数式编程在Java世界中大放异彩。然而,在实践中发现的,Stream API并非银弹,其中隐藏着一些容易踩坑的陷阱。本文将深入分析Stream流中最常见的两大问题:并行流的误用和List转Map时的重复键异常,并提供相应的解决方案。 一、并行流(parallel)的陷阱与优化方案 1.1 问题本质分析 并行流看似是性能优化的"神器",但实践表明,盲目使用parallel()可能适得其反: // 看似高效的并行处理,实则是性能陷阱

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0