微信号:iDotNet

介绍:专注分享 .NET 相关技术文章、教程和工具.有时也会涉及到IT职场相关的一些东西,或者来点幽默趣文.

ASP.NET Core依赖注入和管道方式的异常处理及日志记录

2018-11-26 11:45 DotNet

(给DotNet加星标,提升.Net技能


转自:Ron.liang

cnblogs.com/viter/p/10013195.html


前言


在业务系统,异常处理是所有开发人员必须面对的问题,在一定程度上,异常处理的能力反映出开发者对业务的驾驭水平;


本章将着重介绍如何在 WebApi 程序中对异常进行捕获,然后利用 Nlog 组件进行记录;同时,还将介绍两种不同的,异常捕获方式:管道捕获/服务过滤;通过本练习将学习到如何捕获异常、处理异常跳转、记录异常信息。


搭建框架


首先,创建一个 WebApi 项目,选择 Asp.Net Core Web 应用程序;



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



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



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


异常路由



一切看起来都非常正常和美好,但,祸之福所倚;接下来我们在 接口 Get() 中人为的制造一点麻烦。


[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
   throw new Exception("出错了.....");
   return new string[] { "value1", "value2" };
}


这是由于项目配置了运行环境变量 ASPNETCORE_ENVIRONMENT=Development 后,Startup.cs 中配置了开发环境下,使用系统默认页,所以我们才可以看到上面的异常信息



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



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


在传统的 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;
   }
}


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;
}


在接下来的 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();
}


上面的代码读取了配置文件 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>


为了在 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);
}


最后,在控制器 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" };
   }
}


现在,按 F5 启动程序,如预期所料,报错信息被 CustomerExceptionFilter 捕获,并转换为 json 格式输出



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



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


接下来利用 .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);
   }
}


代码比较简单,在管道中使用 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();
}


一切就绪,按 F5 运行程序,网页中输出了期望中的 json 格式错误信息,同时 NLog 组件也将日志写入了硬盘



结语


在本例中,通过依赖注入和管道中间件的方式,演示了两种不同的全局捕获异常处理的过程;值得注意到是,两种方式对于 NLog 的使用,都是一样的,没有任何差别,代码无需改动;


实际项目中,也是应当区分不同的业务场景,输出不同的日志信息,不管是从安全或者是用户体验友好性上面来说,都是非常值得推荐的方式,全局异常捕获处理,完全和业务剥离。


努力为开源社区作贡献,推荐一个自己开发的基于 .NET Core + pgsql 的快速开发脚手架,内置 ORM框架,github地址:https://github.com/lianggx/mystaging


源码下载:https://files.cnblogs.com/files/viter/Ron.LogFilter.zip


推荐阅读

(点击标题可跳转阅读)

.NET Core 使用AngleSharp爬取周公解梦数据

.NET Core未来发展趋势的浅层判断

ASP.NET Core Jenkins Docker 实现一键化部署


看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

 
DotNet 更多文章 ASP.NET Core Jenkins Docker 实现一键化部署 .NET Core 使用AngleSharp爬取周公解梦数据 转行程序员?你可能忽略了一件事! .NET Core未来发展趋势的浅层判断 .NET Core 用RPC方式进行高效的HTTP服务访问
猜您喜欢 Java过时了么?学Java还有发展前途吗? 途牛原创|浅谈API安全设计 各种流行的编程风格 你属于哪一种? 谈消息总线客户端的多线程实现 前端如何实现图片懒加载(lazyload) 提高用户体验