【问题标题】:Web API Core 3.0 Logging HttpRequest Body as custom class objectWeb API Core 3.0 将 HttpRequest 主体记录为自定义类对象
【发布时间】:2020-05-19 13:15:53
【问题描述】:

我正在使用自定义中间件拦截 HTTP 请求,并且我想使用 NLog 记录对 .json 文件的请求和响应。 当我想将请求/响应正文从字符串反序列化为自定义类对象,然后 POST/PUT/DELETE 方法不起作用时,就会出现问题。

POST 方法中的 Postman 抛出:500 Internal Server Error

POST 方法上的 Angular 应用程序抛出:从源“http://localhost:4200”访问 XMLHttpRequest 在“myValidEndpoint”已被 CORS 策略阻止:请求中不存在“Access-Control-Allow-Origin”标头资源。

如果我将请求/响应主体记录为字符串,则每个 http 方法都可以正常工作。 代码如下:

Program.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

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

[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

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

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; }
    }

【问题讨论】:

  • Postman on POST method throws: 500 Internal Server Error 您是否尝试过调试代码并检查哪些代码 sn-p 在您的自定义中间件中导致异常/错误
  • @Fei Han 是的。将请求/响应正文字符串反序列化为自定义类对象。 List&lt;BodyItem&gt; body = JsonConvert.DeserializeObject&lt;List&lt;BodyItem&gt;&gt;(bodyString); 转换时会出现问题,即使我没有在代码中使用此变量并使用字符串来表示正文。但如果我评论这一行,一切正常。
  • List&lt;BodyItem&gt; body = JsonConvert.DeserializeObject&lt;List&lt;BodyItem&gt;&gt;(bodyString); 如果请求体中的数据无法反序列化为List&lt;BodyItem&gt; 对象,则会导致错误。您确定 API 使用者发送的所有请求正文都应反序列化为 List&lt;BodyItem&gt; 对象吗?也许您可能需要执行不同的代码逻辑来反序列化为基于 context.Request.Path 的不同自定义类对象。
  • 谢谢您,先生,您的提示让我明白了问题所在。当我试图将 one 对象反序列化为 response 中的 list 对象时,出现了问题。这就是 GET 响应起作用的原因,因为它有更多对象要反序列化,但 POST 方法发送一个对象!现在我必须检查我的响应正文中有多少对象并相应地构建逻辑。

标签: c# asp.net-core asp.net-core-webapi nlog


【解决方案1】:

当我想将请求/响应正文从字符串反序列化为自定义类对象,然后 POST/PUT/DELETE 方法不起作用时,就会出现问题。

对于上述问题,正如我们在 cmets 中讨论的那样,API 使用者(或来自响应主体)发送的请求正文中的数据可能并不总是能够反序列化为 List 对象,因此可能会导致您的自定义中间件代码逻辑出错.

要修复它,您可以构建额外的代码逻辑来检查请求路径等以反序列化为不同的自定义类对象。

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
    }
}

【讨论】:

  • 再次感谢您,我是这样弄的:Object body; if (bodyString != string.Empty) { body = JsonConvert.DeserializeObject(bodyString); } else { body = new JObject(); }
  • 您可以根据自己的实际场景和需求构建逻辑。
猜你喜欢
  • 1970-01-01
  • 2020-09-01
  • 2022-01-18
  • 1970-01-01
  • 2018-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多