我們在構建WEBAPI項目時,通常需要構建一個全局的記錄API 請求和返回 的功能,在WEBAPI框架下 我們通過自定義一個DelegateHandler來實現這個功能,
在.NET CORE框架下已經不存在DelegateHandler管道了,我們需要通過Middleware管道來實現。具體實現如下:
定義LoggingMiddleware
public class GlobalApiLoggingMiddleware : IMiddleware { private readonly ILogger _logger; public GlobalApiLoggingMiddleware(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger("ApiLog"); }
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
//在這里我們來攔截請求,收集日志
await next.Invoke(context);
}
}
HttpContext的定義
可以看到里面的Request和Response對象 分別時 HttpRequest 和 HttpResponse,不再像在webapi框架下直接通過HttpRequestMessage、HttpResponseMessage來獲取請求報文和返回報文。
這里需要花費一些技巧。
獲取請求報文,
//reuqest支持buff,否則body只能讀取一次 context.Request.EnableBuffering(); //這里不要釋放stream,否則后續讀取request.body會報錯 var reader = new StreamReader(context.Request.Body, Encoding.UTF8); var requestStr = await reader.ReadToEndAsync();
//var requestStr = reader.ReadToEnd(); 升級.net core 3.1后 會報 Synchronous operations are disallowed. 錯誤
首先類似於webapi框架下獲取請求報文一樣,需要先設置request buffer,這樣request報文可以讀取多次
其次獲取報文的方式 是通過 stream獲取,這里stream不要釋放 不要釋放 不要釋放。重要的事情說三次。
獲取返回報文 更加復雜一點
Stream originalBody = context.Response.Body; try { using (var memStream = new MemoryStream()) { context.Response.Body = memStream; await next.Invoke(context); var request = context.Request; var log = new ApiLogEntity() { Appkey = request.GetAppkey(), ClientIp = request.GetClientRealIp(), HttpMethod = request.Method, Request = requestStr, RequestId = request.GetRequestId(), RequestUrl = request.Path.Value, QueryString = request.QueryString.Value, ServerIp = request.Host.Value, StatusCode = context.Response.StatusCode }; memStream.Position = 0; log.Response = await new StreamReader(memStream).ReadToEndAsync(); memStream.Position = 0; await memStream.CopyToAsync(originalBody); _logger.LogInformation(JsonConvert.SerializeObject(log)); } } finally { //重新給response.body賦值,用於返回 context.Response.Body = originalBody; }
這里HttpResponse的body是不允許讀取的!!所以這里的策略是 先給body賦值一個新的stream,
執行完action得到返回值后,可以讀取我們自己定義的stream拿到返回報文
最后把返回值stream copy給原來的body對象,並重新賦給context.Response.Body,這里客戶端可以正確返回了。
OK,結束!