前言

    全局异常处理是我们编程过程中不可或缺的重要环节。有了全局异常处理机制给我们带来了很多便捷,首先我们不用满屏幕处理程序可能出现的异常,其次我们可以对异常进行统一的处理,比如收集异常信息或者返回统一的格式等等。ASP.NET Core为我们提供了两种机制去处理全局异常,一是基于中间件的方式,二是基于Filter过滤器的方式。Filter过滤器的方式相对来说比较简单,就是捕获Action执行过程中出现的异常,然后调用注册的Filter去执行处理异常信息,在这里就不过多介绍这种方式了,接下来我们主要介绍中间件的方式。

异常处理中间件

    ASP.NET Core为我们提供了几种不同处理异常方式的中间件分别是UseDeveloperExceptionPage、UseExceptionHandler、UseStatusCodePages、UseStatusCodePagesWithRedirects、UseStatusCodePagesWithReExecute。这几种方式处理的思路是一致的都是通过捕获该管道后续的管道执行过程中出现的异常,只是处理的方式不一样。一般推荐全局异常处理相关中间件写到所有管道的最开始,这样可以捕获到整个执行管道过程中的异常信息。接下来我们介绍一下最常用的三个异常处理中间件UseDeveloperExceptionPage、UseExceptionHandler、UseStatusCodePage。

UseDeveloperExceptionPage

UseDeveloperExceptionPage的使用场景大部分是开发阶段,通过名称我们就可以看出,通过它捕获的异常会返回一个异常界面,它的使用方式很简单

//这个判断不是必须的,但是在正式环境中给用户展示代码错误信息,终究不是合理的
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

如果程序出现异常,出现的效果是这个样子的



这里包含了非常详细的异常堆栈信息、请求参数、Cookie信息、Header信息、和路由终结点相关的信息。接下来我们找到UseDeveloperExceptionPage所在的扩展类

public static class DeveloperExceptionPageExtensions
{
public static IApplicationBuilder UseDeveloperExceptionPage(this IApplicationBuilder app)
{
return app.UseMiddleware<DeveloperExceptionPageMiddleware>();
} public static IApplicationBuilder UseDeveloperExceptionPage(
this IApplicationBuilder app,
DeveloperExceptionPageOptions options)
{
return app.UseMiddleware<DeveloperExceptionPageMiddleware>(Options.Create(options));
}
}

我们看到有两个扩展方法一个是无参的,另一个是可以传递DeveloperExceptionPageOptions的扩展方法,因为平时使用无参的方法所以我们看下DeveloperExceptionPageOptions包含了哪些信息,找到DeveloperExceptionPageOptions源码

public class DeveloperExceptionPageOptions
{
public DeveloperExceptionPageOptions()
{
SourceCodeLineCount = 6;
}
/// <summary>
/// 展示出现异常代码的地方上下展示多少行的代码信息,默认是6行
/// </summary>
public int SourceCodeLineCount { get; set; } /// <summary>
/// 通过这个文件提供程序我们可以猜测到,我们可以自定义异常错误界面
/// </summary>
public IFileProvider FileProvider { get; set; }
}

接下来我们就看核心的DeveloperExceptionPageMiddleware中间件大致是如何工作的

public class DeveloperExceptionPageMiddleware
{
private readonly RequestDelegate _next;
private readonly DeveloperExceptionPageOptions _options;
private readonly ILogger _logger;
private readonly IFileProvider _fileProvider;
private readonly DiagnosticSource _diagnosticSource;
private readonly ExceptionDetailsProvider _exceptionDetailsProvider;
private readonly Func<ErrorContext, Task> _exceptionHandler;
private static readonly MediaTypeHeaderValue _textHtmlMediaType = new MediaTypeHeaderValue("text/html"); public DeveloperExceptionPageMiddleware(
RequestDelegate next,
IOptions<DeveloperExceptionPageOptions> options,
ILoggerFactory loggerFactory,
IWebHostEnvironment hostingEnvironment,
DiagnosticSource diagnosticSource,
IEnumerable<IDeveloperPageExceptionFilter> filters)
{
_next = next;
_options = options.Value;
_logger = loggerFactory.CreateLogger<DeveloperExceptionPageMiddleware>();
//默认使用ContentRootFileProvider
_fileProvider = _options.FileProvider ?? hostingEnvironment.ContentRootFileProvider;
//可以发送诊断日志
_diagnosticSource = diagnosticSource;
_exceptionDetailsProvider = new ExceptionDetailsProvider(_fileProvider, _options.SourceCodeLineCount);
_exceptionHandler = DisplayException;
//构建IDeveloperPageExceptionFilter执行管道,说明我们同时还可以通过程序的方式捕获异常信息
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)
{
_logger.UnhandledException(ex);
if (context.Response.HasStarted)
{
_logger.ResponseStartedErrorPageMiddleware();
throw;
}
try
{
//清除输出相关信息,将状态码设为500
context.Response.Clear();
context.Response.StatusCode = 500;
//核心处理
await _exceptionHandler(new ErrorContext(context, ex));
//发送名称为Microsoft.AspNetCore.Diagnostics.UnhandledException诊断日志,我们可以自定义订阅者处理异常
if (_diagnosticSource.IsEnabled("Microsoft.AspNetCore.Diagnostics.UnhandledException"))
{
_diagnosticSource.Write("Microsoft.AspNetCore.Diagnostics.UnhandledException", new { httpContext = context, exception = ex });
}
return;
}
catch (Exception ex2)
{
_logger.DisplayErrorPageException(ex2);
}
throw;
}
}
}

通过上面代码我们可以了解到我们可以通过自定义IDeveloperPageExceptionFilter的方式拦截到异常信息做处理

public class MyDeveloperPageExceptionFilter : IDeveloperPageExceptionFilter
{
private readonly ILogger<MyDeveloperPageExceptionFilter> _logger;
public MyDeveloperPageExceptionFilter(ILogger<MyDeveloperPageExceptionFilter> logger)
{
_logger = logger;
} public async Task HandleExceptionAsync(ErrorContext errorContext, Func<ErrorContext, Task> next)
{
_logger.LogInformation($"状态码:{errorContext.HttpContext.Response.StatusCode},异常信息:{errorContext.Exception.Message}");
await next(errorContext);
}
}

自定义的MyDeveloperPageExceptionFilter是需要注入的

services.AddSingleton<IDeveloperPageExceptionFilter,MyDeveloperPageExceptionFilter>();

同时还可以通过诊断日志的方式处理异常信息,我使用了Microsoft.Extensions.DiagnosticAdapter扩展包,所以可以定义强类型类

public class DiagnosticCollector
{
private readonly ILogger<DiagnosticCollector> _logger;
public DiagnosticCollector(ILogger<DiagnosticCollector> logger)
{
_logger = logger;
} [DiagnosticName("Microsoft.AspNetCore.Diagnostics.UnhandledException")]
public void OnRequestStart(HttpContext httpContext, Exception exception)
{
_logger.LogInformation($"诊断日志收集到异常,状态码:{httpContext.Response.StatusCode},异常信息:{exception.Message}");
}
}

通过这里可以看出,异常处理扩展性还是非常强的,这仅仅是.Net Core设计方式的冰山一角。刚才我们提到_exceptionHandler才是处理的核心,通过构造函数可知这个委托是通过DisplayException方法初始化的,接下来我们看这里的相关实现

private Task DisplayException(ErrorContext errorContext)
{
var httpContext = errorContext.HttpContext;
var headers = httpContext.Request.GetTypedHeaders();
var acceptHeader = headers.Accept;
//如果acceptHeader不存在或者类型不是text/plain,将以字符串的形式输出异常,比如通过代码或者Postman的方式调用并未设置头信息
if (acceptHeader == null || !acceptHeader.Any(h => h.IsSubsetOf(_textHtmlMediaType)))
{
httpContext.Response.ContentType = "text/plain";
var sb = new StringBuilder();
sb.AppendLine(errorContext.Exception.ToString());
sb.AppendLine();
sb.AppendLine("HEADERS");
sb.AppendLine("=======");
foreach (var pair in httpContext.Request.Headers)
{
sb.AppendLine($"{pair.Key}: {pair.Value}");
}
return httpContext.Response.WriteAsync(sb.ToString());
}
//判断是否为编译时异常,比如视图文件可以动态编译
if (errorContext.Exception is ICompilationException compilationException)
{
return DisplayCompilationException(httpContext, compilationException);
}
//处理运行时异常
return DisplayRuntimeException(httpContext, errorContext.Exception);
}

关于DisplayCompilationException我们这里就不做过多解释了,在Asp.Net Core中cshtml文件可以动态编译,有兴趣的同学可以自行了解。我们重点看下DisplayRuntimeException处理

private Task DisplayRuntimeException(HttpContext context, Exception ex)
{
//获取终结点信息
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
EndpointModel endpointModel = null;
if (endpoint != null)
{
endpointModel = new EndpointModel();
endpointModel.DisplayName = endpoint.DisplayName;
if (endpoint is RouteEndpoint routeEndpoint)
{
endpointModel.RoutePattern = routeEndpoint.RoutePattern.RawText;
endpointModel.Order = routeEndpoint.Order;
var httpMethods = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>()?.HttpMethods;
if (httpMethods != null)
{
endpointModel.HttpMethods = string.Join(", ", httpMethods);
}
}
}
var request = context.Request;
//往视图还是个输出的模型,对于我们上面截图展示的信息对应的数据
var model = new ErrorPageModel
{
Options = _options,
//异常详情
ErrorDetails = _exceptionDetailsProvider.GetDetails(ex),
//查询参数相关
Query = request.Query,
//Cookie信息
Cookies = request.Cookies,
//头信息
Headers = request.Headers,
//路由信息
RouteValues = request.RouteValues,
//终结点信息
Endpoint = endpointModel
};
var errorPage = new ErrorPage(model);
//执行输出视图页面,也就是我们看到的开发者页面
return errorPage.ExecuteAsync(context);
}

其整体实现思路就是捕获后续执行过程中出现的异常,如果出现异常则包装异常信息以及Http上下文和路由相关信息到ErrorPageModel模型中,然后这个模型作为异常展示界面的数据模型进行展示。

UseExceptionHandler

UseExceptionHandler可能是我们在实际开发过程中使用最多的方式。UseDeveloperExceptionPage固然强大,但是返回的终究还是供开发者使用的界面,通过UseExceptionHandler我们可以通过自己的方式处理异常信息,这里就需要我自己编码

app.UseExceptionHandler(configure =>
{
configure.Run(async context =>
{
var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
var ex = exceptionHandlerPathFeature?.Error;
if (ex != null)
{
context.Response.ContentType = "text/plain;charset=utf-8";
await context.Response.WriteAsync(ex.ToString());
}
});
});
//或
app.UseExceptionHandler(new ExceptionHandlerOptions
{
ExceptionHandler = async context =>
{
var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
var ex = exceptionHandlerPathFeature?.Error;
if (ex != null)
{
context.Response.ContentType = "text/plain;charset=utf-8";
await context.Response.WriteAsync(ex.ToString());
}
}
});
//或
app.UseExceptionHandler(new ExceptionHandlerOptions
{
ExceptionHandlingPath = new PathString("/exception")
});

通过上面的实现方式我们大概可以猜出扩展方法的几种类型找到源码位置ExceptionHandlerExtensions扩展类

public static class ExceptionHandlerExtensions
{
public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app)
{
return app.UseMiddleware<ExceptionHandlerMiddleware>();
} public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, string errorHandlingPath)
{
return app.UseExceptionHandler(new ExceptionHandlerOptions
{
ExceptionHandlingPath = new PathString(errorHandlingPath)
});
} public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, Action<IApplicationBuilder> configure)
{
//创建新的执行管道
var subAppBuilder = app.New();
configure(subAppBuilder);
var exceptionHandlerPipeline = subAppBuilder.Build(); return app.UseExceptionHandler(new ExceptionHandlerOptions
{
ExceptionHandler = exceptionHandlerPipeline
});
} public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, ExceptionHandlerOptions options)
{
return app.UseMiddleware<ExceptionHandlerMiddleware>(Options.Create(options));
}
}

通过UseExceptionHandler扩展方法我们可以知道这么多扩展方法其实本质都是在构建ExceptionHandlerOptions我们来看一下大致实现

public class ExceptionHandlerOptions
{
/// <summary>
/// 指定处理异常的终结点路径
/// </summary>
public PathString ExceptionHandlingPath { get; set; } /// <summary>
/// 指定处理异常的终结点委托
/// </summary>
public RequestDelegate ExceptionHandler { get; set; }
}

这个类非常简单,要么指定处理异常信息的具体终结点路径,要么自定义终结点委托处理异常信息。通过上面的使用示例可以很清楚的看到这一点,接下来我们查看一下ExceptionHandlerMiddleware中间件的大致实现

public class ExceptionHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ExceptionHandlerOptions _options;
private readonly ILogger _logger;
private readonly Func<object, Task> _clearCacheHeadersDelegate;
private readonly DiagnosticListener _diagnosticListener; public ExceptionHandlerMiddleware(
RequestDelegate next,
ILoggerFactory loggerFactory,
IOptions<ExceptionHandlerOptions> options,
DiagnosticListener diagnosticListener)
{
_next = next;
_options = options.Value;
_logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleware>();
_clearCacheHeadersDelegate = ClearCacheHeaders;
_diagnosticListener = diagnosticListener;
//ExceptionHandler和ExceptionHandlingPath不同同时不存在
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);
//task未完成情况
if (!task.IsCompletedSuccessfully)
{
return Awaited(this, context, task);
}
return Task.CompletedTask;
}
catch (Exception exception)
{
edi = ExceptionDispatchInfo.Capture(exception);
}
return HandleException(context, edi); //处理未完成task
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);
}
}
}
}

通过这段处理我们可以看出所有的异常处理都指向当前类的HandleException方法

private async Task HandleException(HttpContext context, ExceptionDispatchInfo edi)
{
_logger.UnhandledException(edi.SourceException);
// 如果输出已经开始执行了,后续的代码就没必要执行了,直接重新抛出
if (context.Response.HasStarted)
{
_logger.ResponseStartedErrorHandler();
edi.Throw();
} PathString originalPath = context.Request.Path;
//如果指定处理异常的终结点,将异常处理交给指定的终结点去处理
if (_options.ExceptionHandlingPath.HasValue)
{
//将处理路径指向,异常处理终结点路径
context.Request.Path = _options.ExceptionHandlingPath;
}
try
{
//清除原有HTTP上下文信息,为了明确指定程序出现异常,防止异常未被处理而后续当做正常操作执行
ClearHttpContext(context);
//将异常信息包装成ExceptionHandlerFeature,后续处理程序获取异常信息都是通过ExceptionHandlerFeature
var exceptionHandlerFeature = new ExceptionHandlerFeature()
{
//异常信息
Error = edi.SourceException,
//原始路径
Path = originalPath.Value,
};
//将包装的ExceptionHandlerFeature放入到上下文中,后续处理程序可通过HttpContext获取异常信息
context.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature);
context.Features.Set<IExceptionHandlerPathFeature>(exceptionHandlerFeature);
//设置状态码
context.Response.StatusCode = 500;
context.Response.OnStarting(_clearCacheHeadersDelegate, context.Response);
//调用给定的异常处理终结点处理异常信息
await _options.ExceptionHandler(context);
//同样也可以发送诊断日志,可以利用处理程序返回输出,诊断日志记录异常将职责分离
if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled("Microsoft.AspNetCore.Diagnostics.HandledException"))
{
_diagnosticListener.Write("Microsoft.AspNetCore.Diagnostics.HandledException", new { httpContext = context, exception = edi.SourceException });
}
return;
}
catch (Exception ex2)
{
_logger.ErrorHandlerException(ex2);
}
finally
{
//异常处理结束后,恢复原始的请求路径,供后续执行程序能拿到原始的请求信息
context.Request.Path = originalPath;
}
//如果异常没被处理则重新抛出
edi.Throw();
}

最后还有一段清除上下文和清除输出缓存的方法,因为程序一旦发生了异常,可能创建了新的终结点,所以执行管道会有所调整,所以需要重新计算​。​而且异常信息保留输出缓存是没有意义的。​

private static void ClearHttpContext(HttpContext context)
{
context.Response.Clear();
//因为可能创建了新的终结点,所以执行管道会有所调整,所以需要重新计算
context.SetEndpoint(endpoint: null);
var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
routeValuesFeature?.RouteValues?.Clear();
} private static Task ClearCacheHeaders(object state)
{
//清除输出缓存相关
var headers = ((HttpResponse)state).Headers;
headers[HeaderNames.CacheControl] = "no-cache";
headers[HeaderNames.Pragma] = "no-cache";
headers[HeaderNames.Expires] = "-1";
headers.Remove(HeaderNames.ETag);
return Task.CompletedTask;
}

从上面的代码我们可以看出UseExceptionHandler要比UseDeveloperExceptionPage实现方式简单很多。其大致思路就是捕获后续管道执行异常,如果存在异常则将异常包装成ExceptionHandlerFeature,放入到Http上下文中。之所以相对简单主要原因还是UseExceptionHandler最终处理异常由我们自定义的终结点去处理,所以它只是负责包装异常相关信息,并将它交于我们定义的异常处理终结点。

UseStatusCodePages

无论是UseDeveloperExceptionPage还是UseExceptionHandler都是通过捕获异常的方式去处理异常信息,UseStatusCodePages则是通过Http状态码去判断是否为成功的返回并进行处理,使用方式如下

app.UseStatusCodePages();
//或
app.UseStatusCodePages("text/plain;charset=utf-8", "状态码:{0}");
//或
app.UseStatusCodePages(async context =>
{
context.HttpContext.Response.ContentType = "text/plain;charset=utf-8";
await context.HttpContext.Response.WriteAsync($"状态码:{context.HttpContext.Response.StatusCode}");
});
//或
app.UseStatusCodePages(new StatusCodePagesOptions { HandleAsync = async context=> {
context.HttpContext.Response.ContentType = "text/plain;charset=utf-8";
await context.HttpContext.Response.WriteAsync($"状态码:{context.HttpContext.Response.StatusCode}");
}});
//或
app.UseStatusCodePages(configure =>
{
configure.Run(async context =>
{
await context.Response.WriteAsync($"状态码:{context.Response.StatusCode}");
});
});

接下来我们查看一下UseStatusCodePages扩展方法相关实现

public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, StatusCodePagesOptions options)
{
return app.UseMiddleware<StatusCodePagesMiddleware>(Options.Create(options));
} public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app)
{
return app.UseMiddleware<StatusCodePagesMiddleware>();
} public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, Func<StatusCodeContext, Task> handler)
{
return app.UseStatusCodePages(new StatusCodePagesOptions
{
HandleAsync = handler
});
} public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, string contentType, string bodyFormat)
{
return app.UseStatusCodePages(context =>
{
var body = string.Format(CultureInfo.InvariantCulture, bodyFormat, context.HttpContext.Response.StatusCode);
context.HttpContext.Response.ContentType = contentType;
return context.HttpContext.Response.WriteAsync(body);
});
}

虽然扩展方法比较多,但是本质都是组装StatusCodePagesOptions,所以我们直接查看源码

public class StatusCodePagesOptions
{
public StatusCodePagesOptions()
{
//初始化
HandleAsync = context =>
{
var statusCode = context.HttpContext.Response.StatusCode;
var body = BuildResponseBody(statusCode);
context.HttpContext.Response.ContentType = "text/plain";
return context.HttpContext.Response.WriteAsync(body);
};
} private string BuildResponseBody(int httpStatusCode)
{
//组装默认消息模板
var internetExplorerWorkaround = new string(' ', 500);
var reasonPhrase = ReasonPhrases.GetReasonPhrase(httpStatusCode);
return string.Format(CultureInfo.InvariantCulture, "Status Code: {0}{1}{2}{3}",
httpStatusCode,
string.IsNullOrWhiteSpace(reasonPhrase) ? "" : "; ",
reasonPhrase,
internetExplorerWorkaround);
}
public Func<StatusCodeContext, Task> HandleAsync { get; set; }
}

看着代码不少,其实都是吓唬人的,就是给HandleAsync一个默认值,这个默认值里有默认的输出模板。接下来我们查看一下StatusCodePagesMiddleware中间件源码

