This is an evolution of my previous post in which I described a method for inspecting the body of a request in a middleware method and passing it out so it could be logged later if required.

The below code reads in the body of a request and then if the response indicates failure (I’m also only interested in POST requests) the body is pushed as an additional property to the Serilog log.

public class ExceptionHandlingMiddleware
{
	private readonly RequestDelegate _next;

	public ExceptionHandlingMiddleware(RequestDelegate next)
	{
		_next = next;
	}

	public async Task Invoke(HttpContext httpContext)
	{
		string requestContent = new StreamReader(httpContext.Request.Body).ReadToEnd();
		string requestMethod = httpContext.Request.Method;
		httpContext.Request.Body.Position = 0;

		Stream responseBody = httpContext.Response.Body;

		try
		{
			using (MemoryStream memoryStream = new MemoryStream())
			{
				httpContext.Response.Body = memoryStream;
				
				await _next(httpContext);

				int responseStatus = httpContext.Response.StatusCode;

				if (!IsSuccessStatusCode(httpContext.Response.StatusCode) && requestMethod == "POST")
				{
					// Get response body if required
					//memoryStream.Position = 0;
					//string responseContent = new StreamReader(memoryStream).ReadToEnd();

					using (LogContext.PushProperty("RequestBody", requestContent))
					{
						Log.Warning("Invalid request ({@ResponseStatusCode}): {@RequestPath}", httpContext.Response.StatusCode, httpContext.Request.Path.Value);
					}
				}

				memoryStream.Position = 0;
				await memoryStream.CopyToAsync(responseBody);
			}
		}
		finally
		{
			httpContext.Response.Body = responseBody;
		}
	}

	public bool IsSuccessStatusCode(int statusCode)
	{
		if ((statusCode >= 200) && (statusCode <= 299))
		{
			return true;
		}
		else
		{
			return false;
		}
	}
}

// Extension method used to add the middleware to the HTTP request pipeline.
public static class ExceptionHandlingMiddlewareExtensions
{
	public static IApplicationBuilder UseExceptionHandlingMiddleware(this IApplicationBuilder builder)
	{
		return builder.UseMiddleware<ExceptionHandlingMiddleware>();
	}
}

To use the above middleware it needs to be enabled in the Configure method of your Startup.cs file. By enabling buffering on the request the request body is upgraded to a FileBufferingStream which means that it goes from being a forward only stream that doesn’t support seeking or reading the stream a second time to one that does.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
	app.Use(async (context, next) => {
		context.Request.EnableBuffering();
		await next();
	});

	app.UseMiddleware<ExceptionHandlingMiddleware>();
	
	// Your other code...
}

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *