理解ASP.NET Core 错误处理机制(Handle Errors)

目录
  • 使用中间件进行错误处理
    • 开发人员异常页
      • IDeveloperPageExceptionFilter
    • 异常处理程序
      • 通过lambda提供异常处理程序
      • 异常处理程序页
    • 无响应正文的Http错误状态码处理
      • UseStatusCodePages
      • UseStatusCodePagesWithRedirects
      • UseStatusCodePagesWithReExecute
    • 使用过滤器进行错误处理
    • 错误处理中间件 VS 异常过滤器

注:本文隶属于《理解ASP.NET Core》系列文章,请查看置顶博客或点击此处查看全文目录

使用中间件进行错误处理

开发人员异常页

开发人员异常页用于显示未处理的请求异常的详细信息。当我们通过ASP.NET Core模板创建一个项目时,Startup.Configure方法中会自动生成以下代码:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        // 添加开发人员异常页中间件
        app.UseDeveloperExceptionPage();
    }
}

需要注意的是,与“异常处理”有关的中间件,一定要尽早添加,这样,它可以最大限度的捕获后续中间件抛出的未处理异常。

可以看到,当程序运行在开发环境中时,才会启用开发人员异常页,这很好理解,因为在生产环境中,我们不能将异常的详细信息暴露给用户,否则,这将会导致一系列安全问题。

现在我们在下方添加如下代码抛出一个异常:

app.Use((context, next) =>
{
    throw new NotImplementedException();
});

当开发人员异常页中间件捕获了该未处理异常时,会展示类似如下的相关信息:

该异常页面展示了如下信息:

  • 异常消息
  • 异常堆栈追踪(Stack)
  • HTTP请求查询参数(Query)
  • Cookies
  • HTTP请求标头(Headers)
  • 路由(Routing),包含了终结点和路由信息

IDeveloperPageExceptionFilter

当你查看DeveloperExceptionPageMiddleware的源码时,你会在构造函数中发现一个入参,类型为IEnumerable<IDeveloperPageExceptionFilter>。通过这个Filter集合,组成一个错误处理器管道,按照先注册先执行的原则,顺序进行错误处理。

下面是DeveloperExceptionPageMiddleware的核心源码:

public class DeveloperExceptionPageMiddleware
{
    public DeveloperExceptionPageMiddleware(
        RequestDelegate next,
        IOptions<DeveloperExceptionPageOptions> options,
        ILoggerFactory loggerFactory,
        IWebHostEnvironment hostingEnvironment,
        DiagnosticSource diagnosticSource,
        IEnumerable<IDeveloperPageExceptionFilter> filters)
    {
        // ...

        // 将 DisplayException 放置在管道最底部
        // DisplayException 就用于向响应中写入我们上面见到的异常页
        _exceptionHandler = DisplayException;

        foreach (var filter in filters.Reverse())
        {
            var nextFilter = _exceptionHandler;
            _exceptionHandler = errorContext => filter.HandleExceptionAsync(errorContext, nextFilter);
        }
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            // 响应已经启动,则跳过处理,直接上抛
            if (context.Response.HasStarted)
            {
                throw;
            }

            try
            {
                context.Response.Clear();
                context.Response.StatusCode = 500;

                // 错误处理
                await _exceptionHandler(new ErrorContext(context, ex));

                // ...

                // 错误已成功处理
                return;
            }
            catch (Exception ex2) { }

            // 若处理过程中抛出了新的异常ex2,则重新引发原始异常ex
            throw;
        }
    }
}

这也就说明,如果我们想要自定义开发者异常页,那我们可以通过实现IDeveloperPageExceptionFilter接口来达到目的。

先看一下IDeveloperPageExceptionFilter接口定义:

public interface IDeveloperPageExceptionFilter
{
    Task HandleExceptionAsync(ErrorContext errorContext, Func<ErrorContext, Task> next);
}

public class ErrorContext
{
    public ErrorContext(HttpContext httpContext, Exception exception)
    {
        HttpContext = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
        Exception = exception ?? throw new ArgumentNullException(nameof(exception));
    }

    public HttpContext HttpContext { get; }

    public Exception Exception { get; }
}

HandleExceptionAsync方法除了错误上下文信息外,还包含了一个Func<ErrorContext, Task> next,这是干嘛的呢?其实,前面我们已经提到了,IDeveloperPageExceptionFilter的所有实现,会组成一个管道,当错误需要在管道中的后续处理器作进一步处理时,就是通过这个next传递错误的,所以,当需要传递错误时,一定要记得调用next

不废话了,赶紧实现一个看看效果吧:

public class MyDeveloperPageExceptionFilter : IDeveloperPageExceptionFilter
{
    public Task HandleExceptionAsync(ErrorContext errorContext, Func<ErrorContext, Task> next)
    {
        errorContext.HttpContext.Response.WriteAsync($"MyDeveloperPageExceptionFilter: {errorContext.Exception}");

        // 我们不调用 next,这样就不会执行 DisplayException

        return Task.CompletedTask;
    }
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDeveloperPageExceptionFilter, MyDeveloperPageExceptionFilter>();
}

当抛出一个异常,你会看到类似如下的页面:

异常处理程序

上面介绍了开发环境中的异常处理,现在我们来看一下生产环境中的异常处理,通过调用UseExceptionHandler扩展方法注册中间件ExceptionHandlerMiddleware

该异常处理程序:

  • 可以捕获后续中间件未处理的异常
  • 若无异常或HTTP响应已经启动(Response.HasStarted == true),则不做任何处理
  • 不会改变URL中的路径

默认情况下,会生成类似如下的模板:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        // 添加异常处理程序
        app.UseExceptionHandler("/Home/Error");
    }
}

通过lambda提供异常处理程序

我们可以通过lambda向UseExceptionHandler中提供一个异常处理逻辑:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseExceptionHandler(errorApp =>
    {
        var loggerFactory = errorApp.ApplicationServices.GetRequiredService<ILoggerFactory>();
        var logger = loggerFactory.CreateLogger("ExceptionHandlerWithLambda");

        errorApp.Run(async context =>
        {
            // 这里可以自定义 http response 内容,以下仅是示例

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

            logger.LogError($"Exception Handled:{exceptionHandlerPathFeature.Error}");

            var statusCode = StatusCodes.Status500InternalServerError;
            var message = exceptionHandlerPathFeature.Error.Message;

            if (exceptionHandlerPathFeature.Error is NotImplementedException)
            {
                message = "俺未实现";
                statusCode = StatusCodes.Status501NotImplemented;
            }

            context.Response.StatusCode = statusCode;
            context.Response.ContentType = "application/json";
            await context.Response.WriteAsJsonAsync(new
            {
                Message = message,
                Success = false,
            });

        });
    });
}

可以看到,当捕获到异常时,可以通过HttpContext.Features,并指定类型IExceptionHandlerPathFeatureIExceptionHandlerFeature(前者继承自后者),来获取到异常信息。

public interface IExceptionHandlerFeature
{
    // 异常信息
    Exception Error { get; }
}

public interface IExceptionHandlerPathFeature : IExceptionHandlerFeature
{
    // 未被转义的http请求资源路径
    string Path { get; }
}

再提醒一遍,千万不要将敏感的错误信息暴露给客户端。

异常处理程序页

除了使用lambda外,我们还可以指定一个路径,指向一个备用管道进行异常处理,这个备用管道对于MVC来说,一般是Controller中的Action,例如MVC模板默认的/Home/Error

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseExceptionHandler("/Home/Error");
}

public class HomeController : Controller
{
    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
        return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }
}

当捕获到异常时,你会看到类似如下的页面:

你可以在ActionError中自定义错误处理逻辑,就像lambda一样。

需要注意的是,不要随意对Error添加[HttpGet][HttpPost]等限定Http请求方法的特性。一旦你加上了[HttpGet],那么该方法只能处理Get请求的异常。

不过,如果你就是打算将不同方法的Http请求分别进行处理,你可以类似如下进行处理:

public class HomeController : Controller
{
    // 处理Get请求的异常
    [HttpGet("[controller]/error")]
    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult GetError()
    {
        _logger.LogInformation("Get Exception Handled");

        return View("Error", new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }

    // 处理Post请求的异常
    [HttpPost("[controller]/error")]
    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult PostError()
    {
        _logger.LogInformation("Post Exception Handled");

        return View("Error", new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }
}

另外,还需要提醒一下,如果在请求备用管道(如示例中的Error)时也报错了,无论是Http请求管道中的中间件报错,还是Error里面报错,此时ExceptionHandlerMiddleware均会重新引发原始异常,而不是向外抛出备用管道的异常。

一般异常处理程序页是面向所有用户的,所以请保证它可以匿名访问。

下面一块看一下ExceptionHandlerMiddleware吧:

public class ExceptionHandlerMiddleware
{
    public ExceptionHandlerMiddleware(
        RequestDelegate next,
        ILoggerFactory loggerFactory,
        IOptions<ExceptionHandlerOptions> options,
        DiagnosticListener diagnosticListener)
    {
        // 要么手动指定一个异常处理器(如通过lambda)
        // 要么提供一个资源路径,重新发送给后续中间件,进行异常处理
        if (_options.ExceptionHandler == null)
        {
            if (_options.ExceptionHandlingPath == null)
            {
                throw new InvalidOperationException(Resources.ExceptionHandlerOptions_NotConfiguredCorrectly);
            }
            else
            {
                _options.ExceptionHandler = _next;
            }
        }
    }

    public Task Invoke(HttpContext context)
    {
        ExceptionDispatchInfo edi;
        try
        {
            var task = _next(context);
            if (!task.IsCompletedSuccessfully)
            {
                return Awaited(this, context, task);
            }

            return Task.CompletedTask;
        }
        catch (Exception exception)
        {
            edi = ExceptionDispatchInfo.Capture(exception);
        }

        // 同步完成并抛出异常时,进行处理
        return HandleException(context, edi);

        static async Task Awaited(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
        {
            ExceptionDispatchInfo edi = null;
            try
            {
                await task;
            }
            catch (Exception exception)
            {
                edi = ExceptionDispatchInfo.Capture(exception);
            }

            if (edi != null)
            {
                // 异步完成并抛出异常时,进行处理
                await middleware.HandleException(context, edi);
            }
        }
    }

    private async Task HandleException(HttpContext context, ExceptionDispatchInfo edi)
    {
        // 响应已经启动,则跳过处理,直接上抛
        if (context.Response.HasStarted)
        {
            edi.Throw();
        }

        PathString originalPath = context.Request.Path;
        if (_options.ExceptionHandlingPath.HasValue)
        {
            context.Request.Path = _options.ExceptionHandlingPath;
        }
        try
        {
            ClearHttpContext(context);

            // 将 exceptionHandlerFeature 存入 context.Features
            var exceptionHandlerFeature = new ExceptionHandlerFeature()
            {
                Error = edi.SourceException,
                Path = originalPath.Value,
            };
            context.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature);
            context.Features.Set<IExceptionHandlerPathFeature>(exceptionHandlerFeature);
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            context.Response.OnStarting(_clearCacheHeadersDelegate, context.Response);

            // 处理异常
            await _options.ExceptionHandler(context);

            if (context.Response.StatusCode != StatusCodes.Status404NotFound || _options.AllowStatusCode404Response)
            {
                return;
            }
        }
        catch (Exception ex2) { }
        finally
        {
            // 还原请求路径,保证浏览器中的Url不变
            context.Request.Path = originalPath;
        }

        // 如果异常未被处理,则重新引发原始异常
        edi.Throw();
    }
}

无响应正文的Http错误状态码处理

默认情况下,当ASP.NET Core遇到没有正文的400-599Http错误状态码时,不会为其提供页面,而是返回状态码和空响应正文。可是,为了良好的用户体验,一般我们会对常见的错误状态码(404)提供友好的页面,如gitee404

请注意,本节所涉及到的中间件与上两节所讲解的错误异常处理中间件不冲突,可以同时使用。确切的说,本节并不是处理异常,只是为了提升用户体验。

UseStatusCodePages

我们可以通过StatusCodePagesMiddleware中间件实现该功能:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseDeveloperExceptionPage();

    // 添加 StatusCodePagesMiddleware 中间件
    app.UseStatusCodePages();

    // ...请求处理中间件
    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

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

注意,一定要在异常处理中间件之后,请求处理中间件之前调用UseStatusCodePages

现在,你可以请求一个不存在的路径,例如Home/Index2,你会在浏览器中看到如下输出:

Status Code: 404; Not Found 

UseStatusCodePages也提供了重载,允许我们自定义响应内容类型和正文内容,如:

// 使用占位符 {0} 来填充Http状态码
app.UseStatusCodePages("text/plain", "Status code is: {0}");

浏览器输出为:

Status code is: 404

同样地,我们也可以通过向UseStatusCodePages传入lambda表达式进行处理:

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

    await context.HttpContext.Response.WriteAsync(
        $"Status code is: {context.HttpContext.Response.StatusCode}");
});

