ASP.NET MVC model binding is great, but occasionally you just need to access the body of a request as a raw string within a controller method.
ASP.NET MVC 5 (and probably some previous versions)
In the .NET Framework version of MVC, this is simple. You first reset the position of the stream, then re-read it:
Request.InputStream.Position = 0;
var rawRequestBody = new StreamReader(Request.InputStream).ReadToEnd();
The stream positon reset is needed because the MVC framework will already have read the stream content, in order to use it internally. Without it, you just read zero bytes and hence get an empty string.
ASP.NET Core 3+
In Core MVC, it seems things are significantly more complicated. First let us create this helper extension method as explained below.
public static async Task<string> GetRawBodyAsync(this HttpRequest request, Encoding encoding = null)
{
if (!request.Body.CanSeek)
{
// We only do this if the stream isn't *already* seekable,
// as EnableBuffering will create a new stream instance
// each time it's called
request.EnableBuffering();
}
request.Body.Position = 0;
var reader = new StreamReader(request.Body, encoding ?? Encoding.UTF8);
var body = await reader.ReadToEndAsync().ConfigureAwait(false);
request.Body.Position = 0;
return body;
}
Request.EnableBuffering()
just calls the internal BufferingHelper.EnableRewind() method, which replaces the request body with a seekable stream and correctly registers it for disposal/cleanup by the framework. We also need to call the asynchronous read method of StreamReader
and await
the result.
Now I can call this in a controller action to get the raw body string, while also still having access to any bound models and/or the Request.Form
collection.
[HttpPost]
public async Task<IActionResult> ExampleAction()
{
var rawRequestBody = await Request.GetRawBodyAsync();
// rawRequestBody will be *empty* here
// Other code here
return Ok();
}
The rawRequestBody
will be empty here. Calling request.EnableBuffering()
(either directly or via my extension method) within a controller action won't work if you also need model binding.
In this case, the MVC model binder will fully consume the body stream, so reading it after that just returns an empty string.
What we need to do here is call EnableBuffering()
before the request reaches the MVC pipeline, so that the body stream is still available after the model binder has read from it.
We can do this in a number of ways, all of which involve middleware. All of these solutions are called from within the Configure
method of the Startup
class (for older versions of .NET Core, e.g. .NET 3.1) or the Program class (for newer version such as .NET 6 & .NET 7)
Inline Middleware
This is the simplest solution, and may be best for you if you just want to enable this behaviour for all requests.
app.Use(next => context => {
context.Request.EnableBuffering();
return next(context);
});
Custom Middleware
It's arguably cleaner to encapsulate the behaviour in a custom middleware class, e.g:
public class EnableRequestBodyBufferingMiddleware
{
private readonly RequestDelegate _next;
public EnableRequestBodyBufferingMiddleware(RequestDelegate next) =>
_next = next;
public async Task InvokeAsync(HttpContext context)
{
context.Request.EnableBuffering();
await _next(context);
}
}
Then within Configure
:
app.UseMiddleware<EnableRequestBodyBufferingMiddleware>();
Conditional Middleware Application
This is the "best" solution (YMMV), in that the middleware can be applied only to actions which require it:
app.UseWhen(
ctx => ctx.Request.Path.StartsWithSegments("/home/withmodelbinding"),
ab => ab.UseMiddleware<EnableRequestBodyBufferingMiddleware>()
);