public class StatusCodePagesMiddleware
{
private readonly RequestDelegate _next;
private readonly StatusCodePagesOptions _options; public StatusCodePagesMiddleware(RequestDelegate next, IOptions<StatusCodePagesOptions> options)
{
_next = next;
_options = options.Value;
} public async Task Invoke(HttpContext context)
{
//初始化StatusCodePagesFeature
var statusCodeFeature = new StatusCodePagesFeature();
context.Features.Set<IStatusCodePagesFeature>(statusCodeFeature); await _next(context); if (!statusCodeFeature.Enabled)
{
return;
}
//这个范围外的Http状态码直接忽略,不受程序处理只处理值为400-600之间的状态码
if (context.Response.HasStarted
|| context.Response.StatusCode < 400
|| context.Response.StatusCode >= 600
|| context.Response.ContentLength.HasValue
|| !string.IsNullOrEmpty(context.Response.ContentType))
{
return;
}
//将状态信息包装到StatusCodeContext,传递给自定义处理终结点
var statusCodeContext = new StatusCodeContext(context, _options, _next);
await _options.HandleAsync(statusCodeContext);
}
}

这个中间件的实现思路更为简单,主要就是拦截请求判断Http状态码,判断是否是400-600,也就是4xx 5xx相关的状态码,如果符合则包装成StatusCodeContext,交由自定义的终结点去处理。

总结

关于常用异常处理中间件我们介绍到这里就差不多了,接下来我们总结对比一下三种中间件的异同和大致实现的方式

  • UseDeveloperExceptionPage中间件主要工作方式就是捕获后续中间件执行异常,如果存在异常则将异常信息包装成ErrorPageModel视图模型,然后通过这个模型去渲染开发者异常界面。
  • UseExceptionHandler中间件核心思路和UseDeveloperExceptionPage类似都是通过捕获后续中间件执行异常,不同之处在于UseExceptionHandler将捕获的异常信息包装到ExceptionHandlerFeature然后将其放入Http上下文中,后续的异常处理终结点通过Http上下文获取到异常信息进行处理。
  • UseStatusCodePages中间件相对于前两种中间件最为简单,其核心思路就是获取执行完成后的Http状态码判断是否是4xx 5xx相关,如果是则执行自定义的状态码拦截终结点。这个中间件核心是围绕StatusCode其实并不包含处理异常相关的逻辑,所以整体实现相对简单。

最后我们再来总结下使用中间件的方式和使用IExceptionFilter的方式的区别

  • 中间件的方式是对整个请求执行管道进行异常捕获,主要是负责整个请求过程中的异常捕获,其生命周期更靠前,捕获异常的范围更广泛。毕竟MVC只是Asp.Net Core终结点的一种实现方式,目前Asp.Net Core还可以处理GRPC Signalr等其它类型的终结点信息。
  • IExceptionFilter主要是针对Action执行过程中的异常,毕竟终结点只是中间件的一种形式,所以可处理范围比较有限,主要适用于MVC程序。对于其它终结点类型有点无能为力。

以上就是文章的全部内容,由于能力有限,如果存在理解不周之处请多多谅解。我觉得学习一个东西,如果你能了解到它的工作方式或者实现原理,肯定会对你的编程思路有所提升,看过的代码用过的东西可能会忘,但是思路一旦形成,将会改变你以后的思维方式。

欢迎扫码关注我的公众号

