我想使用 aspnet core 示例提供的日志记录中间件。在我添加日志记录中间件之前,一切正常。添加日志记录中间件后,我收到错误消息。
添加中间件之前:
info: Microsoft.AspNetCore.Server.Kestrel[32]
Connection id "0HLJA6SH4AF9Q", Request id "0HLJA6SH4AF9Q:00000001": the application completed without reading the entire request body.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1] Request starting HTTP/1.1 POST https://localhost:5001/api/operations/deposit application/json 282
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Route matched with {action = "Post", controller = "Operations"}. Executing action API.Controllers.OperationsController.Post (API)
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method API.Controllers.OperationsController.Post (API) with arguments (API.Req.Deposit) - Validation state: Valid
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action method API.Controllers.OperationsController.Post (API), returned result Microsoft.AspNetCore.Mvc.ObjectResult in 4782.8378ms.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
Executing ObjectResult, writing value of type 'API.Res.Deposit'.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action API.Controllers.OperationsController.Post (API) in 4901.4518ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 4930.5295ms 200 application/json; charset=utf-8
在启动中添加中间件后.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseMiddleware<RequestResponseLoggingMiddleware>();
app.UseHttpsRedirection();
app.UseMvc();
}
将输出整个日志
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 POST http://localhost:5000/api/operations/deposit application/json 282
info: API.RequestResponseLoggingMiddleware[0]
http localhost:5000/api/operations/deposit
{
"merchant": "981001",
....,
"userName": "susan"
}
info: API.RequestResponseLoggingMiddleware[0]
Response
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 50.7335ms 307
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 POST https://localhost:5001/api/operations/deposit application/json 282
info: API.RequestResponseLoggingMiddleware[0]
https localhost:5001/api/operations/deposit
{
"merchant": "981001",
...,
"userName": "susan"
}
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Route matched with {action = "Post", controller = "Operations"}. Executing action API.Controllers.OperationsController.Post (API)
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
Executing ObjectResult, writing value of type 'Microsoft.AspNetCore.Mvc.SerializableError'.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action API.Controllers.OperationsController.Post (API) in 179.1267ms
info: API.RequestResponseLoggingMiddleware[0]
Response {"":["A non-empty request body is required."]}
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 213.3557ms 400 application/json; charset=utf-8
它发现了一个序列化问题,然后我尝试按如下方式设置SuppressModelStateInvalidFilter
:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.Configure<ApiBehaviorOptions>(
options => {options.SuppressModelStateInvalidFilter = true; });
}
日志显示更多信息,如下所示:
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Route matched with {action = "Post", controller = "Operations"}. Executing action API.Controllers.OperationsController.Post (API)
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method API.Controllers.OperationsController.Post (API) with arguments () - Validation state: Invalid
Object reference not set to an instance of an object.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action method API.Controllers.OperationsController.Post (API), returned result Microsoft.AspNetCore.Mvc.ObjectResult in 454.3309ms.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
Executing ObjectResult, writing value of type 'API.Res.Deposit'.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action API.Controllers.OperationsController.Post (API) in 640.913ms
info: API.RequestResponseLoggingMiddleware[0]
Response
{
"branch": null,
"holder": null,
"number": null,
"expire": "0001-01-01T00:00:00",
"error": 0,
"verifyText": null,
"transactionId": null
}
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 671.6427ms 200 application/json; charset=utf-8
一切都变为空。为什么请求上下文正文在中间件中丢失?
正如@PeterBons指出的那样,问题已解决。
我们需要设置request.EnableRewind();
,并在阅读内容后将正文位置设置为 0 request.Body.Position = 0;
.以下是我使用的代码:
private async Task<string> FormatRequest(HttpRequest request)
{
request.EnableRewind();
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
await request.Body.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
var bodyAsText = Encoding.UTF8.GetString(buffer);
request.Body.Position = 0;
return $"{bodyAsText}";
}
祝您编码愉快!
创建中间件类以截获所有请求和响应,而不会影响响应流。在启动时启用中间件类关联.cs
app.UseMiddleware<HttpRequestResponseLogger>();
实现一个中间件类来拦截请求和响应。 (可选)可以将这些日志存储在数据库中。我忽略了机密和不必要的标头值
public class HttpRequestResponseLogger
{
RequestDelegate next;
public HttpRequestResponseLogger(RequestDelegate next)
{
this.next = next;
}
//can not inject as a constructor parameter in Middleware because only Singleton services can be resolved
//by constructor injection in Middleware. Moved the dependency to the Invoke method
public async Task InvokeAsync(HttpContext context, IHttpLogRepository repoLogs)
{
HttpLog logEntry = new HttpLog();
await RequestLogger(context, logEntry);
await next.Invoke(context);
await ResponseLogger(context, logEntry);
//store log to database repository
repoLogs.SaveLog(logEntry);
}
// Handle web request values
public async Task RequestLogger(HttpContext context, HttpLog log)
{
string requestHeaders = string.Empty;
log.RequestedOn = DateTime.Now;
log.Method = context.Request.Method;
log.Path = context.Request.Path;
log.QueryString = context.Request.QueryString.ToString();
log.ContentType = context.Request.ContentType;
foreach (var headerDictionary in context.Request.Headers)
{
//ignore secrets and unnecessary header values
if (headerDictionary.Key != "Authorization" && headerDictionary.Key != "Connection" &&
headerDictionary.Key != "User-Agent" && headerDictionary.Key != "Postman-Token" &&
headerDictionary.Key != "Accept-Encoding")
{
requestHeaders += headerDictionary.Key + "=" + headerDictionary.Value + ", ";
}
}
if (requestHeaders != string.Empty)
log.Headers = requestHeaders;
//Request handling. Check if the Request is a POST call
if (context.Request.Method == "POST")
{
context.Request.EnableBuffering();
var body = await new StreamReader(context.Request.Body).ReadToEndAsync();
context.Request.Body.Position = 0;
log.Payload = body;
}
}
//handle response values
public async Task ResponseLogger(HttpContext context, HttpLog log)
{
using (Stream originalRequest = context.Response.Body)
{
try
{
using (var memStream = new MemoryStream())
{
context.Response.Body = memStream;
// All the Request processing as described above
// happens from here.
// Response handling starts from here
// set the pointer to the beginning of the
// memory stream to read
memStream.Position = 0;
// read the memory stream till the end
var response = await new StreamReader(memStream)
.ReadToEndAsync();
// write the response to the log object
log.Response = response;
log.ResponseCode = context.Response.StatusCode.ToString();
log.IsSuccessStatusCode = (
context.Response.StatusCode == 200 ||
context.Response.StatusCode == 201);
log.RespondedOn = DateTime.Now;
// since we have read till the end of the stream,
// reset it onto the first position
memStream.Position = 0;
// now copy the content of the temporary memory
// stream we have passed to the actual response body
// which will carry the response out.
await memStream.CopyToAsync(originalRequest);
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
// assign the response body to the actual context
context.Response.Body = originalRequest;
}
}
}