基于 abp vNext 和 .NET Core 开发博客项目 - 异常处理和日志记录

在开始之前,我们实现一个之前的遗留问题,这个问题是有人在GitHub Issues(https://github.com/Meowv/Blog/issues/8)上提出来的,就是当我们对Swagger进行分组,实现IDocumentFilter接口添加了文档描述信息后,切换分组时会显示不属于当前分组的Tag。

经过研究和分析发现,是可以解决的,我不知道大家有没有更好的办法,我的实现方法请看:

//SwaggerDocumentFilter.cs
...public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context){var tags = new List<OpenApiTag>{...}#region 实现添加自定义描述时过滤不属于同一个分组的APIvar groupName = context.ApiDescriptions.FirstOrDefault().GroupName;var apis = context.ApiDescriptions.GetType().GetField("_source", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(context.ApiDescriptions) as IEnumerable<ApiDescription>;var controllers = apis.Where(x => x.GroupName != groupName).Select(x => ((ControllerActionDescriptor)x.ActionDescriptor).ControllerName).Distinct();swaggerDoc.Tags = tags.Where(x => !controllers.Contains(x.Name)).OrderBy(x => x.Name).ToList();#endregion}
...

根据调试代码发现,我们可以从context.ApiDescriptions获取到当前显示的是哪一个分组下的API。

然后使用GetType().GetField(string name, BindingFlags bindingAttr)获取到_source,当前项目的所有API,里面同时也包含了ABP默认生成的一些接口。

再将API中不属于当前分组的API筛选掉,用Select查询出所有的Controller名称进行去重。

因为OpenApiTag中的Name名称与Controller的Name是一致的,所以最后将包含controllers名称的tag查询出来取反,即可满足需求。


上一篇文章集成了GitHub,使用JWT的方式完成了身份认证和授权,保护了我们写的API接口。

本篇主要实现对项目中出现的异常仅需处理,当出现不可避免的错误时,或者未授权用户调用接口时,可以进行有效的监控和日志记录。

目前调用未授权接口,会直接返回一个状态码为401的错误页面,这样显得太不友好,我们还是用之前写的统一返回模型来告诉调用者,你是未授权的,调不了我的接口,上篇也有提到过,我们将用两种方式来解决。

方式一 :使用AddJwtBearer()扩展方法下面的options.Events事件机制。

//MeowvBlogHttpApiHostingModule.cs
...//应用程序提供的对象,用于处理承载引发的事件,身份验证处理程序options.Events = new JwtBearerEvents{OnChallenge = async context =>{// 跳过默认的处理逻辑,返回下面的模型数据context.HandleResponse();context.Response.ContentType = "application/json;charset=utf-8";context.Response.StatusCode = StatusCodes.Status200OK;var result = new ServiceResult();result.IsFailed("UnAuthorized");await context.Response.WriteAsync(result.ToJson());}};
...

在项目启动时,实例化了OnChallenge,如果用户调用未授权,将请求的状态码赋值为200,并返回模型数据。

如图所示,可以看到已经成功返回了一段比较友好的JSON数据。

{"Code": 1,"Message": "UnAuthorized","Success": false,"Timestamp": 1590226085318
}

方式二 :使用中间件的方式。

我们注释掉上面的代码,在.HttpApi.Hosting添加文件夹Middleware,新建一个中间件ExceptionHandlerMiddleware.cs

using Meowv.Blog.ToolKits.Base;
using Meowv.Blog.ToolKits.Extensions;
using Microsoft.AspNetCore.Http;
using System;
using System.Net;
using System.Threading.Tasks;namespace Meowv.Blog.HttpApi.Hosting.Middleware
{/// <summary>/// 异常处理中间件/// </summary>public class ExceptionHandlerMiddleware{private readonly RequestDelegate next;public ExceptionHandlerMiddleware(RequestDelegate next){this.next = next;}/// <summary>/// Invoke/// </summary>/// <param name="context"></param>/// <returns></returns>public async Task Invoke(HttpContext context){try{await next(context);}catch (Exception ex){await ExceptionHandlerAsync(context, ex.Message);}finally{var statusCode = context.Response.StatusCode;if (statusCode != StatusCodes.Status200OK){Enum.TryParse(typeof(HttpStatusCode), statusCode.ToString(), out object message);await ExceptionHandlerAsync(context, message.ToString());}}}/// <summary>/// 异常处理,返回JSON/// </summary>/// <param name="context"></param>/// <param name="message"></param>/// <returns></returns>private async Task ExceptionHandlerAsync(HttpContext context, string message){context.Response.ContentType = "application/json;charset=utf-8";var result = new ServiceResult();result.IsFailed(message);await context.Response.WriteAsync(result.ToJson());}}
}

RequestDelegate是一种请求委托类型,用来处理HTTP请求的函数,返回的是delegate,实现异步的Invoke方法。

这里我写了一个比较通用的方法,当出现异常时直接执行ExceptionHandlerAsync()方法,当没有异常发生时,在finally中判断当前请求状态,可能是200?404?401?等等,不管它是什么,反正不是200,获取到状态码枚举的Key值用来当作错误信息返回,最后也执行ExceptionHandlerAsync()方法,返回我们自定义的模型。

写好了中间件,然后在OnApplicationInitialization(...)中使用它。

        public override void OnApplicationInitialization(ApplicationInitializationContext context){...// 异常处理中间件app.UseMiddleware<ExceptionHandlerMiddleware>();...}

同样可以达到效果,相比之下他还支持状态非401的错误返回,比如我们访问一个不存在的页面:https://localhost:44388/aaa ,也可以友好的进行处理。

当然这两种方式可以共存,互不影响。

还有一种处理异常的方式,就是我们的过滤器Filter,abp已经默认为我们实现了全局的异常模块,详情可以看其文档:https://docs.abp.io/zh-Hans/abp/latest/Exception-Handling ,在这里,我准备移除abp提供的异常处理模块,自己实现一个。

先看一下目前的异常显示情况,我们在HelloWorldController中写一个异常接口。

//HelloWorldController.cs
...[HttpGet][Route("Exception")]public string Exception(){throw new NotImplementedException("这是一个未实现的异常接口");}
...

按理说,他应该会执行到我们写的ExceptionHandlerMiddleware中间件中去,但是被我们的Filter进行拦截了,现在我们移除默认的拦截器AbpExceptionFilter

还是在模块类MeowvBlogHttpApiHostingModuleConfigureServices()方法中。

Configure<MvcOptions>(options =>
{var filterMetadata = options.Filters.FirstOrDefault(x => x is ServiceFilterAttribute attribute && attribute.ServiceType.Equals(typeof(AbpExceptionFilter)));// 移除 AbpExceptionFilteroptions.Filters.Remove(filterMetadata);
});

options.Filters中找到AbpExceptionFilter,然后Remove掉,此时再看一下有异常的接口。

当我们注释掉我们的中间件时,他就会显示如下图这样。

这个页面有没有很熟悉的感觉?相信做过.net core开发的都遇到过吧。

ok,现在为止已经完美显示了。但到这里还远远不够,说好的自己实现Filter呢?我们现在实现Filter又有什么用呢?我们可以在Filter中可以做一些日志记录。

.HttpApi.Hosting层添加文件夹Filters,新建一个MeowvBlogExceptionFilter.cs的Filter,他需要实现我们的IExceptionFilter接口的OnExceptionAsync()方法即可。

//MeowvBlogExceptionFilter.cs
using Meowv.Blog.ToolKits.Helper;
using Microsoft.AspNetCore.Mvc.Filters;namespace Meowv.Blog.HttpApi.Hosting.Filters
{public class MeowvBlogExceptionFilter : IExceptionFilter{/// <summary>/// 异常处理/// </summary>/// <param name="context"></param>/// <returns></returns>public void OnException(ExceptionContext context){// 日志记录LoggerHelper.WriteToFile($"{context.HttpContext.Request.Path}|{context.Exception.Message}", context.Exception);}}
}

OnException(...)方法很简单,这里只做了记录日志的操作,剩下的交给我们中间件去处理吧。

注意,一定要在移除默认AbpExceptionFilter后,将我们自己实现的MeowvBlogExceptionFilter在模块类ConfigureServices()方法中注入到系统。

...Configure<MvcOptions>(options =>{...// 添加自己实现的 MeowvBlogExceptionFilteroptions.Filters.Add(typeof(MeowvBlogExceptionFilter));});
...

说到日志,就有很多种处理方式,请选择你熟悉的方式,我这里将使用log4net进行处理,仅供参考。

.ToolKits层添加log4net包,使用命令安装:Install-Package log4net,然后添加文件夹Helper,新建一个LoggerHelper.cs

//LoggerHelper.cs
using log4net;
using log4net.Config;
using log4net.Repository;
using System;
using System.IO;namespace Meowv.Blog.ToolKits.Helper
{public static class LoggerHelper{private static readonly ILoggerRepository Repository = LogManager.CreateRepository("NETCoreRepository");private static readonly ILog Log = LogManager.GetLogger(Repository.Name, "NETCorelog4net");static LoggerHelper(){XmlConfigurator.Configure(Repository, new FileInfo("log4net.config"));}/// <summary>/// 写日志/// </summary>/// <param name="message"></param>/// <param name="ex"></param>public static void WriteToFile(string message){Log.Info(message);}/// <summary>/// 写日志/// </summary>/// <param name="message"></param>/// <param name="ex"></param>public static void WriteToFile(string message, Exception ex){if (string.IsNullOrEmpty(message))message = ex.Message;Log.Error(message, ex);}}
}

.HttpApi.Hosting中添加log4net配置文件,log4net.config配置文件如下:

//log4net.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration><configSections><p name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/></configSections><log4net debug="false"><appender name="info" type="log4net.Appender.RollingFileAppender,log4net"><param name="File" value="log4net/info/" /><param name="AppendToFile" value="true" /><param name="MaxSizeRollBackups" value="-1"/><param name="MaximumFileSize" value="5MB"/><param name="RollingStyle" value="Composite" /><param name="DatePattern" value="yyyyMMdd\\HH&quot;.log&quot;" /><param name="StaticLogFileName" value="false" /><layout type="log4net.Layout.PatternLayout,log4net"><param name="ConversionPattern" value="%n
{&quot;system&quot;: &quot;Meowv.Blog&quot;,&quot;datetime&quot;: &quot;%d&quot;,&quot;description&quot;: &quot;%m&quot;,&quot;level&quot;: &quot;%p&quot;,&quot;info&quot;: &quot;%exception&quot;
}" /></layout><filter type="log4net.Filter.LevelRangeFilter"><levelMin value="INFO" /><levelMax value="INFO" /></filter></appender><appender name="error" type="log4net.Appender.RollingFileAppender,log4net"><param name="File" value="log4net/error/" /><param name="AppendToFile" value="true" /><param name="MaxSizeRollBackups" value="-1"/><param name="MaximumFileSize" value="5MB"/><param name="RollingStyle" value="Composite" /><param name="DatePattern" value="yyyyMMdd\\HH&quot;.log&quot;" /><param name="StaticLogFileName" value="false" /><layout type="log4net.Layout.PatternLayout,log4net"><param name="ConversionPattern" value="%n
{&quot;system&quot;: &quot;Meowv.Blog&quot;,&quot;datetime&quot;: &quot;%d&quot;,&quot;description&quot;: &quot;%m&quot;,&quot;level&quot;: &quot;%p&quot;,&quot;info&quot;: &quot;%exception&quot;
}" /></layout><filter type="log4net.Filter.LevelRangeFilter"><levelMin value="ERROR" /><levelMax value="ERROR" /></filter></appender><root><level value="ALL"></level><appender-ref ref="info"/><appender-ref ref="error"/></root></log4net></configuration>

此时再去调用 .../HelloWorld/Exception,将会得到日志文件,内容是以JSON格式进行存储的。

关于Filter的更多用法可以参考微软官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/controllers/filters

到这里,系统的异常处理和日志记录便完成了,你学会了吗?????????????

开源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309676.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

操作系统复习题+最终版

一、单选题 1、在单处理器系统中&#xff0c;如果同时存在9个进程&#xff0c;则处于就绪队列中的进程最多有&#xff08;8&#xff09;个。 A.1 B.9 C.10 D.8 分析&#xff1a;不可能出现这样一种情况&#xff0c;单处理器系统9个进程都处于就绪状态&#xff0c;但是8个处于…

[汇编语言]实验二:字的传送

实验二 实验内容: &#xff08;1&#xff09;: &#xff08;2&#xff09;:如果把上面的ax改成al呢&#xff1f; &#xff08;3&#xff09;: &#xff08;4&#xff09;: &#xff08;1&#xff09;实验代码: &#xff08;2&#xff09;实验代码: &#xff08;3&#xf…

Web页面适配移动端方案研究

源宝导读&#xff1a;由于我们ERP目前大都是在在PC上面运行&#xff0c;大家现在关注移动端比较少&#xff0c;谈到移动端适配时&#xff0c;可能都有些生疏也可能比较好奇。以前做过一些移动端的little项目&#xff0c;那么借助这次分享的机会&#xff0c;和大家一起讨论学习下…

计网复习题和知识点+最终版

分析题&#xff1a;出处 1.以太网交换机进行转发决策时使用的 PDU 地址是 _________。 &#xff08;A &#xff09; A&#xff0e;目的物理地址 B.目的 IP 地址 C.源物理地址 D.源 IP 地址 分析&#xff1a;以太网交换机属于数据链路的设备&#xff0c;用的是MAC地址/物理地…

[汇编语言]实验三:栈和栈段

实验三 实验内容: &#xff08;1&#xff09; &#xff08;2&#xff09; &#xff08;3&#xff09; &#xff08;4&#xff09; &#xff08;5&#xff09; &#xff08;6&#xff09;

概率论+往期考试卷

工程数学2018――2019学年 一、单项选择题 1&#xff0e;对掷一颗骰子的试验&#xff0c;将“出现偶数点”称为 &#xff08; D &#xff09; A、样本空间 B、必然事件 C、不可能事件 D、随机事件 2&#xff0e;若事件A、B 互不相容&#xff0c;则下列等式中未必成立的是 &…

.net core HttpClient 使用之消息管道解析(二)

一、前言前面分享了 .net core HttpClient 使用之掉坑解析&#xff08;一&#xff09;&#xff0c;今天来分享自定义消息处理HttpMessageHandler和PrimaryHttpMessageHandler 的使用场景和区别二、源代码阅读2.1 核心消息管道模型图先贴上一张核心MessageHandler 管道模型的流程…

[汇编语言]实验五:编写,调试具有多个段的程序

&#xff08;1&#xff09; 实验代码: assume cs:code, ds:data,ss:stackdata segmentdw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h data endsstack segmentdw 0,0,0,0,0,0,0,0 stack endscode segmentstart: mov ax,stackmov ss,axmov sp,16mov ax,datamov ds,axpus…

Fibonacci Sum HDU - 6755【2020 Multi-University Training Contest 1】斐波那契数列变形+二项式定理

【杭电多校2020】Distinct Sub-palindromes 分析&#xff1a; 题目&#xff1a; The Fibonacci numbers are defined as below: Given three integers N, C and K, calculate the following summation: Since the answer can be huge, output it modulo 1000000009 (1091…

List的扩容机制,你真的明白吗?

一&#xff1a;背景1. 讲故事在前一篇大内存排查中&#xff0c;我们看到了Dictionary正在做扩容操作&#xff0c;当时这个字典的count251w&#xff0c;你把字典玩的66飞起&#xff0c;其实都是底层为你负重前行&#xff0c;比如其中的扩容机制&#xff0c;当你遇到几百万甚至千…

[汇编语言]用[bx+idata]的方式进行数组的处理-字母大小写转换

第一个字符串"BaSiC"中的小写字母变成大写字母&#xff1b; 第二个字符串"iNfOrMaTiOn"中的大写字母变成小写字母&#xff1b; 方法一: 代码如下: assume cs:codesg,ds:datasgdatasg segment db BaSiC db iNfOrMaTiOn datasg endscodesg segment start…

中科大软件测试期末复习

前言 taozs老师画的重点&#xff0c;极其重要&#xff01;&#xff01;&#xff01; 25道多选 测试是为了证明这个系统没有bug。 错 测试四象限&#xff1a; 单元测试&#xff08;工具&#xff09;、组件测试&#xff08;开发人员做&#xff0c;dao层 controller层&#xf…

.NET 程序下锐浪报表 (Grid++ Report) 的绿色发布指南

在锐浪报表官方为 CSharp 编写的开发文档&#xff1a;“在C#与VB.NET中开始使用说明.txt” 中&#xff0c;关于发布项目是这么描述的&#xff1a;★发布你的项目&#xff0c;用VS.NET制作安装程序&#xff1a;1、先创建安装项目&#xff1a;在解决方案资源管理器的根节点上点右…

C++输出对齐(如何使输出对齐)

代码如下: #include <iostream> #include <iomanip> using namespace std;int main() {cout << setw(15)<<std::left<<"unsigned int" << setw(15) << std::left<<sizeof(unsigned) << endl;cout <<…

HDFS(一)

HDFS&#xff08;一&#xff09; 参考&#xff1a; http://hadoop.apache.org/docs/r1.0.4/cn/hdfs_design.html https://www.cnblogs.com/zsql/p/11587240.html Hadoop Distribute File System&#xff1a;Hadoop分布式文件系统&#xff0c;Hadoop核心组件之一&#xff0c;作为…

[开源] .Net orm FreeSql 1.5.0 最新版本(番号:好久不见)

废话开头这篇文章是我有史以来编辑最长时间的&#xff0c;历时 4小时&#xff01;&#xff01;&#xff01;原本我可以利用这 4小时编写一堆胶水代码&#xff0c;真心希望善良的您点个赞&#xff0c;谢谢了&#xff01;&#xff01;很久很久没有写文章了&#xff0c;上一次还是…

MapReduce简述

MapReduce 参考&#xff1a; https://www.cnblogs.com/lixiansheng/p/8942370.html https://baike.baidu.com/item/MapReduce/133425?fraladdin 概念 MapReduce是面向大数据并行处理的计算模型&#xff0c;用于大规模数据集的并行计算。它提供了一个庞大但设计精良的并行计算…

调试实战 —— dll 加载失败之全局变量初始化篇

前言 最近项目里总是遇到 dll 加载不上的问题&#xff0c;原因各种各样。今天先总结一个虽然不是项目中实际遇到的问题&#xff0c;但是却非常经典的问题。其它几种问题&#xff0c;后续慢慢总结。示例代码包含一个 exe 工程&#xff0c;两个 dll 工程。exe 会加载两个 dll 并调…