codecamp

处理 ASP.NET Core 中的错误

本文介绍了处理 ASP.NET Core 应用中常见错误的一些方法。

查看或下载示例代码如何下载

开发人员异常页

要将应用配置为显示有关请求异常的详细信息的页面,请使用“开发人员异常页”。 该页面通过 Microsoft.AspNetCore.App 元包中的 Microsoft.AspNetCore.Diagnostics 包提供。 当应用在开发环境中运行时,在 Startup.Configure 方法中添加一行:

C#

app.UseDeveloperExceptionPage();

将对 UseDeveloperExceptionPage 的调用放在要对其捕获异常的任何中间件前面。

 警告

仅当应用程序在开发环境中运行时才启用开发人员异常页。 否则当应用程序在生产环境中运行时,详细的异常信息会向公众泄露 有关配置环境的详细信息,请参阅 在 ASP.NET Core 中使用多个环境

要查看开发人员异常页,请将环境设置为 Development,运行示例应用,并向应用的基 URL 添加 ?throw=true。 该页包括关于异常和请求的以下信息:

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

配置自定义异常处理页

如果应用未在开发环境中运行,则调用 UseExceptionHandler 扩展方法,添加异常处理中间件。 中间件:

  • 捕获异常。
  • 记录异常。
  • 在备用管道中为指定的页或控制器重新执行请求。 如果响应已启动,则不会重新执行请求。

在示例应用的以下示例中,UseExceptionHandler 在非开发环境中添加了异常处理中间件。 在捕获并记录异常后,扩展方法指定 /Error 终结点处重新执行了请求的错误页或控制器:

C#

app.UseExceptionHandler("/Error");

Razor Pages 应用模板在“Pages”文件夹中提供了一个错误页 (.cshtml) 和 PageModel 类 (ErrorModel)。

在 MVC 应用中,以下错误处理程序方法包含在 MVC 应用模板中并在主控制器中显示:

C#

[AllowAnonymous]
public IActionResult Error()
{
    return View(new ErrorViewModel 
        { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}

不要使用 HTTP 方法属性(如 HttpGet)修饰错误处理程序操作方法。 显式谓词可阻止某些请求访问方法。 允许匿名访问方法,以便未经身份验证的用户能够接收错误视图。

访问异常

使用 IExceptionHandlerPathFeature 访问控制器或页中的异常或原始请求路径:

C#

// using Microsoft.AspNetCore.Diagnostics;

var exceptionHandlerPathFeature = 
    HttpContext.Features.Get<IExceptionHandlerPathFeature>();
var path = exceptionHandlerPathFeature?.Path;
var error = exceptionHandlerPathFeature?.Error;

 警告

不要向客户端提供来自 IExceptionHandlerFeature 或 IExceptionHandlerPathFeature 的敏感错误信息。 提供服务的错误是一种安全风险。

配置自定义异常处理代码

使用自定义异常处理页为错误提供终结点的另一种方法是为 UseExceptionHandler 提供 lambda。 使用带 UseExceptionHandler 的 lambda 允许在返回响应之前访问错误。

示例应用演示了 Startup.Configure 中的自定义异常处理代码。 使用“索引”页上的“引发异常”链接触发异常。 运行如下 lambda:

C#

// using Microsoft.AspNetCore.Diagnostics;

app.UseExceptionHandler(errorApp =>
{
    errorApp.Run(async context =>
    {
        context.Response.StatusCode = 500;
        context.Response.ContentType = "text/html";

        await context.Response.WriteAsync("<html lang=\"en\"><body>\r\n");
        await context.Response.WriteAsync("ERROR!<br><br>\r\n");

        var exceptionHandlerPathFeature = 
            context.Features.Get<IExceptionHandlerPathFeature>();

        // Use exceptionHandlerPathFeature to process the exception (for example, 
        // logging), but do NOT expose sensitive error information directly to 
        // the client.

        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            await context.Response.WriteAsync("File error thrown!<br><br>\r\n");
        }

        await context.Response.WriteAsync("<a href=\"/\">Home</a><br>\r\n");
        await context.Response.WriteAsync("</body></html>\r\n");
        await context.Response.WriteAsync(new string(' ', 512)); // IE padding
    });
});

 警告

不要向客户端提供来自 IExceptionHandlerFeature 或 IExceptionHandlerPathFeature 的敏感错误信息。 提供服务的错误是一种安全风险。

配置状态代码页

默认情况下,ASP.NET Core 应用不会为 HTTP 状态代码(如“404 - 未找到”)提供状态代码页。 应用返回状态代码和空响应正文。 要提供状态代码页,请使用状态代码页中间件。

该中间件通过 Microsoft.AspNetCore.App 元包中的 Microsoft.AspNetCore.Diagnostics 包提供。

向 Startup.Configure 方法添加代码行:

C#

app.UseStatusCodePages();

在请求处理中间件(例如,静态文件中间件和 MVC 中间件)之前调用 UseStatusCodePages 方法。

默认情况下,状态代码页中间件为常见状态代码(如“404 - 未找到”)添加纯文本处理程序:

Status Code: 404; Not Found

中间件支持几种可用于自定义其行为的扩展方法。

重载 UseStatusCodePages 采用 lambda 表达式,可用于处理自定义错误处理逻辑和手动编写响应:

C#

// using Microsoft.AspNetCore.Http;

app.UseStatusCodePages(async context =>
{
    context.HttpContext.Response.ContentType = "text/plain";

    await context.HttpContext.Response.WriteAsync(
        "Status code page, status code: " + 
        context.HttpContext.Response.StatusCode);
});

重载 UseStatusCodePages 采用内容类型和格式字符串,可用于自定义内容类型和响应文本:

C#

app.UseStatusCodePages("text/plain", "Status code page, status code: {0}");

重定向和重新执行扩展方法

UseStatusCodePagesWithRedirects

  • 向客户端发送“302 - 已找到”状态代码。
  • 将客户端重定向到 URL 模板中的位置。

C#

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

通常,应用在以下情况下使用 UseStatusCodePagesWithRedirects

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

UseStatusCodePagesWithReExecute

  • 向客户端返回原始状态代码。
  • 通过使用备用路径重新执行请求管道,从而生成响应正文。

C#

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

应用在以下情况下通常使用 UseStatusCodePagesWithReExecute

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

模板可能包括状态代码的占位符 ({0})。 模板必须以正斜杠 (/) 开头。 使用占位符时,请确认终结点(页或控制器)可以处理路径段。 例如,错误的 Razor Page 应通过 @page 指令接受可选路径段值:

CSHTML

@page "{code?}"

可禁用 Razor 页处理程序方法或 MVC 控制器中的特定请求的状态代码页。 要禁用状态代码页,请尝试从请求的 HttpContext.Features 集合中检索 IStatusCodePagesFeature,并在功能可用时禁用该功能:

C#

var statusCodePagesFeature = HttpContext.Features.Get<IStatusCodePagesFeature>();

if (statusCodePagesFeature != null)
{
    statusCodePagesFeature.Enabled = false;
}

若要在应用中使用指向终结点的 UseStatusCodePages 重载,请为终结点创建 MVC 视图或 Razor Page。 例如,Razor Pages 应用模板将生成以下页和页模型类:

Error.cshtml:

CSHTML

@page
@model ErrorModel
@{
    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>
    </p>
}

<h3>Development Mode</h3>
<p>
    Swapping to the <strong>Development</strong> environment displays 
    detailed information about the error that occurred.
</p>
<p>
    <strong>The Development environment shouldn't be enabled for deployed 
    applications.</strong> It can result in displaying sensitive information 
    from exceptions to end users. For local debugging, enable the 
    <strong>Development</strong> environment by setting the 
    <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to 
    <strong>Development</strong> and restarting the app.
</p>

Error.cshtml.cs:

C#

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
    public string RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
    }
}

