Record Request Body in ASP.NET Core 3.0

on Monday, June 15, 2020

Application Insights is a great tool, but it doesn’t record the body of a request by default. This is for good reason, payloads can be large and can sometimes contain sensitive information. But … sometimes you just need to record them.

When you do a google search there’s a great StackOverflow post (View POST request body in Application Insights) which gives you a lot of hope that it can be setup easily. But, with all the advancements in ASP.NET Core 3.0, it’s not quiet as easy as that post makes it look.

Here’s the obstacles you may need to overcome:

  • In ASP.NET Core 3.0, the request’s Body is no longer available to be read after a particular point in the application pipeline. You will need to create a middleware component to “EnableBuffering”. (This was done for purposes of speed as the underlying layers of the stack were replaced with Span’s. There’s a new Request.BodyReader that works with the spans for high performance, but it’s also not available to be read after a particular point in the application pipeline.)
  • The ITelemetryInitializer runs after a request completes. This means that the request’s body is disposed of by the time the initializer runs and records the event. You will have to record the body somewhere after “EnableBuffering” is enabled and before the Action completes. Like inside of an IActionFilter.
  • You may not want to record the body of everything that flows through your website, so an ActionFilterAttribute can make it easy to select which action you would like to record.

So, here’s some code that can help accomplish that:

using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
namespace YourNamespace
{
public class EnableBufferingMiddleware
{
private readonly RequestDelegate _next;
public EnableBufferingMiddleware(
RequestDelegate next
)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var request = context.Request;
if (request.CanReadBody())
{
//var syncIOFeature = context.Features.Get<IHttpBodyControlFeature>();
//if (syncIOFeature != null)
//{
// syncIOFeature.AllowSynchronousIO = true;
//}
request.EnableBuffering();
}
await _next(context);
}
}
}
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace YourNamespace
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware<EnableBufferingMiddleware>();
// use that middleware before everything else
}
}
}
view raw Startup.cs hosted with ❤ by GitHub
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace YourNamespace
{
public class TrackConstants
{
public const string JsonBody = "JsonBody";
}
}
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;
namespace YourNamespace
{
public class TrackRequestBodyAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var request = context.HttpContext.Request;
request.Body.Position = 0;
using var stream = new StreamReader(request.Body, Encoding.UTF8, false, 1024, true);
var body = await stream.ReadToEndAsync();
request.Body.Position = 0;
context.HttpContext.Items.Add(TrackConstants.JsonBody, body);
await base.OnActionExecutionAsync(context, next);
}
}
}
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.AspNetCore.Http;
namespace YourNamespace
{
// https://stackoverflow.com/questions/42686363/view-post-request-body-in-application-insights
public class TrackRequestBodyInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor httpContextAccessor;
public TrackRequestBodyInitializer(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
if (telemetry is RequestTelemetry requestTelemetry)
{
var context = httpContextAccessor.HttpContext;
var request = context.Request;
if (request.CanReadBody())
{
const string jsonBody = TrackConstants.JsonBody;
if (!context.Items.ContainsKey(jsonBody))
{
return;
}
if (requestTelemetry.Properties.ContainsKey(jsonBody))
{
return;
}
var body = context.Items[jsonBody] as string;
requestTelemetry.Properties.Add(jsonBody, body);
}
}
}
}
}
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace YourNamespace
{
[ApiController]
[Route("[controller]")]
public class YourController : ControllerBase
{
private readonly ILogger<YourController> _logger;
public PullRequestUpdatedController(
ILogger<YourController> logger
)
{
_logger = logger;
}
[TrackRequestBody]
public async Task<IActionResult> PostAsync([FromBody] YourObjectModel model)
{
// do stuff
return Ok();
}
}
}

0 comments:

Post a Comment


Creative Commons License
This site uses Alex Gorbatchev's SyntaxHighlighter, and hosted by herdingcode.com's Jon Galloway.