You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 8中IExceptionHandler无跳转显示错误页面问询

在ASP.NET Core 8 MVC中用IExceptionHandler渲染HTML错误页(无重定向)

实现思路

核心是通过IRazorViewEngine直接渲染指定的Error视图为HTML字符串,再写入响应流,避免重定向导致地址栏变更。需要手动构建视图渲染所需的上下文,并注入必要的服务完成视图解析与渲染。


步骤1:注册必要服务

在Program.cs中添加异常处理器、上下文访问器等服务:

var builder = WebApplication.CreateBuilder(args);

// 注册MVC服务
builder.Services.AddControllersWithViews();

// 注册自定义异常处理器
builder.Services.AddExceptionHandler<CustomExceptionHandler>();
// 注册HttpContext访问器(用于获取当前请求上下文)
builder.Services.AddHttpContextAccessor();
// 注册日志服务(用于记录异常详情)
builder.Services.AddLogging();

var app = builder.Build();

// 启用异常处理中间件
app.UseExceptionHandler();

// 其他中间件配置(静态文件、路由、授权等)
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

步骤2:实现自定义IExceptionHandler

创建CustomExceptionHandler类,完成异常日志记录、视图渲染与响应输出:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Diagnostics;
using System.Text;

public class CustomExceptionHandler : IExceptionHandler
{
    private readonly IRazorViewEngine _razorViewEngine;
    private readonly ITempDataProvider _tempDataProvider;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILogger<CustomExceptionHandler> _logger;

    public CustomExceptionHandler(
        IRazorViewEngine razorViewEngine,
        ITempDataProvider tempDataProvider,
        IHttpContextAccessor httpContextAccessor,
        ILogger<CustomExceptionHandler> logger)
    {
        _razorViewEngine = razorViewEngine;
        _tempDataProvider = tempDataProvider;
        _httpContextAccessor = httpContextAccessor;
        _logger = logger;
    }

    public async ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        // 记录异常详情(仅后台日志,不暴露给用户)
        _logger.LogError(exception, "发生未处理的服务器异常");

        // 构建视图渲染所需的ActionContext
        var actionContext = new ActionContext(
            httpContext,
            httpContext.GetRouteData(),
            new ActionDescriptor(),
            new ModelStateDictionary());

        // 准备错误页模型(与Home/Error视图的模型一致)
        var errorModel = new ErrorViewModel
        {
            RequestId = httpContext.TraceIdentifier,
            Message = "若问题持续,请联系管理员"
        };

        // 查找Error视图(优先找Home控制器下的视图,其次Shared目录)
        var viewResult = _razorViewEngine.FindView(actionContext, "Error", isMainPage: false);
        if (!viewResult.Success)
        {
            // 视图不存在时返回简易错误HTML
            await httpContext.Response.WriteAsync(
                "<h1>服务器错误</h1><p>若问题持续,请联系管理员</p>", 
                cancellationToken);
            return true;
        }

        // 渲染视图为HTML字符串
        var errorHtml = await RenderViewToStringAsync(
            actionContext, 
            viewResult.View, 
            errorModel, 
            cancellationToken);

        // 设置响应头与内容
        httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
        httpContext.Response.ContentType = "text/html; charset=utf-8";
        await httpContext.Response.WriteAsync(errorHtml, cancellationToken);

        return true;
    }

    // 私有方法:将视图渲染为字符串
    private async Task<string> RenderViewToStringAsync(
        ActionContext actionContext,
        IView view,
        object model,
        CancellationToken cancellationToken)
    {
        using var stringWriter = new StringWriter();
        
        var viewData = new ViewDataDictionary(
            new EmptyModelMetadataProvider(),
            actionContext.ModelState)
        {
            Model = model
        };

        var viewContext = new ViewContext(
            actionContext,
            view,
            viewData,
            new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
            stringWriter,
            new HtmlHelperOptions());

        await view.RenderAsync(viewContext);
        return stringWriter.ToString();
    }
}

步骤3:准备错误页模型与视图

  1. ErrorViewModel(与Home控制器的Error Action模型一致):
public class ErrorViewModel
{
    public string? RequestId { get; set; }
    public string? Message { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
  1. Error视图(Views/Home/Error.cshtml或Views/Shared/Error.cshtml):
@model ErrorViewModel
@{
    ViewData["Title"] = "错误";
}

<div class="container">
    <h1 class="text-danger">服务器错误</h1>
    <p class="lead">@Model.Message</p>
    @if (Model.ShowRequestId)
    {
        <p>
            <strong>请求ID:</strong> <code>@Model.RequestId</code>
        </p>
    }
</div>

关键说明

  • 避免重定向:直接将渲染后的HTML写入响应,浏览器地址栏保持原请求路径,刷新时会重新发起原请求而非停留在错误页。
  • 上下文构建:手动创建ActionContext确保视图渲染时能获取到当前请求的路由、模型状态等信息。
  • 安全注意:仅向用户展示友好提示,异常详情通过日志记录,避免泄露敏感信息。

内容的提问来源于stack exchange,提问作者PBO

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 00:30:10