简体   繁体   English

Web API Core 3.0 将 HttpRequest 主体记录为自定义类对象

[英]Web API Core 3.0 Logging HttpRequest Body as custom class object

I am intercepting HTTP request using custom middleware and I want to Log request and response to .json file using NLog.我正在使用自定义中间件拦截 HTTP 请求,并且我想使用 NLog 将请求和响应记录到 .json 文件。 The problem occurs when I want to deserialize Request/Response body from string to custom class object and then POST/PUT/DELETE methods are not working.当我想将请求/响应正文从字符串反序列化为自定义类对象,然后 POST/PUT/DELETE 方法不起作用时,就会出现问题。

Postman on POST method throws: 500 Internal Server Error Postman on POST 方法抛出: 500 Internal Server Error

Angular app on POST method throws: Access to XMLHttpRequest at 'myValidEndpoint' from origin ' http://localhost:4200 ' has been blocked by CORS policy:No 'Access-Control-Allow-Origin' header is present on the requested resource. POST 方法上的 Angular 应用程序抛出:从源 ' http://localhost:4200 ' 访问 XMLHttpRequest 在 'myValidEndpoint' 已被 CORS 策略阻止:请求的资源上不存在 'Access-Control-Allow-Origin' 标头。

If I Log request/response body as a string every http method works fine.如果我将请求/响应正文记录为字符串,则每个 http 方法都可以正常工作。 Here's the code:这是代码:

Program.cs程序.cs

    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    } 

Startup.cs启动文件

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            LogManager.LoadConfiguration(String.Concat(Directory.GetCurrentDirectory(), "/nlog.config"));
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }
        public HttpConfiguration Config { get; }


        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IDrzavaService, DrzavaService>();
            services.AddTransient<IGradService, GradService>();
            services.AddEntityFrameworkNpgsql().AddDbContext<DrzavedbContext>(opt => opt.UseNpgsql(Configuration.GetConnectionString("DrzaveConnection")))
                .AddUnitOfWork<DrzavedbContext>();
            services.AddControllers().AddNewtonsoftJson();
            services.AddCors(options =>
            {
                options.AddDefaultPolicy(
                    builder =>
                    {
                        builder.WithOrigins("http://localhost:4200").AllowAnyHeader().AllowAnyMethod();
                    });
            });

        }


        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseMyMiddleware();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMiddleware<ExceptionMiddleware>();

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseCors();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });


        }
    }

nlog.config配置文件

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Trace"
      internalLogFile="./GlobalErrorHandlingLogs/internal_logs/internallog.txt">

  <extensions>
    <add assembly="NLog.Web.AspNetCore"/>
  </extensions>

  <targets>
    <target name="ExceptionMiddleware" xsi:type="File"
            fileName="./../../../GlobalErrorHandlingLogs/logs/${shortdate}_logfile.txt"
            layout="${longdate} ${level:uppercase=true} ${message} ${exception:format=tostring}"/>
    <target name="file" xsi:type="AutoFlushWrapper">
      <target name="RequestLoggingMiddleware" xsi:type="File"
            fileName="./../../../HttpRequestHandlingLogs/logs/${shortdate}_HttpLog.json">
        <layout xsi:type="JsonLayout" >
          <attribute name="level" layout="${level:upperCase=true}"/>
          <attribute name="eventProperties" encode="false">
            <layout type='JsonLayout' includeAllProperties="true"  maxRecursionLimit="5"/>
          </attribute>
        </layout>
      </target>
    </target>
  </targets>

  <rules>
    <logger name="*" minlevel="Error" writeTo="ExceptionMiddleware" />
    <logger name="*" minlevel="Info" maxlevel="Info" writeTo="RequestLoggingMiddleware" />
  </rules>
</nlog>

RequestLoggingMiddleware.cs请求日志中间件.cs

