异常处理程序页
若要为生产环境配置自定义错误处理页,请调用 UseExceptionHandler。 此异常处理中间件:
- 捕获并记录未经处理的异常。
- 使用指示的路径在备用管道中重新执行请求。 如果响应已启动,则不会重新执行请求。 模板生成的代码使用 /Error 路径重新执行请求。
由于此中间件可以重新执行请求管道:
- 中间件需要处理具有相同请求的重新进入。 这通常意味着在调用 _next 后清理它们的状态,或在 HttpContext 上缓存它们的处理以避免重做。 在处理请求正文时,这意味着缓冲或缓存结果(如表单读取器)。
- 对于模板中使用的 UseExceptionHandler(IApplicationBuilder, String) 重载,只会修改请求路径,并清除路由数据。 请求数据(如标头、方法和项)均按原样重复使用。
- 限定范围内的服务保持不变。
在下面的示例中,UseExceptionHandler 在非开发环境中添加异常处理中间件:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
Razor Pages 应用模板在 Pages 文件夹中提供了一个“错误”页面 (.cshtml) 和 PageModel 类 (ErrorModel)。 对于 MVC 应用,项目模板包括 Error 操作方法和 Home 控制器的错误视图。
MVC的话同理,可以创建一个Error页面处理和显示错误页面。
异常处理中间件使用原始 HTTP 方法重新执行请求。 如果错误处理程序终结点限制为一组特定的 HTTP 方法,那么只会对这些 HTTP 方法运行。 例如,使用 [HttpGet] 属性的 MVC 控制器操作仅对 GET 请求运行。 若要确保所有请求都到达自定义错误处理页,请不要将它们限制为一组特定的 HTTP 方法。
根据原始 HTTP 方法以不同方式处理异常:
对于 Razor Pages,创建多个处理程序方法。 例如,使用 OnGet 处理 GET 异常,使用 OnPost 处理 POST 异常。
对于 MVC,请将 HTTP 谓词属性应用于多个操作。 例如,使用 [HttpGet] 处理 GET 异常,使用 [HttpPost] 处理 POST 异常。
若要允许未经身份验证的用户查看自定义错误处理页,请确保它支持匿名访问。
使用 IExceptionHandlerPathFeature 访问错误处理程序中的异常和原始请求路径。 以下示例使用 IExceptionHandlerPathFeature 获取有关引发的异常详细信息:
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public string? ExceptionMessage { get; set; }
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
var exceptionHandlerPathFeature =
HttpContext.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
ExceptionMessage = "The file was not found.";
}
if (exceptionHandlerPathFeature?.Path == "/")
{
ExceptionMessage ??= string.Empty;
ExceptionMessage += " Page: Home.";
}
}
}
异常处理程序 lambda
自定义异常处理程序页的替代方法是向 UseExceptionHandler 提供 lambda。 使用 lambda,可以在返回响应前访问错误。
以下代码使用 lambda 处理异常:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
// using static System.Net.Mime.MediaTypeNames;
context.Response.ContentType = Text.Plain;
await context.Response.WriteAsync("An exception was thrown.");
var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
await context.Response.WriteAsync(" The file was not found.");
}
if (exceptionHandlerPathFeature?.Path == "/")
{
await context.Response.WriteAsync(" Page: Home.");
}
});
});
app.UseHsts();
}
实现IExceptionHandler接口
IExceptionHandler 是接口,可为开发人员提供用于在中心位置处理已知异常的回叫。
IExceptionHandler 实现是通过调用 IServiceCollection.AddExceptionHandler<T> 来注册的。 IExceptionHandler 实例的生存期是 singleton。 可以添加多个实现,并按注册顺序调用它们。
如果异常处理程序处理请求,它可以返回 true 来停止处理。 如果异常不是由任何异常处理程序处理,则控件将回退到中间件的默认行为和选项。 对于已处理和未经处理的异常,会发出不同的指标和日志。
以下示例说明了 IExceptionHandler 实现:
using Microsoft.AspNetCore.Diagnostics;
namespace ErrorHandlingSample
{
public class CustomExceptionHandler : IExceptionHandler
{
private readonly ILogger<CustomExceptionHandler> logger;
public CustomExceptionHandler(ILogger<CustomExceptionHandler> logger)
{
this.logger = logger;
}
public ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
var exceptionMessage = exception.Message;
logger.LogError(
"Error Message: {exceptionMessage}, Time of occurrence {time}",
exceptionMessage, DateTime.UtcNow);
// Return false to continue with the default behavior
// - or - return true to signal that this exception is handled
return ValueTask.FromResult(false);
}
}
}
以下示例显示了在program.cs文件里为依赖项注入注册 IExceptionHandler 实现:
using ErrorHandlingSample;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddExceptionHandler<CustomExceptionHandler>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
// Remaining Program.cs code omitted for brevity
UseStatusCodePages
默认情况下,ASP.NET Core 应用不会为 HTTP 错误状态代码(如“404 - 未找到”)提供状态代码页。 当应用设置没有正文的 HTTP 400-599 错误状态代码时,它将返回状态代码和空响应正文。 若要启用常见错误状态代码的默认纯文本处理程序,请在 Program.cs 中调用 UseStatusCodePages:
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePages();
在请求处理中间件之前调用 UseStatusCodePages。 例如,在静态文件中间件和端点中间件之前调用 UseStatusCodePages。
未使用 UseStatusCodePages 时,导航到没有终结点的 URL 会返回一条与浏览器相关的错误消息,指示找不到终结点。 调用 UseStatusCodePages 时,浏览器将返回以下响应:
Status Code: 404; Not Found
UseStatusCodePages 通常不在生产中使用,因为它返回对用户没有用的消息。状态代码页中间件不捕获异常。 若要提供自定义错误处理页,请使用异常处理程序页。
UseStatusCodePagesWithRedirects
UseStatusCodePagesWithRedirects 扩展方法:
向客户端发送“302 - 已找到”状态代码。
将客户端重定向到 URL 模板中提供的错误处理终结点。 错误处理终结点通常会显示错误信息并返回 HTTP 200。
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");
URL 模板可能会包括状态代码的 {0} 占位符,如前面的代码中所示。 如果 URL 模板以波形符 ~(代字号)开头,则 ~ 会替换为应用的 PathBase。 在应用中指定终结点时,请为终结点创建 MVC 视图或 Razor 页面。
使用此方法通常是当应用:
应将客户端重定向到不同的终结点(通常在不同的应用处理错误的情况下)。 对于 Web 应用,客户端的浏览器地址栏反映重定向终结点。
不应保留原始状态代码并通过初始重定向响应返回该代码。