前言

    在业务系统,异常处理是所有开发人员必须面对的问题,在一定程度上,异常处理的能力反映出开发者对业务的驾驭水平;本章将着重介绍如何在 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() 中人为的制造一点麻烦。
  1. [HttpGet]
  2. public ActionResult<IEnumerable<string>> Get()
  3. {
  4. throw new Exception("出错了.....");
  5. return new string[] { "value1", "value2" };
  6. }
2.2 这是由于项目配置了运行环境变量 ASPNETCORE_ENVIRONMENT=Development 后,Startup.cs 中配置了开发环境下,使用系统默认页,所以我们才可以看到上面的异常信息

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

3. 异常处理方式一:服务过滤

    在传统的 Asp.Net MVC 应用程序中,我们一般都使用服务过滤的方式去捕获和处理异常,这种方式非常常见,而且可用性来说,体验也不错,幸运的是 Asp.Net Core 也完整的支持该方式,接下来创建一个全局异常处理类 CustomerExceptionFilter

  1. public class CustomerExceptionFilter : Attribute, IExceptionFilter
  2. {
  3. private readonly ILogger logger = null;
  4. private readonly IHostingEnvironment environment = null;
  5. public CustomerExceptionFilter(ILogger<CustomerExceptionFilter> logger, IHostingEnvironment environment)
  6. {
  7. this.logger = logger;
  8. this.environment = environment;
  9. }
  10. public void OnException(ExceptionContext context)
  11. {
  12. Exception exception = context.Exception;
  13. string error = string.Empty;
  14. void ReadException(Exception ex)
  15. {
  16. error += string.Format("{0} | {1} | {2}", ex.Message, ex.StackTrace, ex.InnerException);
  17. if (ex.InnerException != null)
  18. {
  19. ReadException(ex.InnerException);
  20. }
  21. }
  22. ReadException(context.Exception);
  23. logger.LogError(error);
  24. ContentResult result = new ContentResult
  25. {
  26. StatusCode = 500,
  27. ContentType = "text/json;charset=utf-8;"
  28. };
  29. if (environment.IsDevelopment())
  30. {
  31. var json = new { message = exception.Message, detail = error };
  32. result.Content = JsonConvert.SerializeObject(json);
  33. }
  34. else
  35. {
  36. result.Content = "抱歉,出错了";
  37. }
  38. context.Result = result;
  39. context.ExceptionHandled = true;
  40. }
  41. }
3.1 CustomerExceptionFilter 继承自 IExceptionFilter 接口,并实现 void OnException(ExceptionContext context) 方法,在 CustomerExceptionFilter

构造方法中,定义了两个参数,用于记录异常日志和获取程序运行环境变量

  1. private readonly ILogger logger = null;
  2. private readonly IHostingEnvironment environment = null;
  3. public CustomerExceptionFilter(ILogger<CustomerExceptionFilter> logger, IHostingEnvironment environment)
  4. {
  5. this.logger = logger;
  6. this.environment = environment;
  7. }
3.2 在接下来的 OnException 方法中,利用 environment 进行产品环境的判断,并使用 logger 将日志写入硬盘文件中,为了将日志写入硬盘,

需要引用 Nuget 包 NLog.Extensions.Logging/NLog.Web.AspNetCore ,并在 Startup.cs 文件的 Configure 方法中添加扩展

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory)
  2. {
  3. // 将 NLog
  4. factory.AddConsole(Configuration.GetSection("Logging"))
  5. .AddNLog()
  6. .AddDebug();
  7. var nlogFile = System.IO.Path.Combine(env.ContentRootPath, "nlog.config");
  8. env.ConfigureNLog(nlogFile);
  9. if (env.IsDevelopment())
  10. {
  11. app.UseDeveloperExceptionPage();
  12. }
  13. app.UseMvc();
  14. }
3.3 上面的代码读取了配置文件 nlog.config 并设置为 NLog 的配置,该文件定义如下
  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="info">
  3. <!-- Load the ASP.NET Core plugin -->
  4. <extensions>
  5. <add assembly="NLog.Web.AspNetCore"/>
  6. </extensions>
  7. <!-- Layout: https://github.com/NLog/NLog/wiki/Layout%20Renderers -->
  8. <targets>
  9. <target xsi:type="File" name="errorfile" fileName="/data/logs/logfilter/error-${shortdate}.log" layout="${longdate}|${logger}|${uppercase:${level}}| ${message} ${exception}|${aspnet-Request-Url}" />
  10. <target xsi:type="Null" name="blackhole" />
  11. </targets>
  12. <rules>
  13. <logger name="Microsoft.*" minlevel="Error" writeTo="blackhole" final="true" />
  14. <logger name="*" minlevel="Error" writeTo="errorfile" />
  15. </rules>
  16. </nlog>
3.4 为了在 WebApi 控制器中使用 CustomerExceptionFilter 过滤器,我们还需要在 Startup.cs 将 CustomerExceptionFilter 注入到容器中
  1. // This method gets called by the runtime. Use this method to add services to the container.
  2. public void ConfigureServices(IServiceCollection services)
  3. {
  4. // 将异常过滤器注入到容器中
  5. services.AddScoped<CustomerExceptionFilter>();
  6. services.AddMvc()
  7. .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
  8. }
3.5 最后,在控制器 ValuesController 中应用该异常过滤器
  1. [ServiceFilter(typeof(CustomerExceptionFilter))]
  2. [Route("api/[controller]"), ApiController]
  3. public class ValuesController : ControllerBase
  4. {
  5. // GET api/values
  6. [HttpGet]
  7. public ActionResult<IEnumerable<string>> Get()
  8. {
  9. throw new Exception("出错了.....");
  10. return new string[] { "value1", "value2" };
  11. }
  12. }
