Web API Core 3.0 Ведение журнала HttpRequest Body как объект пользовательского класса - PullRequest
1 голос
/ 03 февраля 2020

Я перехватываю HTTP-запрос, используя пользовательское промежуточное программное обеспечение, и хочу зарегистрировать запрос и ответ на файл. json с помощью NLog. Проблема возникает, когда я хочу десериализовать тело запроса / ответа из строки в объект пользовательского класса , а затем методы POST / PUT / DELETE не работают.

Почтальон при бросках метода POST: 500 Внутренняя ошибка сервера

Angular Приложение при бросках метода POST: Доступ к XMLHttpRequest в myValidEndpoint из источника 'http://localhost: 4200 ' заблокировано политикой 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; }
    }

1 Ответ

0 голосов
/ 05 февраля 2020

Проблема возникает, когда я хочу десериализовать тело запроса / ответа из строки в объект пользовательского класса, а затем методы POST / PUT / DELETE не работают.

Для вышеуказанной проблемы, как мы обсуждали в комментариях, что данные из тела запроса, отправленные потребителем API (или из тела ответа), не всегда могут быть десериализованы в объект List, поэтому это может вызвать ошибку в вашем пользовательском коде промежуточного программного обеспечения logi c.

И чтобы исправить это, вы можете построить дополнительный код логики c для проверки пути запроса и c для десериализации в другой объект пользовательского класса.

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