ASP.NET Core 6 MVC自定义全局错误页无法显示求助
问题排查:ASP.NET Core 6 MVC自定义错误页不显示,返回JSON的问题
问题描述
开发ASP.NET Core 6 MVC项目,需实现包含Transaction ID、Span ID等附加信息的全局自定义错误页。已创建ErrorHandlingMiddleware捕获错误信息,但测试时仅返回序列化的JSON字符串,自定义error.cshtml页面未展示,中间件已正常捕获并记录错误。
现有代码片段
1. 修改后的ErrorViewModel类
public class ErrorViewModel { public string? RequestId { get; set; } public Activity? CurrentActivity { get; set; } public string TraceId { get; set; } public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); }
2. ErrorHandlingMiddleware中间件
using MVCLogging.Models; using System.Diagnostics; using System.Text.Json; namespace MVCLogging.Middleware { public class ErrorHandlingMiddleware: IMiddleware { public ErrorViewModel Err; public ILogger<ErrorHandlingMiddleware> _logger { get; } public ErrorHandlingMiddleware(ILogger<ErrorHandlingMiddleware> logger) { _logger = logger; Err = new(); } public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next) { try { await next(httpContext); } catch(Exception ex) { Err = new(); Err.TraceId = httpContext.TraceIdentifier; Err.RequestId = Activity.Current?.Id ?? httpContext.TraceIdentifier; Err.CurrentActivity = Activity.Current; var result = JsonSerializer.Serialize(Err); httpContext.Response.ContentType = "application/json"; httpContext.Response.WriteAsync(result); _logger.LogError(result); var resp = httpContext.Response; resp.Redirect("/Home/Error"); } } } }
3. Program.cs注册中间件
using MVCLogging.Configurations; using MVCLogging.Middleware; using MVCLogging.Models; using NLog; using NLog.Web; // Early init of NLog to allow startup and exception logging, before host is built var logger = NLog.LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); logger.Debug("Nlog init main"); var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); builder.Services.AddTransient<ErrorHandlingMiddleware>(); builder.Services.AddProductSvc(); builder.Logging.ClearProviders(); builder.Logging.AddConsole(); builder.Host.UseNLog(); var app = builder.Build(); //use error handling middleware app.UseExceptionHandler("/Home/Error"); app.UseHsts(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseMiddleware<ErrorHandlingMiddleware>(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
4. 修改后的error.cshtml页面
@model ErrorViewModel @{ ViewData["Title"] = "Error"; } <h1 class="text-danger">Error.</h1> <h2 class="text-danger">An error occurred while processing your request.</h2> @if (Model.ShowRequestId) { <p> <strong>Request ID:</strong> <code>@Model.RequestId</code> <br /> <strong>HTTP TraceId:</strong> <code>@Model.TraceId</code> <br /> <strong>Activity.Id:</strong> <code>@Model.CurrentActivity?.Id</code> <br /> <strong>Activity.SpanId:</strong> <code>@Model.CurrentActivity?.SpanId</code> <br /> <strong>Activity.TraceId:</strong> <code>@Model.CurrentActivity?.TraceId</code> <br /> <strong>Activity.Parent:</strong> <code>@Model.CurrentActivity?.Parent</code> <br /> <strong>Activity.ParentId:</strong> <code>@Model.CurrentActivity?.ParentId</code> <br /> <strong>Activity.ParentSpanId:</strong> <code>@Model.CurrentActivity?.ParentSpanId</code> <br /> <strong>Activity.RootId:</strong> <code>@Model.CurrentActivity?.RootId</code> <br /> <strong>Activity.Kind:</strong> <code>@Model.CurrentActivity?.Kind</code> </p> }
5. HomeController中的测试Action
public IActionResult TriggerError() { int valueA = 7; int valueB = 0; ViewData["result"] = valueA / valueB; return View(); }
测试现象
中间件正常捕获并记录错误信息,但页面仅返回序列化的JSON字符串,未展示自定义错误页:
{"RequestId":"00-ecfcaba10ec348ff8a531c532b4d6983-417e523e0e66c711-00","CurrentActivity":{"Status":0,"StatusDescription":null,"Kind":0,"OperationName":"Microsoft.AspNetCore.Hosting.HttpRequestIn","DisplayName":"Microsoft.AspNetCore.Hosting.HttpRequestIn","Source":{"Name":"","Version":""},"Parent":null,"Duration":"00:00:00","StartTimeUtc":"2023-07-29T14:20:00.1658232Z","Id":"00-ecfcaba10ec348ff8a531c532b4d6983-417e523e0e66c711-00","ParentId":null,"RootId":"ecfcaba10ec348ff8a531c532b4d6983","Tags":[],"TagObjects":[],"Events":[],"Links":[],"Baggage":[],"Context":{"TraceId":{},"SpanId":{},"TraceFlags":0,"TraceState":null,"IsRemote":false},"TraceStateString":null,"SpanId":{},"TraceId":{},"Recorded":false,"IsAllDataRequested":true,"ActivityTraceFlags":0,"ParentSpanId":{},"IdFormat":2},"TraceId":"0HMSG7RGCGSKG:00000007","ShowRequestId":true}
问题原因及修复方案
核心问题
- 响应已写入内容,无法重定向:catch块中先调用
httpContext.Response.WriteAsync(result)写入JSON,此时HTTP响应已开始输出,后续Redirect操作无法生效。 - 中间件顺序错误:
UseMiddleware<ErrorHandlingMiddleware>放在UseExceptionHandler之后,导致异常被自定义中间件捕获后,UseExceptionHandler无法再处理。 - 错误信息传递无效:直接返回JSON,未将错误数据传递给
/Home/Error页面的Model。
修复步骤
步骤1:修正ErrorHandlingMiddleware逻辑
移除JSON写入操作,将错误信息存入HttpContext.Items后重新抛出异常,交给UseExceptionHandler处理:
using MVCLogging.Models; using System.Diagnostics; namespace MVCLogging.Middleware { public class ErrorHandlingMiddleware: IMiddleware { public ILogger<ErrorHandlingMiddleware> _logger { get; } public ErrorHandlingMiddleware(ILogger<ErrorHandlingMiddleware> logger) { _logger = logger; } public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next) { try { await next(httpContext); } catch(Exception ex) { var errorModel = new ErrorViewModel { TraceId = httpContext.TraceIdentifier, RequestId = Activity.Current?.Id ?? httpContext.TraceIdentifier, CurrentActivity = Activity.Current }; // 将错误模型存入HttpContext,供错误页面读取 httpContext.Items["ErrorModel"] = errorModel; // 记录包含异常详情的日志 _logger.LogError(ex, "未处理异常,RequestId: {RequestId}", errorModel.RequestId); // 重新抛出异常,让UseExceptionHandler处理跳转 throw; } } } }
步骤2:调整Program.cs中间件顺序
将自定义中间件放在UseExceptionHandler之前,确保异常能被捕获并传递:
var app = builder.Build(); // 先使用自定义中间件捕获异常 app.UseMiddleware<ErrorHandlingMiddleware>(); // 再用ExceptionHandler处理异常并跳转错误页 app.UseExceptionHandler("/Home/Error"); app.UseHsts(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
步骤3:修改HomeController的Error Action
从HttpContext.Items读取错误模型并传递给视图:
public IActionResult Error() { var errorModel = HttpContext.Items["ErrorModel"] as ErrorViewModel ?? new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier, TraceId = HttpContext.TraceIdentifier }; return View(errorModel); }
步骤4:移除无效的Redirect操作
原中间件中的resp.Redirect("/Home/Error")已不需要,UseExceptionHandler会自动跳转至指定错误页。
额外优化建议
- 提取Activity关键字段:避免直接传递
Activity对象,将TraceId、SpanId等需要的字段单独提取到ErrorViewModel,减少不必要的数据传递。 - 优化日志内容:日志中记录完整异常信息(如堆栈跟踪),而非序列化的模型,便于问题排查。
内容的提问来源于stack exchange,提问作者bedrock
相关产品推荐
相关产品推荐

