.net core MVC 經過 Filters 過濾器攔截請求及響應內容

前提:git

  須要nuget   Microsoft.Extensions.Logging.Log4Net.AspNetCore   2.2.6;github

        Swashbuckle.AspNetCore 我暫時用的是  4.01;json

描述:經過 Filters 攔截器獲取 Api 請求內容及響應內容,並記錄到日誌文件;api

 

     有文中代碼記錄接口每次請求及響應狀況以下圖:app

 

解決辦法:async

  步驟1  配置 Swagger 接口文檔ide

    對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管道增長 SwaggerUiui

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

相關文章
相關標籤/搜索