.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

posted @ 2018-12-28 17:05  皓月青峰  阅读(7126)  评论(0编辑  收藏  举报