public class RequestLoggingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly Logger _logger;
        private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;
        Stopwatch _stopwatch;

        public RequestLoggingMiddleware(RequestDelegate next)
        {
            _next = next;
            _logger = LogManager.GetCurrentClassLogger();
            _recyclableMemoryStreamManager = new RecyclableMemoryStreamManager();
        }

        public async Task Invoke(HttpContext context)
        {
            await LogRequest(context);
            await LogResponse(context);
        }


        private async Task LogRequest(HttpContext context)
        {
            _stopwatch = Stopwatch.StartNew();
            context.Request.Headers.Add("X-Request-Guid", Guid.NewGuid().ToString());

            context.Request.EnableBuffering();
            await using var requestStream = _recyclableMemoryStreamManager.GetStream();
            await context.Request.Body.CopyToAsync(requestStream);
            string bodyString = ReadStreamInChunks(requestStream);
            List<BodyItem> body = JsonConvert.DeserializeObject<List<BodyItem>>(bodyString);

            RequestModel requestModel = new RequestModel()
            {
                requestStart = DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt"),
                method = context.Request.Method,
                schema = context.Request.Scheme,
                host = context.Request.Host.ToString(),
                path = context.Request.Path,
                requestBody = body,
                requestGuid = context.Request.Headers["X-Request-Guid"]
            };

            _logger.Info("{request}", requestModel);


            context.Request.Body.Position = 0;
        }

        private static string ReadStreamInChunks(Stream stream)
        {
            const int readChunkBufferLength = 4096;
            stream.Seek(0, SeekOrigin.Begin);
            using var textWriter = new StringWriter();
            using var reader = new StreamReader(stream);
            var readChunk = new char[readChunkBufferLength];
            int readChunkLength;
            do
            {
                readChunkLength = reader.ReadBlock(readChunk, 0, readChunkBufferLength);
                textWriter.Write(readChunk, 0, readChunkLength);
            } while (readChunkLength > 0);
            return textWriter.ToString();
        }

        private async Task LogResponse(HttpContext context)
        {
            context.Response.Headers.Add("X-Request-Guid", context.Request.Headers["X-Request-Guid"].ToString());
            var originalBodyStream = context.Response.Body;
            await using var responseBody = _recyclableMemoryStreamManager.GetStream();
            context.Response.Body = responseBody;
            await _next(context);
            context.Response.Body.Seek(0, SeekOrigin.Begin);
            string bodyStream = await new StreamReader(context.Response.Body).ReadToEndAsync();
            List<BodyItem> body = JsonConvert.DeserializeObject<List<BodyItem>>(bodyStream);

            context.Response.Body.Seek(0, SeekOrigin.Begin);


            ResponseModel responseModel = new ResponseModel()
            {
                requestEnd = DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt"),
                responseBody = body,
                responseGuid = context.Response.Headers["X-Request-Guid"],
                statusCode = context.Response.StatusCode.ToString(),
                requestDuration = _stopwatch.ElapsedMilliseconds
            };

            _logger.Info("{response}", JsonConvert.SerializeObject(responseModel));

            await responseBody.CopyToAsync(originalBodyStream);
        }
    }

    public static class MyMiddlewareExtensions
    {
        public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<RequestLoggingMiddleware>();
        }
    }

BodyItem.cs BodyItem.cs

[JsonObject]
    class BodyItem
    {
        [JsonProperty]
        public int id { get; set; }
        [JsonProperty]
        public string name { get; set; }
        [JsonProperty]
        public int? population { get; set; }
        [JsonProperty]
        public int? countryId{ get; set; }
    }

RequestModel.cs请求模型.cs

class RequestModel
    {
        public string requestStart { get; set; }
        public string method { get; set; }
        public string schema { get; set; }
        public string host { get; set; }
        public string path { get; set; }
        public List<BodyItem> requestBody { get; set; }
        public string requestGuid { get; set; }
    }

ResponseModel.cs响应模型.cs

class ResponseModel
    {
        public string requestEnd { get; set; }
        public List<BodyItem> responseBody { get; set; }
        public string responseGuid { get; set; }
        public string statusCode { get; set; }
        public float requestDuration { get; set; }
    }

The problem occurs when I want to deserialize Request/Response body from string to custom class object and then POST/PUT/DELETE methods are not working.当我想将请求/响应正文从字符串反序列化为自定义类对象,然后 POST/PUT/DELETE 方法不起作用时,就会出现问题。

For above issue, as we discussed in comments, the data from request body sent by API consumer (or from response body) may not always be able to deserialized to List object, so it might cause error within your custom middleware code logic.对于上述问题,正如我们在评论中所讨论的,来自 API 消费者(或来自响应正文)的请求正文中的数据可能并不总是能够反序列化为 List 对象,因此它可能会在您的自定义中间件代码逻辑中导致错误。

And to fix it, you can build additional code logic to check request path etc to to deserialize to different custom class object.为了修复它,您可以构建额外的代码逻辑来检查请求路径等以反序列化到不同的自定义类对象。

private async Task LogRequest(HttpContext context)
{
    // check context.Request.Path
    // or context.Request.Method etc 
    // perform different code logic to deserialize to different custom class object

    if (context.Request.Path.ToString() == "/api/{your_controller_name}/{action_name}")
    {
        // code logic here
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM