隐藏

.net core MVC 通过 Filters 过滤器拦截请求及响应内容

发布:2021/7/15 17:45:54作者:管理员 来源:本站 浏览次数:1173



前提:


需要nuget   Microsoft.Extensions.Logging.Log4Net.AspNetCore   2.2.6;


Swashbuckle.AspNetCore 我暂时用的是  4.01;


描述:通过 Filters 拦截器获取 Api 请求内容及响应内容,并记录到日志文件;




  有文中代码记录接口每次请求及响应情况如下图:




解决办法:


步骤1  配置 Swagger 接口文档


对startup.cs   进行修改代码如下:


ConfigureServices 中增加Swagger 配置

复制代码


services.AddSwaggerGen(c =>

{

   c.SwaggerDoc("v1", new Info

   {

       Version = "v1",

       Title = "Filters 过滤器测试Api",

       Description = @"通过 IActionFilter, IAsyncResourceFilter 拦截器拦截请求及响应上下文并记录到log4日志"

   });

   c.IncludeXmlComments(this.GetType().Assembly.Location.Replace(".dll", ".xml"), true);  //是需要设置 XML 注释文件的完整路径

});


复制代码


对 Configure Http管道增加 SwaggerUi

复制代码


public void Configure(IApplicationBuilder app, IHostingEnvironment env)

{if (env.IsDevelopment())

   {

       app.UseDeveloperExceptionPage();

   }

   app.UseMvc();


   app.UseSwagger();

   app.UseSwaggerUI(o =>

   {

       o.SwaggerEndpoint("/swagger/v1/swagger.json", "Filters 过滤器测试Api");

   });

}


复制代码


步骤2 创建Log4net 日志帮助类  LogHelper.cs

复制代码


   /// <summary>

   /// 日志帮助类

   /// </summary>

   public static class LogHelper

   {

       /// <summary>

       /// 日志提供者

       /// </summary>

       private static ILogger logger;


       /// <summary>

       /// 静太方法构造函数

       /// </summary>

       static LogHelper()

       {

           logger = new LoggerFactory().AddConsole().AddDebug().AddLog4Net().CreateLogger("Logs");

       }


       /// <summary>

       /// 打印提示

       /// </summary>

       /// <param name="message">日志内容</param>

       public static void Info(object message)

       {

           logger.LogInformation(message?.ToString());

       }


       /// <summary>

       /// 打印错误

       /// </summary>

       /// <param name="message">日志内容</param>

       public static void Error(object message)

       {

           logger.LogError(message?.ToString());

       }


       /// <summary>

       /// 打印错误

       /// </summary>

       /// <param name="ex">异常信息</param>

       /// <param name="message">日志内容</param>

       public static void Error(Exception ex, string message)

       {

           logger.LogError(ex, message);

       }


       /// <summary>

       /// 调试信息打印

       /// </summary>

       /// <param name="message"></param>

       public static void Debug(object message)

       {

           logger.LogDebug(message?.ToString());

       }

   }


复制代码


步骤3 定义可读写的Http 上下文流接口  IReadableBody.cs 及 http 请求上下文中间件 HttpContextMiddleware.cs

复制代码


   /// <summary>

   /// 定义可读Body的接口

   /// </summary>

   public interface IReadableBody

   {

       /// <summary>

       /// 获取或设置是否可读

       /// </summary>

       bool IsRead { get; set; }


       /// <summary>

       /// 读取文本内容

       /// </summary>

       /// <returns></returns>

       Task<string> ReadAsStringAsync();

   }


复制代码

复制代码


/// <summary>

   /// Http 请求中间件

   /// </summary>

   public class HttpContextMiddleware

   {

       /// <summary>

       /// 处理HTTP请求

       /// </summary>

       private readonly RequestDelegate next;


       /// <summary>

       /// 构造 Http 请求中间件

       /// </summary>

       /// <param name="next"></param>

       public HttpContextMiddleware(RequestDelegate next)

       {

           this.next = next;

       }


       /// <summary>

       /// 执行响应流指向新对象

       /// </summary>

       /// <param name="context"></param>

       /// <returns></returns>

       public Task Invoke(HttpContext context)

       {

           context.Response.Body = new ReadableResponseBody(context.Response.Body);

           return this.next.Invoke(context);

       }


       /// <summary>

       /// 可读的Response Body

       /// </summary>

       private class ReadableResponseBody : MemoryStream, IReadableBody

       {

           /// <summary>

           /// 流内容

           /// </summary>

           private readonly Stream body;


           /// <summary>

           /// 获取或设置是否可读

           /// </summary>

           public bool IsRead { get; set; }


           /// <summary>

           /// 构造自定义流

           /// </summary>

           /// <param name="body"></param>

           public ReadableResponseBody(Stream body)

           {

               this.body = body;

           }


           /// <summary>

           /// 写入响应流

           /// </summary>

           /// <param name="buffer"></param>

           /// <param name="offset"></param>

           /// <param name="count"></param>

           public override void Write(byte[] buffer, int offset, int count)

           {

               this.body.Write(buffer, offset, count);

               if (this.IsRead)

               {

                   base.Write(buffer, offset, count);

               }

           }


           /// <summary>

           /// 写入响应流

           /// </summary>

           /// <param name="source"></param>

           public override void Write(ReadOnlySpan<byte> source)

           {

               this.body.Write(source);

               if (this.IsRead)

               {

                   base.Write(source);

               }

           }


           /// <summary>

           /// 刷新响应流

           /// </summary>

           public override void Flush()

           {

               this.body.Flush();


               if (this.IsRead)

               {

                   base.Flush();

               }

           }


           /// <summary>

           /// 读取响应内容

           /// </summary>

           /// <returns></returns>

           public Task<string> ReadAsStringAsync()

           {

               if (this.IsRead == false)

               {

                   throw new NotSupportedException();

               }


               this.Seek(0, SeekOrigin.Begin);

               using (var reader = new StreamReader(this))

               {

                   return reader.ReadToEndAsync();

               }

           }


           protected override void Dispose(bool disposing)

           {

               this.body.Dispose();

               base.Dispose(disposing);

           }

       }

   }


复制代码


步骤4  配置Http管通增加 Http请求上下文中件间


打开 Startup.cs  ,对管通 Configure 增加如下中间件代码:


app.UseMiddleware<HttpContextMiddleware>();


步骤5  增加Api 过滤器 ApiFilterAttribute

复制代码


   /// <summary>

   /// Api 过滤器,记录请求上下文及响应上下文

   /// </summary>

   public class ApiFilterAttribute : Attribute, IActionFilter, IAsyncResourceFilter

   {



       /// <summary>

       /// 请求Api 资源时

       /// </summary>

       /// <param name="context"></param>

       /// <param name="next"></param>

       /// <returns></returns>

       public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)

       {

           // 执行前

           try

           {

               await next.Invoke();

           }

           catch

           {

           }

           // 执行后

           await OnResourceExecutedAsync(context);

       }


       /// <summary>

       /// 记录Http请求上下文

       /// </summary>

       /// <param name="context"></param>

       /// <returns></returns>

       public async Task OnResourceExecutedAsync(ResourceExecutingContext context)

       {

           var log = new HttpContextMessage

           {

               RequestMethod = context.HttpContext.Request.Method,

               ResponseStatusCode = context.HttpContext.Response.StatusCode,

               RequestQurey = context.HttpContext.Request.QueryString.ToString(),

               RequestContextType = context.HttpContext.Request.ContentType,

               RequestHost = context.HttpContext.Request.Host.ToString(),

               RequestPath = context.HttpContext.Request.Path,

               RequestScheme = context.HttpContext.Request.Scheme,

               RequestLocalIp = (context.HttpContext.Request.HttpContext.Connection.LocalIpAddress.MapToIPv4().ToString() + ":" + context.HttpContext.Request.HttpContext.Connection.LocalPort),

               RequestRemoteIp = (context.HttpContext.Request.HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString() + ":" + context.HttpContext.Request.HttpContext.Connection.RemotePort)

           };


           //获取请求的Body

           //数据流倒带 context.HttpContext.Request.EnableRewind();

           if (context.HttpContext.Request.Body.CanSeek)

           {

               using (var requestSm = context.HttpContext.Request.Body)

               {

                   requestSm.Position = 0;

                   var reader = new StreamReader(requestSm, Encoding.UTF8);

                   log.RequestBody = reader.ReadToEnd();

               }

           }


           //将当前 http 响应Body 转换为 IReadableBody

           if (context.HttpContext.Response.Body is IReadableBody body)

           {

               if (body.IsRead)

               {

                   log.ResponseBody = await body.ReadAsStringAsync();

               }

           }

           if (string.IsNullOrEmpty(log.ResponseBody) == false && log.ResponseBody.Length > 200)

           {

               log.ResponseBody = log.ResponseBody.Substring(0, 200) + "......";

           }

           LogHelper.Debug(log);

       }


       /// <summary>

       /// Action 执行前

       /// </summary>

       /// <param name="context"></param>

       public void OnActionExecuting(ActionExecutingContext context)

       {

           //设置 Http请求响应内容设为可读

           if (context.HttpContext.Response.Body is IReadableBody responseBody)

           {

               responseBody.IsRead = true;

           }

       }


       /// <summary>

       /// Action 执行后

       /// </summary>

       /// <param name="context"></param>

       public void OnActionExecuted(ActionExecutedContext context)

       {

       }

   }


复制代码


步骤6  对需要记录请求上下文日志的接口加上特性  [ApiFilter]

复制代码


[ApiFilter]

[Route("api/[controller]/[Action]")]

[ApiController]

public class DemoController : ControllerBase

{

.......

}


复制代码


Demo 地址:https://github.com/intotf/netCore/tree/master/WebFilters