【问题标题】:Exception-Handling Middleware and Page异常处理中间件和页面
【发布时间】:2017-12-01 03:39:18
【问题描述】:

我是中间件概念的新手,目前正在努力寻找在我的 MVC Core 项目中处理异常的正确方法。

我想要发生的是捕获、记录异常,然后将用户发送到带有消息的友好错误页面。起初,我试图在中间件中管理所有这些,但意识到我可能做得不对。

所以如果我希望这个流程发生,我是否应该同时使用我的异常记录中间件和 app.UseExceptionHandler("/Error") 以便中间件将异常重新抛出到页面?如果是这样,我如何在错误页面中获取异常详细信息?我想先拦截的异常处理机制应该是Configure中的最后一个是否正确?

我发现的所有示例都严格处理 HTTP 状态码错误,例如 404;我正在寻找处理实际异常(及其子类)。这样,在我的视图页面中,我可以在适用的情况下抛出我自己的异常(例如,如果视图为必填字段提供了 null。)

Startup.cs sn-p:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
                      ILoggerFactory loggerFactory, LoanDbContext context) {
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    app.UseStatusCodePages();
    if (env.IsDevelopment() || env.IsEnvironment("qa")) {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    } else {
        app.UseExceptionHandler("/Error");
    }
    app.UseMiddleware<MyExceptionHandler>(loggerFactory);
    // ... rest of Configure is irrelevant to this issue

MyExceptionHandler.cs

using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;

namespace MyCompany.MyProject.Helpers
{
    /// <summary>
    /// A custom Exception Handler Middleware which can be re-used in other projects.
    /// </summary>
    public sealed class MyExceptionHandler
    {
        private readonly RequestDelegate _next;
        private readonly ILogger _logger;

        public MyExceptionHandler(RequestDelegate next, ILoggerFactory loggerFactory) {
            _next = next;
            _logger = loggerFactory.CreateLogger<MyExceptionHandler>();
        }

        public async Task Invoke(HttpContext context) {
            try {
                await _next(context);
            } catch (Exception ex) {
                HandleException(ex);
            }
        }

        // I realize this function looks pointless, but it will have more meat to it eventually.
        public void HandleException(Exception ex) {
            if (ex is ArgumentException argEx) {
                _logger.LogError(0, argEx, argEx.Message);
            } else if (ex is InvalidOperationException ioEx) {
                _logger.LogError(0, ioEx, "An Invalid Operation Exception occurred. This is usually caused by a database call that expects "
                    + "one result, but receives none or more than one.");
            } else if (ex is SqlException sqlEx) {
                _logger.LogError(0, sqlEx, $"A SQL database exception occurred. Error Number {sqlEx.Number}");
            } else if (ex is NullReferenceException nullEx) {
                _logger.LogError(0, nullEx, $"A Null Reference Exception occurred. Source: {nullEx.Source}.");
            } else if (ex is DbUpdateConcurrencyException dbEx) {
                _logger.LogError(0, dbEx, "A database error occurred while trying to update your item. This is usually due to someone else modifying the item since you loaded it.");
            } else {
                _logger.LogError(0, ex, "An unhandled exception has occurred.")
            }
        }
    }
}

【问题讨论】:

  • 只是要求好的措施。你查过官方文档docs.microsoft.com/en-us/aspnet/core/fundamentals/…
  • 还有一篇好文章scottsauber.com/2017/04/03/…
  • 是的,他们提供了一个 sn-p 代码“这是您使用 /Error 页面的方式”。结束。如果您的错误页面只是“嘿,发生了一些糟糕的事情!”,那就太好了。没有定制。他们说“在这里处理你的错误”,但没有说明如何获取异常。它在响应中吗?查看包?我不确定接下来的步骤。不过,第二个链接更有用。是否不担心会发生阻止呈现错误视图的异常?
  • 看这篇文章的自定义错误中间件。 dusted.codes/error-handling-in-aspnet-core
  • 是的,他们特别提到了在处理时尝试处理视图错误,然后使用自定义处理程序,因为它将停止渲染。

标签: c# exception-handling asp.net-core asp.net-core-mvc asp.net-core-middleware


【解决方案1】:

我是否应该同时使用我的异常记录中间件和app.UseExceptionHandler("/Error"),以便中间件重新向页面抛出异常?

是的。

仅使用示例的 sn-p。

public async Task Invoke(HttpContext context) {
    try {
        await _next(context);
    } catch (Exception ex) {
        HandleException(ex);
         // re -throw the original exception
         // after logging the information
        throw;
    }
}

上面将在记录后重新抛出原始错误,以便管道中的其他处理程序将捕获它并进行开箱即用的处理。

来源Error Handling in ASP.NET Core

如何在错误页面获取异常详情?

使用IExceptionHandlerPathFeature获取异常和路径

public IActionResult Error()
{
    // Get the details of the exception that occurred
    var exceptionFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();

    if (exceptionFeature != null)
    {
        // Get which route the exception occurred at
        string routeWhereExceptionOccurred = exceptionFeature.Path;

        // Get the exception that occurred
        Exception exceptionThatOccurred = exceptionFeature.Error;

        // TODO: Do something with the exception
        // Log it with Serilog?
        // Send an e-mail, text, fax, or carrier pidgeon?  Maybe all of the above?
        // Whatever you do, be careful to catch any exceptions, otherwise you'll end up with a blank page and throwing a 500
    }

    return View();
}

来源Adding Global Error Handling and Logging in ASP.NET Core with IExceptionHandlerPathFeature

在上面的示例中,他们提到在视图中进行日志记录,但您已经在自定义处理程序中完成了。

在呈现错误视图时要特别注意这个小注释。

无论你做什么,都要小心捕捉任何异常,否则你会得到一个空白页并抛出 500

通过插入管道,您可以避免重新发明框架已经提供的开箱即用的功能,并且还可以管理横切关注点。

【讨论】:

  • 太好了,谢谢!我担心我在某种程度上是多余的,或者使用糟糕的设计来尝试实现这两个功能。这将是一个巨大的帮助。中间件在 Core 中非常漂亮。使许多编码任务变得更容易,并避免不必要的复制。
  • @AndrewS 中间件的可插拔特性非常有用,我见过的所有语言都使用它。松耦合使事情变得非常灵活、干燥和稳固。哈哈。乐意效劳。编码快乐!!!!
  • @AndrewS 为了满足我的强迫症,你所谓的自定义异常处理程序现在实际上被认为是一个异常记录器中间件,因为已经存在一个内置的异常处理程序中间件。我知道,为事物命名很难。哈哈。但这种方式清楚地解释了单一职责。
  • 好点!我最初的意图是将日志记录和错误页面捆绑到同一个中间件中,但现在我已经分离了关注点,重命名是有意义的。
  • 嘿@Nkosi 你能帮我解决以下问题吗? stackoverflow.com/questions/52049727/…
猜你喜欢
  • 2019-04-04
  • 2019-10-08
  • 2014-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-03
  • 2021-05-13
相关资源
最近更新 更多