异常处理代码

异常处理页中的代码可能会引发异常。 建议在生产错误页面中包含纯静态内容。

此外,请注意,发送响应标头后:

  • 应用无法更改响应的状态代码。
  • 任何异常页或处理程序都无法运行。 必须完成响应或中止连接。

服务器异常处理

除应用中的异常处理逻辑外,服务器实现也可以处理一些异常。 如果服务器在发送响应标头之前捕获到异常,服务器将发送不包含响应正文的“500 - 内部服务器错误”响应。 如果服务器在发送响应标头后捕获到异常,服务器会关闭连接。 应用程序无法处理的请求将由服务器进行处理。 当服务器处理请求时,发生的任何异常都将由服务器的异常处理进行处理。 应用的自定义错误页面、异常处理中间件和筛选器都不会影响此行为。

启动异常处理

应用程序启动期间发生的异常仅可在承载层进行处理。 借助 Web 主机,可以使用 captureStartupErrors 和 detailedErrors 键配置主机在响应启动期间出现错误时的行为方式

仅当捕获的启动错误发生在主机地址/端口绑定之后,承载层才会为该错误显示错误页。 如果出于任何原因导致任何绑定失败:

  • 托管层将记录关键异常。
  • dotnet 进程崩溃。
  • 当应用在 Kestrel 服务器上运行时,不会显示错误页。

在 IIS 或 IIS Express 上运行应用时,如果无法启动进程,ASP.NET Core 模块将返回“502.5 - 进程失败”。 有关更多信息,请参见对 IIS 上的 ASP.NET Core 进行故障排除。 要了解如何排查 Azure 应用服务的启动问题,请参阅 对 Azure 应用服务上的 ASP.NET Core 进行故障排除

ASP.NET Core MVC 错误处理

MVC 应用还有一些其他的错误处理选项,例如配置异常筛选器和执行模型验证。

异常筛选器

在 MVC 应用中,异常筛选器可以进行全局配置,也可以为每个控制器或每个操作单独配置。 这些筛选器处理在执行控制器操作或其他筛选器时出现的任何未处理的异常。 不会以其他方式调用这些筛选器。 有关更多信息,请参见ASP.NET Core 中的筛选器

 提示

异常筛选器适合捕获 MVC 操作内发生的异常,但它们不如异常处理中间件灵活。 建议使用中间件。 仅在需要根据所选 MVC 操作以不同方式执行错误处理时,才使用筛选器。

处理模型状态错误

模型验证在每个控制器操作被调用之前发生,操作方法负责检查 ModelState.IsValid 并相应地作出反应。

某些应用使用标准约定处理模型验证错误,在这种情况下,使用筛选器可以更好地实施这种策略。你需要使用无效模型状态测试操作的行为。 有关更多信息,请参见ASP.NET Core 中的测试控制器逻辑


ASP.NET Core 中的路由
在 ASP.NET Core 中使用 IHttpClientFactory 发出 HTTP 请求
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }