前言

    在业务系统,异常处理是所有开发人员必须面对的问题,在一定程度上,异常处理的能力反映出开发者对业务的驾驭水平;本章将着重介绍如何在 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依赖注入和管道方式的异常处理及日志记录的更多相关文章

  1. C#反射与特性(六):设计一个仿ASP.NETCore依赖注入Web

    目录 1,编写依赖注入框架 1.1 路由索引 1.2 依赖实例化 1.3 实例化类型.依赖注入.调用方法 2,编写控制器和参数类型 2.1 编写类型 2.2 实现控制器 3,实现低配山寨 ASP.NE ...

  2. ASP.NET 依赖注入。

    ASP.NET 依赖注入. http://www.it165.net/pro/html/201407/17685.html 我在网上看到了这篇文章,这边文章主要说的方法就是通过读取配置文件来解决依赖注 ...

  3. 一篇关于spring ioc 依赖注入3种方式的文章引用

    今天看到一篇spring ioc 容器依赖注入3种方式的文章,为了方便后面的复习,在此引用别人的文章,查看请戳我.

  4. ASP .NET依赖注入理解

    ASP .NET依赖注入理解[转]:  https://www.cnblogs.com/wzk153/p/10892444.html

  5. Asp.Net Core 2.0 项目实战(9) 日志记录,基于Nlog或Microsoft.Extensions.Logging的实现及调用实例

    本文目录 1. Net下日志记录 2. NLog的使用     2.1 添加nuget引用NLog.Web.AspNetCore     2.2 配置文件设置     2.3 依赖配置及调用     ...

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

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

  7. Spring依赖注入三种方式详解

    在讲解Spring依赖注入之前的准备工作: 下载包含Spring的工具jar包的压缩包 解压缩下载下来的Spring压缩包文件 解压缩之后我们会看到libs文件夹下有许多jar包,而我们只需要其中的c ...

  8. Spring第七弹—依赖注入之注解方式注入及编码解析@Resource原理

        注入依赖对象可以采用手工装配或自动装配,在实际应用中建议使用手工装配,因为自动装配会产生未知情况,开发人员无法预见最终的装配结果. 手工装配依赖对象  手工装配依赖对象,在这种方式中又有两种编 ...

  9. 几个步骤轻松搞定ASP.NET 依赖注入。

    http://www.it165.net/pro/html/201407/17685.html 我在网上看到了这篇文章,这边文章主要说的方法就是通过读取配置文件来解决依赖注入的问题.但是每次新建一个依 ...

随机推荐

  1. 【源码解析】Sharding-Jdbc的执行过程(一)

    一.ShardingContext 在Sharding-Jdbc中,我们其实需要抓住一个核心类,也就是ShardingContext,分片上下文,里面定义了下面几个内容: @RequiredArgsC ...

  2. BZOJ_1712_[Usaco2007 China]Summing Sums 加密_矩阵乘法

    BZOJ_1712_[Usaco2007 China]Summing Sums 加密_矩阵乘法 Description     那N只可爱的奶牛刚刚学习了有关密码的许多算法,终于,她们创造出了属于奶牛 ...

  3. BZOJ_2792_[Poi2012]Well_二分答案

    BZOJ_2792_[Poi2012]Well_二分答案 Description 给出n个正整数X1,X2,...Xn,可以进行不超过m次操作,每次操作选择一个非零的Xi,并将它减一. 最终要求存在某 ...

  4. C# 利用VS自带的WSDL工具生成WebService服务类

    C# 利用VS自带的WSDL工具生成WebService服务类   WebService有两种使用方式,一种是直接通过添加服务引用,另一种则是通过WSDL生成. 添加服务引用大家基本都用过,这里就不讲 ...

  5. Django:Python3.6.2+Django2.0配置MySQL

    持续学习Django中... Django默认使用的数据库是python自带的SQLlite3,但SQLlite并不适用于大型的项目,因此我将数据库换成了MySQL,下面介绍下Django如何配置数据 ...

  6. 零基础怎么学java

    首先告诉你的是,作为一个初学者想转行学习Java并不是很容易,Java本身是具有一定难度的,虽然说兴趣这东西可以让我们学习不累,但是有多少人学习是因为兴趣,或者有多少人知道自己的兴趣在哪?所以我很明确 ...

  7. LeetCode算法题-Number of Lines To Write String(Java实现)

    这是悦乐书的第319次更新,第340篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第188题(顺位题号是806).我们要将给定字符串S的字母从左到右写成行.每行最大宽度为 ...

  8. vue客户端渲染首屏优化之道

    提取第三方库,缓存,减少打包体积 1. dll动态链接库, 使用DllPlugin DllReferencePlugin,将第三方库提取出来另外打包出来,然后动态引入html.可以提高打包速度和缓存第 ...

  9. Python猫荐书系列:文也深度学习,理也深度学习

    最近出了两件大新闻,相信大家可能有所耳闻. 我来当个播报员,给大家转述一下: 1.中国队在第 11 界罗马尼亚数学大师赛(RMM)中无缘金牌.该项赛事是三大国际赛事之一,被誉为中学奥数的最高难度.其中 ...

  10. Android版数据结构与算法(四):基于哈希表实现HashMap核心源码彻底分析

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 存储键值对我们首先想到HashMap,它的底层基于哈希表,采用数组存储数据,使用链表来解决哈希碰撞,它是线程不安全的,并且存储的key只能有一个为 ...