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

问题描述 投票:1回答:1

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

POST方法上的邮递员抛出:500内部服务器错误

POST方法上的角度应用程序引发:CORS策略已阻止从原点'http://localhost:4200'对'myValidEndpoint'处的XMLHttpRequest的访问:所请求的资源上没有'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; }
    }

c# asp.net-core asp.net-core-webapi nlog
1个回答
0
投票

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

对于上述问题,正如我们在评论中所讨论的那样,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
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.