介绍了那么多,你也看到了,事实上UseStatusCodePages效果并不好,所以我们在生产环境一般是不会用这玩意的,那用啥呢?请随我继续往下看。

UseStatusCodePagesWithRedirects

该扩展方法,内部实际上是通过调用UseStatusCodePages并传入lambda进行实现的,该方法:

  • 接收一个Http资源定位字符串。同样的,会有一个占位符{0},用于填充Http状态码
  • 向客户端发送Http状态码302-已找到
  • 然后将客户端重定向到指定的终结点,在该终结点中,可以针对不同错误状态码分别进行处理
app.UseStatusCodePagesWithRedirects("/Home/StatusCodeError?code={0}");

public class HomeController : Controller
{
    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult StatusCodeError(int code)
    {
        return code switch
        {
            // 跳转到404页面
            StatusCodes.Status404NotFound => View("404"),
            // 跳转到统一展示页面
            _ => View(code),
        };
    }
}

现在你可以自己试一下。

不知道你有没有注意:当我们请求一个不存在的路径时,它的确会跳转到404页面,但是,Url也变了,变成了/Home/StatusCodeError?code=404,而且,响应状态码也变了,变成了200Ok。可以通过源码看一下咋回事(我相信,大家看到302其实也都明白了):

public static IApplicationBuilder UseStatusCodePagesWithRedirects(this IApplicationBuilder app, string locationFormat)
{
    // 两个条件分支都差不多,我们看第二个,容易理解一些
    if (locationFormat.StartsWith("~"))
    {
        locationFormat = locationFormat.Substring(1);
        return app.UseStatusCodePages(context =>
        {
            var location = string.Format(CultureInfo.InvariantCulture, locationFormat, context.HttpContext.Response.StatusCode);
            context.HttpContext.Response.Redirect(context.HttpContext.Request.PathBase + location);
            return Task.CompletedTask;
        });
    }
    else
    {
        return app.UseStatusCodePages(context =>
        {
            // 格式化资源定位,context.HttpContext.Response.StatusCode 作占位符
            var location = string.Format(CultureInfo.InvariantCulture, locationFormat, context.HttpContext.Response.StatusCode);
            // 重定向(302)到设定的资源
            context.HttpContext.Response.Redirect(location);
            return Task.CompletedTask;
        });
    }
}

如果你不想更改原始请求的Url,而且保留原始状态码,那么你应该使用接下来要介绍的UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute

同样的,该扩展方法,内部也是通过调用UseStatusCodePages并传入lambda进行实现的,不过该方法:

  • 接收1个路径字符串和和1个查询字符串。同样的,会有一个占位符{0},用于填充Http状态码
  • Url保持不变,并向客户端返回原始Http状态码
  • 执行备用管道,用于生成响应正文
// 注意,这里要分开写
app.UseStatusCodePagesWithReExecute("/Home/StatusCodeError", "?code={0}");

具体例子就不再列举了,用上面的就行了。现在来看看源码:

public static IApplicationBuilder UseStatusCodePagesWithReExecute(
    this IApplicationBuilder app,
    string pathFormat,
    string queryFormat = null)
{
    return app.UseStatusCodePages(async context =>
    {
        // 请注意,此时Http响应还未启动

        // 格式化资源路径,context.HttpContext.Response.StatusCode 作占位符
        var newPath = new PathString(
            string.Format(CultureInfo.InvariantCulture, pathFormat, context.HttpContext.Response.StatusCode));
        // 格式化查询字符串,context.HttpContext.Response.StatusCode 作占位符
        var formatedQueryString = queryFormat == null ? null :
            string.Format(CultureInfo.InvariantCulture, queryFormat, context.HttpContext.Response.StatusCode);
        var newQueryString = queryFormat == null ? QueryString.Empty : new QueryString(formatedQueryString);

        var originalPath = context.HttpContext.Request.Path;
        var originalQueryString = context.HttpContext.Request.QueryString;
        // 将原始请求信息保存下来,以便后续进行还原
        context.HttpContext.Features.Set<IStatusCodeReExecuteFeature>(new StatusCodeReExecuteFeature()
        {
            OriginalPathBase = context.HttpContext.Request.PathBase.Value,
            OriginalPath = originalPath.Value,
            OriginalQueryString = originalQueryString.HasValue ? originalQueryString.Value : null,
        });

        context.HttpContext.SetEndpoint(endpoint: null);
        var routeValuesFeature = context.HttpContext.Features.Get<IRouteValuesFeature>();
        routeValuesFeature?.RouteValues?.Clear();

        // 构造新请求
        context.HttpContext.Request.Path = newPath;
        context.HttpContext.Request.QueryString = newQueryString;
        try
        {
            // 执行备用管道,生成响应正文
            await context.Next(context.HttpContext);
        }
        finally
        {
            // 还原原始请求信息
            context.HttpContext.Request.QueryString = originalQueryString;
            context.HttpContext.Request.Path = originalPath;
            context.HttpContext.Features.Set<IStatusCodeReExecuteFeature>(null);
        }
    });
}

在MVC中,你可以通过给控制器或其中的Action方法添加[SkipStatusCodePages]特性,可以略过StatusCodePagesMiddleware

使用过滤器进行错误处理

除了错误处理中间件外,ASP.NET Core 还提供了异常过滤器,用于错误处理。

异常过滤器:

  • 通过实现接口IExceptionFilterIAsyncExceptionFilter来自定义异常过滤器
  • 可以捕获Controller创建时(也就是只捕获构造函数中抛出的异常)、模型绑定、Action Filter和Action中抛出的未处理异常
  • 其他地方抛出的异常不会捕获

本节仅介绍异常过滤器,有关过滤器的详细内容,后续文章将会介绍

先来看一下这两个接口:

// 仅具有标记作用,标记其为 mvc 请求管道的过滤器
public interface IFilterMetadata { }

public interface IExceptionFilter : IFilterMetadata
{
    // 当抛出异常时,该方法会捕获
    void OnException(ExceptionContext context);
}

public interface IAsyncExceptionFilter : IFilterMetadata
{
    // 当抛出异常时,该方法会捕获
    Task OnExceptionAsync(ExceptionContext context);
}

OnExceptionOnExceptionAsync方法都包含一个类型为ExceptionContext参数,很显然,它就是与异常有关的上下文,我们的异常处理逻辑离不开它。那接着来看一下它的结构吧:

public class ExceptionContext : FilterContext
{
    // 捕获到的未处理异常
    public virtual Exception Exception { get; set; }

    public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; }

    // 指示异常是否已被处理
    // true:表示异常已被处理,异常不会再向上抛出
    // false:表示异常未被处理,异常仍会继续向上抛出
    public virtual bool ExceptionHandled { get; set; }

    // 设置响应的 IActionResult
    // 如果设置了结果,也表示异常已被处理,异常不会再向上抛出
    public virtual IActionResult? Result { get; set; }
}

