前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ASP.NET Core 错误处理(Handle Errors)

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

作者头像
郑子铭
发布于 2021-12-01 08:50:05
发布于 2021-12-01 08:50:05
2.2K00
代码可运行
举报
运行总次数:0
代码可运行

链接:cnblogs.com/xiaoxiaotank/p/15586706.html

系列文章

《理解 ASP.NET Core - 配置(Configuration)》

《理解 ASP.NET Core - 依赖注入》

《理解 ASP.NET Core - 文件服务器(File Server)》

使用中间件进行错误处理

开发人员异常页

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        // 添加开发人员异常页中间件
        app.UseDeveloperExceptionPage();
    }
}

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
app.Use((context, next) =>
{
    throw new NotImplementedException();
});

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

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

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

IDeveloperPageExceptionFilter

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

下面是DeveloperExceptionPageMiddleware的核心源码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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接口定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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中的路径

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        // 添加异常处理程序
        app.UseExceptionHandler("/Home/Error");
    }
}

通过lambda提供异常处理程序

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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(前者继承自后者),来获取到异常信息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface IExceptionHandlerFeature
{
    // 异常信息
    Exception Error { get; }
}

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

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

异常处理程序页

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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请求分别进行处理,你可以类似如下进行处理:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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吧:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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中间件实现该功能:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,你会在浏览器中看到如下输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Status Code: 404; Not Found 

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 使用占位符 {0} 来填充Http状态码
app.UseStatusCodePages("text/plain", "Status code is: {0}");

浏览器输出为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Status code is: 404

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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-已找到
  • 然后将客户端重定向到指定的终结点,在该终结点中,可以针对不同错误状态码分别进行处理
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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其实也都明白了):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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状态码
  • 执行备用管道,用于生成响应正文
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 注意,这里要分开写
app.UseStatusCodePagesWithReExecute("/Home/StatusCodeError", "?code={0}");

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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中抛出的未处理异常
  • 其他地方抛出的异常不会捕获

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

先来看一下这两个接口:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 仅具有标记作用,标记其为 mvc 请求管道的过滤器
public interface IFilterMetadata { }

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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相关的信息,看看都有什么吧:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 {}
}

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,展示一下错误消息:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@model ErrorViewModel
@{
    ViewData["Title"] = "Error";
}

