.NET Core:处理全局异常
一、前言
在程序设计中,我们会遇到各种各样的异常问题,一个好的异常处理解决方案能够帮助开发者快速的定位问题,也能够给用户更好的用户体验。那么我们在AspNetCore中该如何捕获和处理异常呢?我们以一个WebApi项目为例,讲解如何捕获和处理异常。
二、异常处理
1、异常处理
开发过ASP.NET程序的人都知道:IExceptionFilter。这个过滤器同样在AspNetCore中也可以用来捕获异常。不过,对于使用IExceptionFilter,更建议使用它的异步版本:IAsyncExceptionFilter。那么该如何使用过滤器呢?下面以IAsyncExceptionFilter为例,对于同步版本其实也是一样的。
我们在项目中添加一个Model文件夹,存放返回结果实体类,这里定义一个泛型类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace ExceptionDemo.Model
{
public class ResultModel<T>
{
/// <summary>
/// 返回结果编码 0:失败 1:成功
/// </summary>
public int ResultCode { get; set; } /// <summary>
/// 返回结果内容 成功:Success 失败:异常内容
/// </summary>
public string ResultMsg { get; set; } /// <summary>
/// 返回结果 成功:返回T类型数据 失败:默认null
/// </summary>
public T ResultData { get; set; }
}
}
我们在项目中添加一个Filter文件夹,所有的过滤器都放在该文件夹下面。然后添加一个类:CustomerExceptionFilter,并使该类继承自IAsyncExceptionFilter。代码如下:
using ExceptionDemo.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using System.Threading.Tasks; namespace ExceptionDemo.Filter
{
/// <summary>
/// 自定义异常过滤器
/// </summary>
public class CustomerExceptionFilter : IAsyncExceptionFilter
{
/// <summary>
/// 重写OnExceptionAsync方法,定义自己的处理逻辑
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public Task OnExceptionAsync(ExceptionContext context)
{
// 如果异常没有被处理则进行处理
if(context.ExceptionHandled==false)
{
// 定义返回类型
var result = new ResultModel<string>
{
ResultCode = 0,
ResultMsg = context.Exception.Message
};
context.Result = new ContentResult
{
// 返回状态码设置为200,表示成功
StatusCode = StatusCodes.Status200OK,
// 设置返回格式
ContentType="application/json;charset=utf-8",
Content=JsonConvert.SerializeObject(result)
};
}
// 设置为true,表示异常已经被处理了
context.ExceptionHandled = true;
return Task.CompletedTask;
}
}
}
上面的代码很简单,我们新建了一个自定义的异常过滤器,然后在OnExceptionAsync方法中定义自己的处理逻辑,报错之后依然让http返回状态码为200,并且将错误信息返回到客户端。
然后添加一个控制器,命名为ExceptionFilter,在控制器中模拟发生异常的情况:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ExceptionDemo.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; namespace ExceptionDemo.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ExceptionFilterController : ControllerBase
{
[HttpGet]
public async Task<ResultModel<int>> Get()
{
int i = 0;
int k = 10;
// 这里会发生异常
int j = await Task.Run<int>(() =>
{
return k / i;
}); return new ResultModel<int>()
{
ResultCode=1,
ResultMsg="Success",
ResultData=j
};
}
}
}
最后我们需要把自定义的异常过滤器进行注入,这里选择使用全局注入的方式,在Startup类的ConfigureServices方法中进行注入:
services.AddControllers(options =>
{
options.Filters.Add(new CustomerExceptionFilter());
});
然后运行程序,查看结果:
如何我们没有使用过滤器捕获和处理异常,我们将得到Http状态码为500的内部错误,这种错误不方便定位问题,而且给客户端返回的信息也不够友好。使用了过滤器处理异常,进行特殊处理之后就会显得很友好了。
在上面自定义过滤器的代码中,有下面的一行代码:
context.ExceptionHandled = true;
注意:这句代码很关键,当你处理完异常之后,一定要将此属性更改为true,表示异常已经处理过了,这样其他地方就不会在处理这个异常了。
2、使用中间件处理异常
我们知道,AspNetCore的管道模型具有层层传递的特点,那么我们就可以在管道中实现全局异常捕获。我们新创建一个自定义的异常中间件:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Text.Json;
using System.Threading.Tasks; namespace ExceptionDemo.Middleware
{
/// <summary>
/// 自定义异常中间件
/// </summary>
public class CustomerExceptionMiddleware
{
/// <summary>
/// 委托
/// </summary>
private readonly RequestDelegate _next; public CustomerExceptionMiddleware(RequestDelegate next)
{
_next = next;
} public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{ context.Response.ContentType = "application/problem+json"; var title = "An error occured: " + ex.Message;
var details = ex.ToString(); var problem = new ProblemDetails
{
Status = 200,
Title = title,
Detail = details
}; var stream = context.Response.Body;
await JsonSerializer.SerializeAsync(stream, problem);
}
}
}
}
然后在新建一个扩展方法:
using Microsoft.AspNetCore.Builder; namespace ExceptionDemo.Middleware
{
/// <summary>
/// 静态类
/// </summary>
public static class ExceptionMiddlewareExtension
{
/// <summary>
/// 静态方法
/// </summary>
/// <param name="app">要进行扩展的类型</param>
public static void UseExceptionMiddleware(this IApplicationBuilder app)
{
app.UseMiddleware(typeof(CustomerExceptionMiddleware));
}
}
}
最后在Startup类的Configure方法中使用自定义的异常中间件:
app.UseExceptionMiddleware();
然后我们注释掉上面注册的异常过滤器,运行程序进行访问:
这样也可以捕获到异常。
3、使用框架自带异常中间件
我们首先看下面一段代码:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
这段代码在我们使用AspNetCore创建一个WebApi项目时就会看到,如果是创建的MVC项目,是下面一段代码:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
这两段代码的作用就是捕获和处理异常,是第一个被添加到管道中的中间件。
UseDeveloperExceptionPage的意思很好理解:对于开发模式,一旦报错就跳转到错误堆栈页面。而第二个UseExceptionHandler也很有意思,从它的名字中我们大致可以猜出它肯定是个错误拦截程序。那么它和上面自定义的异常处理中间件有什么区别呢?
UseExceptionHandler其实就是默认的错误处理。它其实也是一个中间件,它的原名叫做ExceptionHandlerMiddleware。在使用UseExceptionHandler方法时,我们可以选填各种参数。比如上面的第二段代码,填入了“/Error”参数,表示当产生异常的时候,将定位到对应的路径,这里定位的页面就是“http://localhost:5001/Error”。这是MVC中自带的一个错误页面,当然,你也可以指定自己定义的一个页面。
UseExceptionHandler还有一个指定ExceptionHandlerOptions参数的扩展方法,该参数是ExceptionHandlerMiddleware中间件的重要参数:
参数名 | 说明 |
---|---|
ExceptionHandlingPath | 重定向的路径,比如刚才的 ""/Error"" 实际上就是指定的该参数 |
ExceptionHandler | 错误拦截处理程序 |
ExceptionHandler允许我们在ExceptionHandlerMiddleware内部指定咱们自己的异常处理逻辑。而该参数的类型为RequestDelegate类型的委托。因此,UseExceptionHandler提供了一个简便的写法,可以让我们在ExceptionHandlerMiddleware中新建自定义的错误拦截管道来处理异常:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using ExceptionDemo.Filter;
using ExceptionDemo.Middleware;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; namespace ExceptionDemo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
#region 注册全局异常过滤器
//services.AddControllers(options =>
//{
// options.Filters.Add(new CustomerExceptionFilter());
//});
#endregion services.AddControllers();
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(builder => builder.Use(ExceptionHandlerDemo));
} app.UseExceptionMiddleware(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
} private async Task ExceptionHandlerDemo(HttpContext httpContext,Func<Task> next)
{
//该信息由ExceptionHandlerMiddleware中间件提供,里面包含了ExceptionHandlerMiddleware中间件捕获到的异常信息。
var exceptionDetails = httpContext.Features.Get<IExceptionHandlerFeature>();
var ex = exceptionDetails?.Error; if (ex != null)
{
httpContext.Response.ContentType = "application/problem+json"; var title = "An error occured: " + ex.Message;
var details = ex.ToString(); var problem = new ProblemDetails
{
Status = 500,
Title = title,
Detail = details
}; var stream = httpContext.Response.Body;
await JsonSerializer.SerializeAsync(stream, problem);
}
}
}
}
三、中间件和过滤器的比较
在上面的例子中,我们分别使用了中间件和过滤器的方式来处理异常,那么中间件和过滤器有什么区别呢?两者的区别:拦截范围的不同。
IExceptionFilter作为一种过滤器,它需要在控制器发现错误之后将错误信息提交给它处理,因此它的异常处理范围是控制器内部。如果我们想捕获进入控制器之前的一些错误,IExceptionFilter是捕获不到的。而对于ExceptionHandlerMiddleware异常中间件来说就很容易了,它作为第一个中间件被添加到管道中,在它之后发生的任何异常都可以捕获的到。
那么为什么要有两种异常处理的方式呢?只使用ExceptionHandlerMiddleware中间件处理异常不可以吗?它可以捕获任何时候发生的异常,为什么还要有过滤器呢?如果你想在控制器发生异常时快速捕获和处理异常,那么使用过滤器处理异常是非常不错的选择。如果是控制器内部发生了异常,首先是由过滤器捕获到异常,最后才是中间件捕获到异常。
我们在自定义过滤器的时候有这样一段代码:context.ExceptionHandled = true;如果在自定义过滤器中将异常标记为已经处理之后,则第一个异常处理中间件就认为没有错误了,不会进入到处理逻辑中了。所以,如果不把 ExceptionHandled属性设置为true,可能出现异常处理结果被覆盖的情况。
GitHub代码:git@github.com:jxl1024/ExceptionDemo.git
.NET Core:处理全局异常的更多相关文章
- Asp.net Core WebApi 全局异常类
通过全局异常类,所有程序中遇到的错误都会被拦截,并友好的返回结果. 1.自定义一个全局异常处理类中间件 using Microsoft.AspNetCore.Http; using Newtonsof ...
- asp.net core MVC 全局过滤器之ExceptionFilter异常过滤器(一)
本系类将会讲解asp.net core MVC中的内置全局过滤器的使用,将分为以下章节 asp.net core MVC 过滤器之ExceptionFilter异常过滤器(一) asp.net cor ...
- Net Core集成Exceptionless分布式日志功能以及全局异常过滤
Net Core集成Exceptionless分布式日志功能以及全局异常过滤 相信很多朋友都看过我的上篇关于Exceptionless的简单入门教程[asp.Net Core免费开源分布式异常日志收集 ...
- .NET Core整合log4net以及全局异常捕获实现2
Startup代码 public static ILoggerRepository repository { get; set; } public Startup(IConfiguration con ...
- Asp.net Core全局异常监控和记录日志
前言 系统异常监控可以说是重中之重,系统不可能一直运行良好,开发和运维也不可能24小时盯着系统,系统抛异常后我们应当在第一时间收到异常信息.在Asp.net Core里我使用拦截器 ...
- ASP.NET Core Web API中实现全局异常捕获与处理
处理全局异常 HANDLING ERRORS GLOBALLY 在上面的示例中,我们的 action 内部有一个 try-catch 代码块.这一点很重要,我们需要在我们的 action 方法体中处理 ...
- ASP.NET Core 中间件 自定义全局异常中间件以及 MVC异常过滤器作用
中间件是一种装配到应用管道以处理请求和响应的软件. 每个组件: 选择是否将请求传递到管道中的下一个组件. 可在管道中的下一个组件前后执行工作. 请求委托用于生成请求管道. 请求委托处理每个 HTTP ...
- .NET Core整合log4net以及全局异常捕获实现
在使用log4net之前先安装log4net.这里操作很简单,通过nuget下载并安装log4net很方便.如下图. log4net配置 <?xml version="1.0" ...
- asp.net core全局异常过滤并监控系统BUG将异常信息记录到日志
添加: using Dw.Util.Helper; using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Collect ...
随机推荐
- python 动态指定header获取网页源代码的函数
import random import requests def get_htmla(url): aui=0 while aui==0: try: header={'User-Agent':'Moz ...
- TWAIN.CPP
include "twaincpp.h" #include <QtGlobal> #include <QDateTime> #include <QSt ...
- C语言:带参数的宏与函数的区别
带参数的宏和函数很相似,但有本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算:宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存.而函数是一段可以重复使用的代码,会被编译,会给它 ...
- [刘阳Java]_EasyUI环境搭建_第2讲
在EasyUI的第1讲中我们介绍了学习EasyUI能够做什么,这次我们得快速搭建一个EasyUI环境,来测试一下它的运行效果 1.jQuery EasyUI环境搭建 <script type=& ...
- 【动画消消乐】HTML+CSS 自定义加载动画:清新折叠方块效果 063(附源码及原理详解)
前言 Hello!小伙伴! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出- 自我介绍ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿|C++选手|学生 简介:因C语言结识编程,随后转入计算机专 ...
- java并发编程基础—生命周期与线程控制
一.线程生命周期 线程被创建启动以后,他既不是一启动就进入执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经过新建(New).就绪(Runnable).运行(Running).阻塞(Bloc ...
- HTML - form表单操作
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- Qt+MPlayer音乐播放器开发笔记(一):ubuntu上编译MPlayer以及Demo演示
前言 在ubuntu上实现MPlayer播放器播放音乐. Demo Mplayer MPlayer是一款开源多媒体播放器,以GNU通用公共许可证发布.此款软件 ...
- Oracle19c 如何用rman duplicate 克隆一个数据库。(Active方式)
Oracle19c 如何用rman duplicate 克隆一个数据库. 首先克隆有两种方法,一种是Backup-Based,一种是Active方式.官网文档链接https://docs.oracle ...
- (opencv01)读入图像
(opencv01)Opencv之读入图像 读入图像 img = cv2.imread(filepath, flags) filepath : 要读入图片的完整路径 flags : 读入图片的标志 ...