深入探究ASP.NET Core异常处理中间件的更多相关文章

  1. ASP.NET Core 异常处理与日志记录

    1. ASP.NET Core 异常处理与日志记录 1.1. 异常处理 1.1.1. 异常产生的原因及处理 1.1.2. ASP.NET Core中启动开发人员异常页面 1.2. 日志记录 1.2.1 ...

  2. ASP.NET Core:中间件

    一.什么是中间件 我们都知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请求都是经过管道的一系列操作,最终才会到达我们写的代码中.而中间件就是用于组成应用程序管道来处理请求和响应的 ...

  3. 如何传递参数给ASP.NET Core的中间件(Middleware)

    问题描述 当我们在ASP.NET Core中定义和使用中间件(Middleware)的时候,有什么好的办法可以给中间件传参数吗? 解决方案 在ASP.NET Core项目中添加一个POCO类来传递参数 ...

  4. asp.net core mvc 中间件之WebpackDevMiddleware

    asp.net core mvc 中间件之WebpackDevMiddleware WebpackDevMiddleware中间件主要用于开发SPA应用,启用Webpack,增强网页开发体验.好吧,你 ...

  5. asp.net core mvc 中间件之路由

    asp.net core mvc 中间件之路由 路由中间件 首先看路由中间件的源码 先用httpContext实例化一个路由上下文,然后把中间件接收到的路由添加到路由上下文的路由集合 然后把路由上下文 ...

  6. 如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容?

    原文:如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容? 文章名称: 如何在ASP.NET Core自定义中间件读取Request.Body和 ...

  7. asp.net core 使用中间件拦截请求和返回数据,并对数据进行加密解密。

    原文:asp.net core 使用中间件拦截请求和返回数据,并对数据进行加密解密. GitHub demo https://github.com/zhanglilong23/Asp.NetCore. ...

  8. ASP.NET Core路由中间件[3]: 终结点(Endpoint)

    到目前为止,ASP.NET Core提供了两种不同的路由解决方案.传统的路由系统以IRouter对象为核心,我们姑且将其称为IRouter路由.本章介绍的是最早发布于ASP.NET Core 2.2中 ...

  9. ASP.NET Core路由中间件[2]: 路由模式

    一个Web应用本质上体现为一组终结点的集合.终结点则体现为一个暴露在网络中可供外界采用HTTP协议调用的服务,路由的作用就是建立一个请求URL模式与对应终结点之间的映射关系.借助这个映射关系,客户端可 ...

随机推荐

  1. Java实现 LeetCode 565 数组嵌套(没有重复值的数组)

    565. 数组嵌套 索引从0开始长度为N的数组A,包含0到N - 1的所有整数.找到并返回最大的集合S,S[i] = {A[i], A[A[i]], A[A[A[i]]], - }且遵守以下的规则. ...

  2. Java实现 LeetCode 167 两数之和 II - 输入有序数组

    167. 两数之和 II - 输入有序数组 给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数. 函数应该返回这两个下标值 index1 和 index2,其中 index1 必 ...

  3. Java实现 洛谷 P1103 书本整理

    题目描述 Frank是一个非常喜爱整洁的人.他有一大堆书和一个书架,想要把书放在书架上.书架可以放下所有的书,所以Frank首先将书按高度顺序排列在书架上.但是Frank发现,由于很多书的宽度不同,所 ...

  4. lambda表达式操作DataTable

    using System;using System.Collections.Generic;using System.Data;using System.Linq;using System.Text; ...

  5. 运维、python、docker视频教程

    https://space.bilibili.com/97139894/?spm_id_from=333.788.b_7265636f5f6c697374.13 https://www.bilibil ...

  6. 汇编指令mov、add、sub、jmp

    mov:寄存器,数据 mov:寄存器,寄存器 mov:寄存器,内存单元 mov:段寄存器,内存单元 mov:内存单元,寄存器 mov:内存单元,段寄存器 mov:段寄存器,寄存器 mov:寄存器,段寄 ...

  7. 【雕爷学编程】MicroPython动手做(08)——零基础学MaixPy之识别颜色

    早上用百度搜了一下“颜色识别”,多少有了一点大致的概念,还是老办法,动手做,多实验,往前走,还请各位老师多多指点. OpenCV(百度百科)是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运 ...

  8. 下拉式菜单中的内容堆叠(ul型)

    今天使用ul创建下拉式菜单,菜单中的内容堆在了一起. 这是我的html代码 <!DOCTYPE html> <html lang="en"> <hea ...

  9. 在WinForms里嵌入MediaPlayer的一些版本问题, tlbimp导入, 以及不导入而纯用C#+字符串来动态调用.

    网上很多写使用WindowsMediaPlayer WMP控件的文章. 大多数都是从工具栏或COM导入. 最近正在做的CEF整合Asp.Net Core Blazor server side的过程中, ...

  10. ThreadLocal源码解析-Java8

    目录 一.ThreadLocal介绍 1.1 ThreadLocal的功能 1.2 ThreadLocal使用示例 二.源码分析-ThreadLocal 2.1 ThreadLocal的类层级关系 2 ...