除此之外,ExceptionContext还继承了FilterContext,而FilterContext又继承了ActionContext(这也从侧面说明,过滤器是为Action服务的),也就是说我们也能够获取到一些过滤器和Action相关的信息,看看都有什么吧:

public class ActionContext
{
    // Action相关的信息
    public ActionDescriptor ActionDescriptor { get; set; }

    // HTTP上下文
    public HttpContext HttpContext { get; set; }

    // 模型绑定和验证
    public ModelStateDictionary ModelState { get; }

    // 路由数据
    public RouteData RouteData { get; set; }
}

public abstract class FilterContext : ActionContext
{
    public virtual IList<IFilterMetadata> Filters { get; }

    public bool IsEffectivePolicy<TMetadata>(TMetadata policy) where TMetadata : IFilterMetadata {}

    public TMetadata FindEffectivePolicy<TMetadata>() where TMetadata : IFilterMetadata {}
}

更多参数细节,我会在专门讲过滤器的文章中详细介绍。

下面,我们就来实现一个自定义的异常处理器:

public class MyExceptionFilterAttribute : ExceptionFilterAttribute
{
    private readonly IModelMetadataProvider _modelMetadataProvider;

    public MyExceptionFilterAttribute(IModelMetadataProvider modelMetadataProvider)
    {
        _modelMetadataProvider = modelMetadataProvider;
    }

    public override void OnException(ExceptionContext context)
    {
        if (!context.ExceptionHandled)
        {
            // 此处仅为简单演示
            var exception = context.Exception;
            var result = new ViewResult()
            {
                ViewName = "Error",
                ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState)
                {
                    // 记得给 ErrorViewModel 加上 Message 属性
                    Model = new ErrorViewModel
                    {
                        Message = exception.ToString()
                    }
                }
            };

            context.Result = result;

            // 标记异常已处理
            context.ExceptionHandled = true;
        }
    }
}

接着,找到/Views/Shared/Error.cshtml,展示一下错误消息:

@model ErrorViewModel
@{
    ViewData["Title"] = "Error";
}

<p>@Model.Message</p>

最后,将服务MyExceptionFilterAttribute注册到DI容器:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<MyExceptionFilterAttribute>();

    services.AddControllersWithViews();
}

现在,我们将该异常处理器加在/Home/Index上,并抛个异常:

public class HomeController : Controller
{
    [ServiceFilter(typeof(MyExceptionFilterAttribute))]
    public IActionResult Index()
    {
        throw new Exception("Home Index Error");

        return View();
    }
}

当请求/Home/Index时,你会得到如下页面:

错误处理中间件 VS 异常过滤器

现在,我们已经介绍了两种错误处理的方法——错误处理中间件和异常过滤器。现在来比较一下它们的异同,以及我们何时应该选择哪种处理方式。

错误处理中间件:

  • 可以捕获后续中间件的所有未处理异常
  • 拥有RequestDelegate,操作更加灵活
  • 粒度较粗,仅可针对全局进行配置

错误处理中间件适合用于处理全局异常。

异常过滤器:

  • 仅可捕获Controller创建时(也就是构造函数中抛出的异常)、模型绑定、Action Filter和Action中抛出的未处理异常,其他地方抛出的异常捕获不到
  • 粒度更小,可以灵活针对Controller或Action配置不同的异常过滤器

异常过滤器非常适合用于捕获并处理Action中的异常。

在我们的应用中,可以同时使用错误处理中间件和异常过滤器,只有充分发挥它们各自的优势,才能处理好程序中的错误。

到此这篇关于理解ASP.NET Core 错误处理(Handle Errors) 的文章就介绍到这了,更多相关ASP.NET Core 错误处理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • ASP.NET Core应用JWT进行用户认证及Token的刷新方案

    目录 一.什么是JWT? 为什么要使用JWT? 二.JWT的组成: Header Payload Signature 三.认证流程 四.应用实例 认证服务 User相关: TokenHelper: 应用服务 五.Token的刷新 本文将通过实际的例子来演示如何在ASP.NET Core中应用JWT进行用户认证以及Token的刷新方案 一.什么是JWT? JWT(json web token)基于开放标准(RFC 7519),是一种无状态的分布式的身份验证方式,主要用于在网络应用环境间安全地传递声

  • ASP.NET Core  依赖注入框架的使用

    目录 一.IoC框架 二.IoC-Autofac 三..NET Core中自带DI的使用 四.Autofac 使用 五.批量注入 前言: 还记得上篇文章中ASP.NET Core 依赖注入详细最后提及到,假如服务越来越多怎么处理呢,本篇文章将会带来解决办法.这篇是接上一篇文章的,概念方面的可以参考上一篇文章. 一.IoC框架 先说说常见的Ioc框架吧. Autofac: 目前net用的比较多,好多大佬的项目比较优先选择的框架. Ninject: 已经很少用了,还时在很早的文章中见过. Unity

  • 三种方法解决ASP.NET Core 6中的依赖项

    依赖性注入是一种技术,它允许我们注入一个特定类的依赖对象,而不是直接创建这些实例. 使用依赖注入的好处显而易见,它通过放松模块间的耦合,来增强系统的可维护性和可测试性. 依赖注入允许我们修改具体实现,而不必改变依赖于它们的依赖类型. ASP.NET Core 很重视依赖注入技术.ASP.NET Core 中内置的依赖注入提供功能模块,并不像 StructureMap 和 Ninject 等IoC(控制反转)容器那样功能丰富,但它速度快,易于配置,而且易于使用.我们可以使用它在 ASP.NET C

  • 使用VS2022在ASP.NET Core中构建轻量级服务

    目录 1. 使用 VS2022 创建 ASP.NET Core 项目 2. 在 ASP.NET Core 中启用一个轻量级的服务 3. 在 ASP.NET Core 中使用 IEndpointConventionBuilder 扩展方法 4. 在 ASP.NET Core 中使用轻量级服务检索记录 5. 在 ASP.NET Core 中使用轻量级服务创建记录 6. 在 ASP.NET Core 中使用轻量级服务删除记录 7. ASP.NET Core 中轻量级服务的配置方法 8. 在 ASP.N

  • 理解ASP.NET Core 错误处理机制(Handle Errors)

    目录 使用中间件进行错误处理 开发人员异常页 IDeveloperPageExceptionFilter 异常处理程序 通过lambda提供异常处理程序 异常处理程序页 无响应正文的Http错误状态码处理 UseStatusCodePages UseStatusCodePagesWithRedirects UseStatusCodePagesWithReExecute 使用过滤器进行错误处理 错误处理中间件 VS 异常过滤器 注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博

  • 理解ASP.NET Core 中间件(Middleware)

    目录 中间件 中间件管道 Run Use UseWhen Map MapWhen Run & Use & UseWhen & Map & Map 编写中间件并激活 基于约定的中间件 基于工厂的中间件 基于约定的中间件 VS 基于工厂的中间件 中间件 先借用微软官方文档的一张图: 可以看到,中间件实际上是一种配置在HTTP请求管道中,用来处理请求和响应的组件.它可以: 决定是否将请求传递到管道中的下一个中间件 可以在管道中的下一个中间件处理之前和之后进行操作 此外,中间件的注

  • 理解ASP.NET Core 依赖注入(Dependency Injection)

    目录 依赖注入 什么是依赖注入 依赖注入有什么好处 ASP.NET Core内置的依赖注入 服务生存周期 服务释放 TryAdd{Lifetime}扩展方法 解析同一服务的多个不同实现 Replace && Remove 扩展方法 Autofac 服务解析和注入 构造函数注入 方法注入 属性注入 一些注意事项 框架默认提供的服务 依赖注入 什么是依赖注入 简单说,就是将对象的创建和销毁工作交给DI容器来进行,调用方只需要接收注入的对象实例即可. 微软官方文档-DI 依赖注入有什么好处 依赖

  • 理解ASP.NET Core 启动类(Startup)

    目录 准备工作:一份ASP.NET Core Web API应用程序 Startup类 Startup构造函数 ConfigureServices Configure 省略Startup类 IStartupFilter IHostingStartup HostingStartup 程序集 HostingStartup 特性 激活HostingStarup程序集 1.使用环境变量(推荐) 2.在程序中配置 多环境配置 环境配置方式 基于环境的 Startup 1.将IWebHostEnvironm

  • asp.net 的错误处理机制讲解

    程序健壮性最基本要求就是程序错误的处理与捕捉,在ASP.NET中,错误的处理有和其他编程语言一样的机制,可以使用Try-Catch-Finally等方式,这一点和ASP相比具有较大的进步.而且,使用这些错误处理方法,可以大大提高程序的可读性和程序调试速度,在这几个优势结合的情况下,我们更加应该注意这一点.  关于错误的处理,我们可以参考这篇文章: Try...Catch...Finally in ASP.NET Introduction Error handling in Classic ASP

  • ASP.NET Core 中间件的使用之全局异常处理机制

    目录 1.创建项目 2.创建全局异常过滤器 3.依赖注入全局异常处理机制 4.测试全局异常处理机制 前言: 我们经常听到"秒修复秒上线",觉得很厉害的样子. 其实不然,这只是一个调侃而已,出现问题的方式很多(逻辑漏洞.代码异常.操作方式不正确等). 我们今天来说代码异常问题怎么快速定位,减少不必要的时间浪费. 这就是今天的主题"添加全局异常处理机制"捕捉异常存储到数据库(mongodb.SqlServer.MySQL等). PS:输出txt的话不怎么友好,不是所有人

  • ASP.NET Core基础之请求处理管道

    了解ASP.NET处理管道 为了理解ASP.NET Core中的请求处理管道概念,让我们修改Startup类的Configure()方法,如下所示. 在这里,我们将三个中间件组件注册到请求处理管道中. 如您所见,前两个组件是使用Use() 扩展方法注册的,因此它们有机会在请求处理管道中调用下一个中间件组件. 最后一个使用Run() 扩展方法注册,因为它将成为我们的终止组件,即它将不会调用下一个组件. 了解ASP.NET Core请求处理管道执行顺序 为了理解这一点,让我们将上面的输出与下图进行比

  • ASP.Net Core MVC基础系列之项目创建

    一 : 系列教程环境介绍 1: 操作系统, Windows 10 专业版 64位 (版本号: 1809) 2: IDE使用Visual Studio 2017专业版 (版本号: 15.9.5) 3: SDK环境 .Net Core 2.2.101 4: 前端 Vue 2.X 5: 数据库 MSSQL 2017 6: 语言使用 C# 7.3 二 : .Net Core 2.2环境安装 进行.Net Core开发, 需要安装.Net Core SDK, 下载地址 下载安装完成之后在CMD里面运行do

  • ASP.NET Core异常和错误处理(8)

    在这一章,我们将讨论异常和错误处理.当 ASP.NET Core应用程序中发生错误时,您可以以各种不同的方式来处理.让我们来看看通过添加一个中间件来处理异常情况,这个中间件将帮助我们处理错误. 要模拟出错,让我们转到应用程序,运行,如果我们只是抛出异常的话,看看程序是如何运转转的. using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft

  • 在ASP.NET Core中显示自定义的错误页面

    前言 相信每位程序员们应该都知道在 ASP.NET Core 中,默认情况下当发生500或404错误时,只返回http状态码,不返回任何内容,页面一片空白. 如果在 Startup.cs 的 Configure() 中加上 app.UseStatusCodePages(); ,500错误时依然是一片空白(不知为何对500错误不起作用),404错误时有所改观,页面会显示下面的文字: Status Code: 404; Not Found 如果我们想实现不管500还是404错误都显示自己定制的友好错

随机推荐