我们在构建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,结束!