<p>@Model.Message</p>

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<MyExceptionFilterAttribute>();

    services.AddControllersWithViews();
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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中的异常。

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DotNet NB 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
ASP.NET Core应用的错误处理[4]:StatusCodePagesMiddleware中间件如何针对响应码呈现错误页面
StatusCodePagesMiddleware中间件与ExceptionHandlerMiddleware中间件比较类似,它们都是在后续请求处理过程中“出错”的情况下利用一个错误处理器来完成最终的请求处理与响应的任务。它们之间的差异在于对“错误”的界定上,对于ExceptionHandlerMiddleware中间件来说,它所谓的错误就是抛出异常,但是对于StatusCodePagesMiddleware中间件来说,则将介于400~599之间的响应状态码视为错误。如下面的代码片段所示,StatusCod
蒋金楠
2018/02/07
3K0
ASP.NET Core应用的错误处理[4]:StatusCodePagesMiddleware中间件如何针对响应码呈现错误页面
ASP.NET Core错误处理中间件[1]: 呈现错误信息
NuGet包“Microsoft.AspNetCore.Diagnostics”中提供了几个与异常处理相关的中间件。当ASP.NET Core应用在处理请求过程中出现错误时,我们可以利用它们将原生的或者定制的错误信息作为响应内容发送给客户端。在着重介绍这些中间件之前,下面先演示几个简单的实例,从而使读者大致了解这些中间件的作用。[更多关于ASP.NET Core的文章请点这里]
蒋金楠
2021/01/18
1.8K0
ASP.NET Core 6框架揭秘实例演示[33]:异常处理高阶用法
NuGet包“Microsoft.AspNetCore.Diagnostics”中提供了几个与异常处理相关的中间件,我们可以利用它们将原生的或者定制的错误信息作为响应内容发送给客户端。《错误页面的N种呈现方式》演示了几个简单的实例使读者大致了解这些中间件的作用,现在我们来演示几个高阶用法。本文提供的示例演示已经同步到《ASP.NET Core 6框架揭秘-实例演示版》)
蒋金楠
2022/09/23
1.3K0
ASP.NET Core 6框架揭秘实例演示[33]:异常处理高阶用法
.NET Core开发实战(第22课:异常处理中间件:区分真异常与逻辑异常)--学习笔记(下)
接下来介绍使用代理方法的方式,也就是说把 ErrorController 整段逻辑直接定义在注册的地方,使用一个匿名委托来处理,这里的逻辑与之前的逻辑是相同的
郑子铭
2021/01/13
5110
ASP.NET Core错误处理中间件[2]: 开发者异常页面
《呈现错误信息》通过几个简单的实例演示了如何呈现一个错误页面,该过程由3个对应的中间件来完成。下面先介绍用来呈现开发者异常页面的DeveloperExceptionPageMiddleware中间件,该中间件在捕捉到后续处理过程中抛出的异常之后会返回一个媒体类型为text/html的响应,后者在浏览器上会呈现一个错误页面。由于这是一个为开发者提供诊断信息的异常页面,所以可以将其称为开发者异常页面(Developer Exception Page)。该页面不仅会呈现异常的详细信息(类型、消息和跟踪堆栈等),还会出现与当前请求相关的上下文信息。如下所示的代码片段是DeveloperExceptionPageMiddleware中间件的定义。更多关于ASP.NET Core的文章请点这里]
蒋金楠
2021/01/20
1.2K0
ASP.NET Core 6框架揭秘实例演示[32]:错误页面的N种呈现方式
由于ASP.NET是一个同时处理多个请求的Web应用框架,所以在处理某个请求过程中出现异常并不会导致整个应用的中止。出于安全方面的考量,为了避免敏感信息外泄,客户端在默认情况下并不会得到详细的出错信息,这无疑会在开发过程中增加查错和纠错的难度。对于生产环境来说,我们也希望最终用户能够根据具体的错误类型得到具有针对性并且友好的错误消息。ASP.NET提供的相应的中间件可以帮助我们将定制化的错误信息呈现出来。本文提供的示例演示已经同步到《ASP.NET Core 6框架揭秘-实例演示版》)
蒋金楠
2022/09/21
8230
ASP.NET Core 6框架揭秘实例演示[32]:错误页面的N种呈现方式
ASP.NET Core错误处理中间件[3]: 异常处理器
DeveloperExceptionPageMiddleware中间件错误页面可以呈现抛出的异常和当前请求上下文的详细信息,以辅助开发人员更好地进行纠错诊断工作。ExceptionHandlerMiddleware中间件则主要面向最终用户,我们可以利用它来显示一个友好的定制化错误页面。更多关于ASP.NET Core的文章请点这里]
蒋金楠
2021/01/21
9970
ASP.NET Core应用的错误处理[2]:DeveloperExceptionPageMiddleware中间件如何呈现“开发者异常页面”
在《ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式》中,我们通过几个简单的实例演示了如何呈现一个错误页面,这些错误页面的呈现分别由三个对应的中间件来完成,接下来我们将对这三个中间件进行详细介绍。在开发环境呈现的异常页面是通过一个类型为DeveloperExceptionPageMiddleware中间件实现的。[本文已经同步到《ASP.NET Core框架揭秘》之中] 1: public class DeveloperExceptionPageMiddleware 2:
蒋金楠
2018/02/07
1.5K0
ASP.NET Core应用的错误处理[2]:DeveloperExceptionPageMiddleware中间件如何呈现“开发者异常页面”
ASP.NET Core 框架本质学习
https://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html
Vincent-yuan
2019/09/04
2.1K0
ASP.NET Core 框架本质学习
ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式
由于ASP.NET Core应用是一个同时处理多个请求的服务器应用,所以在处理某个请求过程中抛出的异常并不会导致整个应用的终止。出于安全方面的考量,为了避免敏感信息的外泄,客户端在默认的情况下并不会得到详细的出错信息,这无疑会在开发环境下增加查错纠错的难度。对于生产环境来说,我们也希望最终用户能够根据具体的错误类型得到具有针对性并且友好的错误消息。ASP.NET Core提供了相应的中间件帮助我们将定制化的错误信息呈现出来,这些中间件都定义在“Microsoft.AspNetCore.Diagnostics
蒋金楠
2018/02/07
1.9K0
ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式
C# .NET面试系列七:ASP.NET Core
在.NET中,在ASP.NET Core应用程序中的Controller中注入服务通常使用<u>依赖注入(Dependency Injection)</u>来实现。以下是一些步骤,说明如何在Controller中注入服务:
GoodTime
2024/03/08
5310
C# .NET面试系列七:ASP.NET Core
福爆 | 博客升级 .NET Core 3.0 又踩一坑
昨天刚发了一篇《与时俱进 | 博客现已运行在 .NET Core 3.0 及 Azure 上》得瑟,本以为踩完了坑,结果晚上博客又爆了。Azure Application Insights 监控里发现了大量异常。我们来看看我如何收取福爆。
Edi Wang
2019/09/29
1.2K0
福爆 | 博客升级 .NET Core 3.0 又踩一坑
asp.net core 2.0 查缺补漏
asp.net core 2.0 一些有用有趣的设置. 面向(targeting)不同的.net版本: 打开asp.net core 2.0的项目文件: xxx.csproj, 这部分: <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp2.0</TargetFramework> </PropertyGroup> TargetFramework就是指向的版本. 也可以指向多个.n
solenovex
2018/03/01
6960
asp.net core 2.0 查缺补漏
AspNetCore全局异常处理
在开发ASP.NET Core应用程序时,全局异常处理是一个重要的概念。它允许我们集中处理应用程序中未捕获的异常,确保应用程序的稳定性和用户体验。
Net分享
2024/12/11
1400
AspNetCore全局异常处理
ASP.NET Core应用的错误处理[3]:ExceptionHandlerMiddleware中间件如何呈现“定制化错误页面”
DeveloperExceptionPageMiddleware中间件利用呈现出来的错误页面实现抛出异常和当前请求的详细信息以辅助开发人员更好地进行纠错诊断工作,而ExceptionHandlerMiddleware中间件则是面向最终用户的,我们可以利用它来显示一个友好的定制化的错误页面。按照惯例,我们还是先来看看ExceptionHandlerMiddleware的类型定义。 [本文已经同步到《ASP.NET Core框架揭秘》之中] 1: public class ExceptionHandler
蒋金楠
2018/02/07
1.5K0
ASP.NET Core应用的错误处理[3]:ExceptionHandlerMiddleware中间件如何呈现“定制化错误页面”
ASP.NET Core ActionFilter引发的一个EF异常
最近在使用ASP.NET Core的时候出现了一个奇怪的问题。在一个Controller上使用了一个ActionFilter之后经常出现EF报错。
MJ.Zhou
2020/03/19
7610
ASP.NET Core 中间件(Middleware)详解
什么是中间件(Middleware)? 中间件是组装到应用程序管道中以处理请求和响应的软件。 每个组件: 选择是否将请求传递给管道中的下一个组件。 可以在调用管道中的下一个组件之前和之后执行工作。 请求委托(Request delegates)用于构建请求管道,处理每个HTTP请求。 请求委托使用Run,Map和Use扩展方法进行配置。单独的请求委托可以以内联匿名方法(称为内联中间件)指定,或者可以在可重用的类中定义它。这些可重用的类和内联匿名方法是中间件或中间件组件。请求流程中的每个中间件组件都负责调用
晓晨
2018/06/22
1.5K0
【ASP.NET Core 基础知识】--部署和维护--日志记录和错误处理
日志记录是一种记录系统运行状态、活动和事件的重要机制。在软件开发和系统管理中,日志记录扮演着关键角色,用于追踪应用程序的执行过程、监视系统的健康状况、诊断问题和安全审计等。在ASP.NET Core等现代Web开发框架中,日志记录是构建可靠、高性能应用程序的基础之一。 日志记录不仅仅是简单地将一些文本写入文件。它更多地涉及到收集、存储和分析各种类型的信息,这些信息可以包括但不限于:
喵叔
2024/05/24
2860
ASP.NET Core中使用Graylog记录日志
通常POST请求数据都在请求体中,ASP.NET Core中HttpRequest类型的Body属性是HttpRequestStream类型,该类型源码在Github上可以看到,但在Google和微软关方文档中都没搜索到。反编译Microsoft.AspNetCore.Server.Kestrel.Core.dll只找到了同样继承自ReadOnlyStream的FrameRequestStream
雪飞鸿
2019/02/25
1K0
asp.net core之异常处理
在开发过程中,处理错误是一个重要的方面。ASP.NET Core提供了多种方式来处理错误,以确保应用程序的稳定性和可靠性。
饭勺oO
2023/10/18
3880
asp.net core之异常处理
推荐阅读
相关推荐
ASP.NET Core应用的错误处理[4]:StatusCodePagesMiddleware中间件如何针对响应码呈现错误页面
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验