3.6 现在,按 F5 启动程序,如预期所料,报错信息被 CustomerExceptionFilter 捕获,并转换为 json 格式输出

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

4. 异常处理方式二:中间件捕获

接下来利用 .NetCore 的管道模式,在中间件中对异常进行捕获,首先,创建一个中间件

  1. public class ExceptionMiddleware
  2. {
  3. private readonly RequestDelegate next;
  4. private readonly ILogger logger;
  5. private IHostingEnvironment environment;
  6. public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger, IHostingEnvironment environment)
  7. {
  8. this.next = next;
  9. this.logger = logger;
  10. this.environment = environment;
  11. }
  12. public async Task Invoke(HttpContext context)
  13. {
  14. try
  15. {
  16. await next.Invoke(context);
  17. var features = context.Features;
  18. }
  19. catch (Exception e)
  20. {
  21. await HandleException(context, e);
  22. }
  23. }
  24. private async Task HandleException(HttpContext context, Exception e)
  25. {
  26. context.Response.StatusCode = 500;
  27. context.Response.ContentType = "text/json;charset=utf-8;";
  28. string error = "";
  29. void ReadException(Exception ex)
  30. {
  31. error += string.Format("{0} | {1} | {2}", ex.Message, ex.StackTrace, ex.InnerException);
  32. if (ex.InnerException != null)
  33. {
  34. ReadException(ex.InnerException);
  35. }
  36. }
  37. ReadException(e);
  38. if (environment.IsDevelopment())
  39. {
  40. var json = new { message = e.Message, detail = error };
  41. error = JsonConvert.SerializeObject(json);
  42. }
  43. else
  44. error = "抱歉,出错了";
  45. await context.Response.WriteAsync(error);
  46. }
  47. }
4.1 代码比较简单,在管道中使用 try/catch 进行捕获异常

创建 HandleException(HttpContext context, Exception e) 处理异常,判断是 Development 环境下,输出详细的错误信息,非 Development 环境仅提示调用者“抱歉,出错了”,同时使用 NLog 组件将日志写入硬盘;同样,在 Startup.cs 中将 ExceptionMiddleware 加入管道中

  1. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  2. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory)
  3. {
  4. // 将 NLog
  5. factory.AddConsole(Configuration.GetSection("Logging"))
  6. .AddNLog()
  7. .AddDebug();
  8. var nlogFile = System.IO.Path.Combine(env.ContentRootPath, "nlog.config");
  9. env.ConfigureNLog(nlogFile);
  10. // ExceptionMiddleware 加入管道
  11. app.UseMiddleware<ExceptionMiddleware>();
  12. //if (env.IsDevelopment())
  13. //{
  14. // app.UseDeveloperExceptionPage();
  15. //}
  16. app.UseMvc();
  17. }
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. 【Richard 的刷(水)题记录】

    大概想了想,还是有个记录比较好. 9/24 网络流一日游: 最大流:bzoj1711[Usaco2007 Open]Dining 拆点 BZOJ 3993 Sdoi2015 星际战争 二分 P.S.这 ...

  2. 使用 NLog 给 Asp.Net Core 做请求监控

    为了减少由于单个请求挂掉而拖垮整站的情况发生,给所有请求做统计是一个不错的解决方法,通过观察哪些请求的耗时比较长,我们就可以找到对应的接口.代码.数据表,做有针对性的优化可以提高效率.在 asp.ne ...

  3. 遍历数组 foreach

    package com.java.array; public class Myforeach { public static void main(String[] ARGS){ /* int arr[ ...

  4. numpy C语言源代码调试(二)

    前一篇已经介绍,可以使用gdb进行调试,但是本人不太习惯gdb的文本界面,所以希望找一个比较好用的gdb的前端gui调试器. 想到的第一个是一个非常老的调试工具,DDD. DDD - Data Dis ...

  5. 一个比喻讲明Docker是什么

    之前一直听运维的同事讲Docker,说弄个Docker镜像,打包些应用什么的,还有时不时地在一些帖子里见到过关于Docker的三言两语,然后自己也自我感觉良好的把它总结归纳了一下认为:"往D ...

  6. 对SVN的落地与实践总结

    现今最为流行的Git是管理很几套很成熟的分支管理策略.而SVN确实也有,但结合现公司的实际场景还是做了些调整和变动. 一.分支命名规则 所有分支命名采用小写字母 + 数字 + 特殊符号 组成 项目分支 ...

  7. C#操作符??,?,?:功能解析

    ??操作符:叫做空合并操作符,它会对左右两个操作数进行判断,如果左边的数不为空,就返回左边的数,否则返回右边的数. ?操作符:语法糖,表示可空类型,可空类型也是值类型,它是包含null值的值类型,可通 ...

  8. html学习之路--简单图片轮播

    一个简单的图片轮播效果 photo.html页面代码,基本的HTML结构,在main中显示图片,此处图片依次命名为1.jpg.2.jpg.3.jpg.4.jpg. <!DOCTYPE html& ...

  9. 解决mysql中只能通过localhost访问不能通过ip访问的问题

    解决mysql中只能通过localhost访问不能通过ip访问的问题 原因是没开权限 SELECT * FROM USER WHERE USER='root'; grant all privilege ...

  10. sqlite数据库如何远程连接?

    sqlite数据库如何远程连接代码如下:QSqlDatabase db =QSqlDatabase::addDatabase("QSQLITE"); db.setHostName( ...