开发人员异常页

  “开发人员异常”页显示未经处理的请求异常的详细信息。 ASP.NET Core 应用在以下情况下默认启用开发人员异常页:

  • 在开发环境中运行
  • 使用当前模板创建的应用,即使用WebApplication.CreateBuilder。使用WebHost.CreateDefaultBuilder创建的应用必须通过在Configure中调用app.UseDeveloperExceptionPage来启用开发人员异常页。

  开发人员异常页运行在中间件管道的前面部分,以便它能够捕获随后中间件中抛出的未经处理的异常。
  开发人员异常页可能包含关于异常和请求的以下信息:

  • 堆栈跟踪
  • 查询字符串参数(如果有)
  • Cookie(如果有)
  • 标头

异常处理程序页

  若要为生产环境配置自定义错误处理页,请调用UseExceptionHandler.此异常处理中间件:

  • 捕获并记录未经处理的异常
  • 使用指示的路径在备用管道中重新执行请求。如果响应已启动,则不会重新执行请求。模板生成的代码使用**/Error**路径重新执行请求

如果备用管道引发了一个自身的异常,则异常处理中间件会重新引发原始异常

  由于此中间件可以重新执行请求管道:

  • 中间件需要处理具有相同请求的重新进入。这通常意味着在调用 _next 后清理它们的状态,或在 HttpContext 上缓存它们的处理以避免重做。 在处理请求正文时,这意味着缓冲或缓存结果(如表单读取器)。
  • 对于模板中使用的 UseExceptionHandler(IApplicationBuilder, String) 重载,只会修改请求路径,并清除路由数据。 请求数据(如标头、方法和项)均按原样重复使用。
  • 限定范围内的服务保持不变。
1
2
3
4
5
6
var app = builder.Build();

if(!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[ApiController]
[Route("/Error")]
public class ErrorController : Controller
{
private readonly ILogger<ErrorController> _logger;

public ErrorController(ILogger<ErrorController> logger)
{
_logger = logger;
}

[AllowAnonymous]
[Route("Error")]
public IActionResult Error()
{
// 获取异常详情信息
var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();

_logger.LogError($"路径 {exceptionHandlerPathFeature.Path} 产生了一个错误 {exceptionHandlerPathFeature.Error}");

return View("Error");
}

[Route("Error/{statusCode}")]
public IActionResult HttpStatusCodeHandler(int statusCode)
{
var statusCodeResult = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

if(statusCode == 404)
{
return NotFound();
}
else{
return View("Error");
}
}
}

异常处理程序lambda

  自定义异常处理程序页的替代方法是向 UseExceptionHandler 提供 lambda。 使用 lambda,可以在返回响应前访问错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 来停止处理。 如果异常不是由任何异常处理程序处理,则控件将回退到中间件的默认行为和选项。 对于已处理和未经处理的异常,会发出不同的指标和日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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);
}
}
}

注册IExceptionHandler实现

1
builder.Services.AddExceptionHandler<CustomExceptionHandler>();

  当前面的代码在开发环境中运行时:

  1. 首先调用 CustomExceptionHandler 来处理异常。
  2. 记录异常后,TryHandleException 方法返回 false,因此显示开发人员异常页面。

  在其他环境中:

  1. 首先调用 CustomExceptionHandler 来处理异常。
  2. 记录异常后,TryHandleException 方法返回 false,因此显示 /Error 页面。

UseStatusCodePages

  默认情况下,ASP.NET Core 应用不会为 HTTP 错误状态代码(如“404 - 未找到”)提供状态代码页。 当应用设置没有正文的 HTTP 400-599 错误状态代码时,它将返回状态代码和空响应正文。 若要启用常见错误状态代码的默认纯文本处理程序,请在 Program.cs 中调用 UseStatusCodePages

1
2
3
4
5
6
7
8
9
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseStatusCodePages();

  未使用 UseStatusCodePages 时,导航到没有终结点的 URL 会返回一条与浏览器相关的错误消息,指示找不到终结点。 调用 UseStatusCodePages 时,浏览器将返回以下响应:

1
Status Code: 404; Not Found

UseStatusCodePages 通常不在生产中使用,因为它返回对用户没有用的消息。

包含格式字符串的 UseStatusCodePages

  若要自定义响应内容类型和文本,请利用需要使用内容类型和格式字符串的 UseStatusCodePages 重载:

1
2
3
4
5
6
7
8
9
10
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}"); // {0} 是错误代码的占位符。

包含lambda的 UseStatusCodePages

  若要指定自定义错误处理和响应写入代码,请利用需要使用 lambda 表达式的 UseStatusCodePages 重载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseStatusCodePages(async statusCodeContext =>
{
// using static System.Net.Mime.MediaTypeNames;
statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

await statusCodeContext.HttpContext.Response.WriteAsync(
$"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});

  使用 lambda 的 UseStatusCodePages 通常不在生产中使用,因为它返回对用户没有用的消息。

UseStatusCodePagesWithRedirects

UseStatusCodePagesWithRedirects 扩展方法:

  • 向客户端发送“302 - 已找到”状态代码。
  • 将客户端重定向到 URL 模板中提供的错误处理终结点。 错误处理终结点通常会显示错误信息并返回 HTTP 200。
1
2
3
4
5
6
7
8
9
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

  URL 模板可能会包括状态代码的 {0} 占位符,如前面的代码中所示。 如果 URL 模板以波形符 ~(代字号)开头,则 ~ 会替换为应用的 PathBase。

  使用此方法通常是当应用:

  • 应将客户端重定向到不同的终结点(通常在不同的应用处理错误的情况下)。 对于 Web 应用,客户端的浏览器地址栏反映重定向终结点。
  • 不应保留原始状态代码并通过初始重定向响应返回该代码。

UseStatusCodePagesWithReExecute

  UseStatusCodePagesWithReExecute 扩展方法:

  • 通过使用备用路径重新执行请求管道,从而生成响应正文。
  • 不会在重新执行管道之前或之后更改状态代码。

  新管道执行可能会更改响应的状态代码,因为新管道可完全控制状态代码。 如果新管道不更改状态代码,则会将原始状态代码发送到客户端。

1
2
3
4
5
6
7
8
9
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

  使用此方法通常是当应用应:

  • 处理请求,但不重定向到不同终结点。 对于 Web 应用,客户端的浏览器地址栏反映最初请求的终结点。
  • 保留原始状态代码并通过响应返回该代码。

  URL 模板必须以 / 开头,可能包括状态代码的占位符 {0}。 若要将状态代码作为查询字符串参数传递,请向 UseStatusCodePagesWithReExecute 传递第二个参数。 例如:

1
2
var app = builder.Build();  
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

数据库错误页

  数据库开发人员页面异常筛选器 AddDatabaseDeveloperPageExceptionFilter 捕获可以使用 Entity Framework Core 迁移解决的与数据库相关的异常。 当这些异常出现时,便会生成 HTML 响应,其中包含用于解决问题的可能操作的详细信息。 仅在开发环境中启用此页。 下面的代码添加数据库开发人员页异常筛选器:

1
2
3
4
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

问题详细信息

  在 ASP.NET Core 应用中,下列中间件会在调用 AddProblemDetails 时生成问题详细信息 HTTP 响应,除非 Accept 请求 HTTP 标头不包含注册的 IProblemDetailsWriter 支持的内容类型之一(默认:application/json):

  • ExceptionHandlerMiddleware :未定义自定义处理程序时生成问题详细信息响应。
  • StatusCodePagesMiddleware :默认生成问题详细信息响应。
  • DeveloperExceptionPageMiddleware :当 Accept 请求 HTTP 标头不包含 text/html 时,在开发中生成问题详细信息响应。

  以下代码将应用配置为为所有 尚未包含正文内容的 HTTP 客户端和服务器错误响应生成问题详细信息响应:

1
2
3
4
5
6
7
8
9
10
11
builder.Services.AddProblemDetails();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler();
app.UseHsts();
}

app.UseStatusCodePages();