zoukankan      html  css  js  c++  java
  • .net core MVC 通过 Filters 过滤器拦截请求及响应内容

    前提:

      需要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());
            }
        }
    LogHelper

       步骤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();
        }
    IReadableBody
    /// <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);
                }
            }
        }
    HttpContextMiddleware

       步骤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)
            {
            }
        }
    ApiFilterAttribute

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

    [ApiFilter]
    [Route("api/[controller]/[Action]")]
    [ApiController]
    public class DemoController : ControllerBase
    {
    .......
    }

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

  • 相关阅读:
    360浏览器自动填写用户名和密码、下拉框解决办法
    实用JAVA工具类网站
    JQuery中ajaxSubmit,在ie或360兼容,提交后台不能获得参数
    java中形参的可变参数的定义(如String... args) .
    用sqldeveloper连接数据库
    ORA-12541:TNS:无监听程序问题
    小工具:生成半透明背景色的 CSS 代码,不影响子元素透明度
    JQuery使用小结
    ajaxSubmit 在ie9或360兼容中,form下是空的
    ajax 跨域请求数据 jsonp 示例
  • 原文地址:https://www.cnblogs.com/intotf/p/10191669.html
Copyright © 2011-2022 走看看