Asp.NetCore依赖注入和管道方式的异常处理及日志记录
前言
在业务系统,异常处理是所有开发人员必须面对的问题,在一定程度上,异常处理的能力反映出开发者对业务的驾驭水平;本章将着重介绍如何在 WebApi 程序中对异常进行捕获,然后利用 Nlog 组件进行记录;同时,还将介绍两种不同的
异常捕获方式:管道捕获/服务过滤;通过本练习,将学习到如何捕获异常、处理异常跳转、记录异常信息。
1. 搭建框架
首先,创建一个 WebApi 项目,选择 Asp.Net Core Web 应用程序;

1.1 进一步选择 Api 模板,这里使用的 .netcore 版本为 2.1

- 取消勾选 “启用 Docker 支持(E)” 和 “为 Https 配置(C)”,点击确定,得到一个完整的 WebApi 项目框架,如图

1.2 直接按 F5 运行项目,一切正常,程序启动后进入默认路由调用,并输出结果
2. 异常路由

2.1 一切看起来都非常正常和美好,但,祸之福所倚;接下来我们在 接口 Get() 中人为的制造一点麻烦。
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
throw new Exception("出错了.....");
return new string[] { "value1", "value2" };
}
2.2 这是由于项目配置了运行环境变量 ASPNETCORE_ENVIRONMENT=Development 后,Startup.cs 中配置了开发环境下,使用系统默认页,所以我们才可以看到上面的异常信息

如果你把环境变量设置为 ASPNETCORE_ENVIRONMENT=Production ,你会发现,在异常发生的时候,你得到了一个空白页。

3. 异常处理方式一:服务过滤
在传统的 Asp.Net MVC 应用程序中,我们一般都使用服务过滤的方式去捕获和处理异常,这种方式非常常见,而且可用性来说,体验也不错,幸运的是 Asp.Net Core 也完整的支持该方式,接下来创建一个全局异常处理类 CustomerExceptionFilter
public class CustomerExceptionFilter : Attribute, IExceptionFilter
{
private readonly ILogger logger = null;
private readonly IHostingEnvironment environment = null;
public CustomerExceptionFilter(ILogger<CustomerExceptionFilter> logger, IHostingEnvironment environment)
{
this.logger = logger;
this.environment = environment;
}
public void OnException(ExceptionContext context)
{
Exception exception = context.Exception;
string error = string.Empty;
void ReadException(Exception ex)
{
error += string.Format("{0} | {1} | {2}", ex.Message, ex.StackTrace, ex.InnerException);
if (ex.InnerException != null)
{
ReadException(ex.InnerException);
}
}
ReadException(context.Exception);
logger.LogError(error);
ContentResult result = new ContentResult
{
StatusCode = 500,
ContentType = "text/json;charset=utf-8;"
};
if (environment.IsDevelopment())
{
var json = new { message = exception.Message, detail = error };
result.Content = JsonConvert.SerializeObject(json);
}
else
{
result.Content = "抱歉,出错了";
}
context.Result = result;
context.ExceptionHandled = true;
}
}
3.1 CustomerExceptionFilter 继承自 IExceptionFilter 接口,并实现 void OnException(ExceptionContext context) 方法,在 CustomerExceptionFilter
构造方法中,定义了两个参数,用于记录异常日志和获取程序运行环境变量
private readonly ILogger logger = null;
private readonly IHostingEnvironment environment = null;
public CustomerExceptionFilter(ILogger<CustomerExceptionFilter> logger, IHostingEnvironment environment)
{
this.logger = logger;
this.environment = environment;
}
3.2 在接下来的 OnException 方法中,利用 environment 进行产品环境的判断,并使用 logger 将日志写入硬盘文件中,为了将日志写入硬盘,
需要引用 Nuget 包 NLog.Extensions.Logging/NLog.Web.AspNetCore ,并在 Startup.cs 文件的 Configure 方法中添加扩展
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory)
{
// 将 NLog
factory.AddConsole(Configuration.GetSection("Logging"))
.AddNLog()
.AddDebug();
var nlogFile = System.IO.Path.Combine(env.ContentRootPath, "nlog.config");
env.ConfigureNLog(nlogFile);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
3.3 上面的代码读取了配置文件 nlog.config 并设置为 NLog 的配置,该文件定义如下
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="info">
<!-- Load the ASP.NET Core plugin -->
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<!-- Layout: https://github.com/NLog/NLog/wiki/Layout%20Renderers -->
<targets>
<target xsi:type="File" name="errorfile" fileName="/data/logs/logfilter/error-${shortdate}.log" layout="${longdate}|${logger}|${uppercase:${level}}| ${message} ${exception}|${aspnet-Request-Url}" />
<target xsi:type="Null" name="blackhole" />
</targets>
<rules>
<logger name="Microsoft.*" minlevel="Error" writeTo="blackhole" final="true" />
<logger name="*" minlevel="Error" writeTo="errorfile" />
</rules>
</nlog>
3.4 为了在 WebApi 控制器中使用 CustomerExceptionFilter 过滤器,我们还需要在 Startup.cs 将 CustomerExceptionFilter 注入到容器中
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// 将异常过滤器注入到容器中
services.AddScoped<CustomerExceptionFilter>();
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
3.5 最后,在控制器 ValuesController 中应用该异常过滤器
[ServiceFilter(typeof(CustomerExceptionFilter))]
[Route("api/[controller]"), ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
throw new Exception("出错了.....");
return new string[] { "value1", "value2" };
}
}
3.6 现在,按 F5 启动程序,如预期所料,报错信息被 CustomerExceptionFilter 捕获,并转换为 json 格式输出

3.7 同时,NLog 组件也将日志信息记录到了硬盘中

4. 异常处理方式二:中间件捕获
接下来利用 .NetCore 的管道模式,在中间件中对异常进行捕获,首先,创建一个中间件
public class ExceptionMiddleware
{
private readonly RequestDelegate next;
private readonly ILogger logger;
private IHostingEnvironment environment;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger, IHostingEnvironment environment)
{
this.next = next;
this.logger = logger;
this.environment = environment;
}
public async Task Invoke(HttpContext context)
{
try
{
await next.Invoke(context);
var features = context.Features;
}
catch (Exception e)
{
await HandleException(context, e);
}
}
private async Task HandleException(HttpContext context, Exception e)
{
context.Response.StatusCode = 500;
context.Response.ContentType = "text/json;charset=utf-8;";
string error = "";
void ReadException(Exception ex)
{
error += string.Format("{0} | {1} | {2}", ex.Message, ex.StackTrace, ex.InnerException);
if (ex.InnerException != null)
{
ReadException(ex.InnerException);
}
}
ReadException(e);
if (environment.IsDevelopment())
{
var json = new { message = e.Message, detail = error };
error = JsonConvert.SerializeObject(json);
}
else
error = "抱歉,出错了";
await context.Response.WriteAsync(error);
}
}
4.1 代码比较简单,在管道中使用 try/catch 进行捕获异常
创建 HandleException(HttpContext context, Exception e) 处理异常,判断是 Development 环境下,输出详细的错误信息,非 Development 环境仅提示调用者“抱歉,出错了”,同时使用 NLog 组件将日志写入硬盘;同样,在 Startup.cs 中将 ExceptionMiddleware 加入管道中
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory)
{
// 将 NLog
factory.AddConsole(Configuration.GetSection("Logging"))
.AddNLog()
.AddDebug();
var nlogFile = System.IO.Path.Combine(env.ContentRootPath, "nlog.config");
env.ConfigureNLog(nlogFile);
// ExceptionMiddleware 加入管道
app.UseMiddleware<ExceptionMiddleware>();
//if (env.IsDevelopment())
//{
// app.UseDeveloperExceptionPage();
//}
app.UseMvc();
}
4.2 一切就绪,按 F5 运行程序,网页中输出了期望中的 json 格式错误信息,同时 NLog 组件也将日志写入了硬盘

结语
在本例中,通过依赖注入和管道中间件的方式,演示了两种不同的全局捕获异常处理的过程;值得注意到是,两种方式对于 NLog 的使用,都是一样的,没有任何差别,代码无需改动;实际项目中,也是应当区分不同的业务场景,输出不同的日志信息,不管是从安全或者是用户体验友好性上面来说,都是非常值得推荐的方式,全局异常捕获处理,完全和业务剥离。
源码下载
https://github.com/lianggx/EasyAspNetCoreDemo/tree/master/Ron.LogFilter
Asp.NetCore依赖注入和管道方式的异常处理及日志记录的更多相关文章
- C#反射与特性(六):设计一个仿ASP.NETCore依赖注入Web
目录 1,编写依赖注入框架 1.1 路由索引 1.2 依赖实例化 1.3 实例化类型.依赖注入.调用方法 2,编写控制器和参数类型 2.1 编写类型 2.2 实现控制器 3,实现低配山寨 ASP.NE ...
- ASP.NET 依赖注入。
ASP.NET 依赖注入. http://www.it165.net/pro/html/201407/17685.html 我在网上看到了这篇文章,这边文章主要说的方法就是通过读取配置文件来解决依赖注 ...
- 一篇关于spring ioc 依赖注入3种方式的文章引用
今天看到一篇spring ioc 容器依赖注入3种方式的文章,为了方便后面的复习,在此引用别人的文章,查看请戳我.
- ASP .NET依赖注入理解
ASP .NET依赖注入理解[转]: https://www.cnblogs.com/wzk153/p/10892444.html
- Asp.Net Core 2.0 项目实战(9) 日志记录,基于Nlog或Microsoft.Extensions.Logging的实现及调用实例
本文目录 1. Net下日志记录 2. NLog的使用 2.1 添加nuget引用NLog.Web.AspNetCore 2.2 配置文件设置 2.3 依赖配置及调用 ...
- ASP.NET Core 异常处理与日志记录
1. ASP.NET Core 异常处理与日志记录 1.1. 异常处理 1.1.1. 异常产生的原因及处理 1.1.2. ASP.NET Core中启动开发人员异常页面 1.2. 日志记录 1.2.1 ...
- Spring依赖注入三种方式详解
在讲解Spring依赖注入之前的准备工作: 下载包含Spring的工具jar包的压缩包 解压缩下载下来的Spring压缩包文件 解压缩之后我们会看到libs文件夹下有许多jar包,而我们只需要其中的c ...
- Spring第七弹—依赖注入之注解方式注入及编码解析@Resource原理
注入依赖对象可以采用手工装配或自动装配,在实际应用中建议使用手工装配,因为自动装配会产生未知情况,开发人员无法预见最终的装配结果. 手工装配依赖对象 手工装配依赖对象,在这种方式中又有两种编 ...
- 几个步骤轻松搞定ASP.NET 依赖注入。
http://www.it165.net/pro/html/201407/17685.html 我在网上看到了这篇文章,这边文章主要说的方法就是通过读取配置文件来解决依赖注入的问题.但是每次新建一个依 ...
随机推荐
- BZOJ_1015_[JSOI2008]星球大战_并查集
BZOJ_1015_[JSOI2008]星球大战_并查集 题意:很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治者整个星系.某一天,凭着一个偶然的 机遇,一支反抗军摧毁了帝国的超级武器, ...
- python接口自动化(十五)--参数关联接口(详解)
简介 我们用自动化新建任务之后,要想接着对这个新建任务操作,那就需要用参数关联了,新建任务之后会有一个任务的Jenkins-Crumb,获取到这个Jenkins-Crumb,就可以通过传这个任务Jen ...
- 服务部署到Swarm Cluster中
对于已存在的镜像,将其部署到服务器中并开始对外服务,便是它的职责,而我们要做的便是帮助它完成职责,前两个应用环节都已产生了相应的镜像,在这一环节,将完成服务部署到容器集群的工作,对于这一过程,实际执行 ...
- 约定Service构建方式
对于DevOps中,将开发好的软件交付给运维人员去部署与维护,过程中参杂着诸多不可控制的变量,如环境问题.版本问题等等,而Docker容器极大程度上解决了这些问题,同时对于服务的持续交付,也变得方便和 ...
- FreeSql 扩展包实现 Dapper 的使用习惯
简介 FreeSql.Connection.Extensions 这是 FreeSql 衍生出来的扩展包,实现(Mysql/postgresql/sqlserver/Oracle/SQLite)数据库 ...
- Node 框架接入 ELK 实践总结
本文由云+社区发表 作者:J2X 我们都有过上机器查日志的经历,当集群数量增多的时候,这种原始的操作带来的低效率不仅给我们定位现网问题带来极大的挑战,同时,我们也无法对我们服务框架的各项指标进行有效的 ...
- Python基础 列表介绍、使用
第3章 学习目标: 列表是什么以及如何使用列表元素.列表让你能够在一个地方存储成组的信息,其中可以只包含几个元素,也可以包含数百万个元素.列表是新手可直接使用的最强大的Python功能之一,它融合了众 ...
- js 原型,原型链,原型链继承浅析
对于网上的关于原型,原型链和原型链继承的晦涩语言说明就不累赘了,复制粘贴过来再解释一遍怕自己也整蒙了,本人最怕空气突然安静,四目对视,大眼对小眼,一脸懵逼. 我们先看下面
- Android 6.0 默认关闭定位和GPS,开启后默认选省电
默认关闭定位和GPS 修改位置 frameworks/base/packages/SettingsProvider/res/values/defaults.xml <string name=&q ...
- 【error】Gradle sync failed: Unable to start the daemon process.【已解决】
---恢复内容开始--- 在克隆GIT项目后,Android Studio 报错: Gradle sync failed: Unable to start the daemon process. Th ...