Most of requests to controller actions of our ASP.NET Core applications send some simple parameters or some serialized model objects. But this is not always the case. There are also those requests we don’t want to map to models but we need the request body to process, save or use it some other ways. This blog post shows how to read request body in ASP.NET Core controller action.
Let’s start with simple case when we need request body only once. It is given us as a stream that is easy to read like shown in following code example.
public IActionResult SomeAction()
{
using (var reader = new StreamReader(Request.Body))
{
var body = reader.ReadToEnd();
// Do something
}
return View();
}
SomeAction() and other code int his post expect the body to be string. In case of other formats it’s possible to read data out from stream by using stream methods.
Using input stream more than once
But things get tricky if we need to read input stream more than once. I mean here more than one call to methods that operate on stream. Simple code to emulate and demonstrate the problem is here.
public IActionResult SomeAction()
{
using (var reader = new StreamReader(Request.Body))
{
var body = reader.ReadToEnd();
// Do something
}
// More code
using (var reader = new StreamReader(Request.Body))
{
var body = reader.ReadToEnd();
// Do something else
}
return View();
}
When stream is read second time then variable called body is empty string first reading left stream pointer to last position. Let’s try to move it back to position zero.
public IActionResult SomeAction()
{
using (var reader = new StreamReader(Request.Body))
{
var body = reader.ReadToEnd();
// Do something
}
// More code
Request.Body.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(Request.Body))
{
var body = reader.ReadToEnd();
// Do something else
}
return View();
}
So far, so good, but when we run the code the troubles start. It turns out that request body is stream of type FrameRequestStream and it doesn’t support seeking. So, what we can do if we need to read request body more than once?
Using copy of stream
If we really need a stream we can use MemoryStream and copy request body there.
public IActionResult SomeAction()
{
using (var mem = new MemoryStream())
using (var reader = new StreamReader(mem))
{
Request.Body.CopyTo(mem);
var body = reader.ReadToEnd();
// Do something
mem.Seek(0, SeekOrigin.Begin);
body = reader.ReadToEnd();
}
// More code
return View();
}
Here is also something to be aware about – reader will close memory stream when it is disposed so all work must be done while reader is active. It is classic problem with many API-s actually – they close or dispose the stream when they are done and they don’t consider the possibility that stream will be needed later again.
Possible drawbacks
I will point out here some situations where we cannot use the code above:
- The code here may run to troubles when request body is streamed (technically the input stream is valid also for this kind of requests).
- In case of requests with big body the memory copy of stream can degrade system performance.
- As much as possible try to keep the number of request body copies as minimal as possible.
If request body is just some XML or JSON we don’t want to map to some model the code given here will work smooth.
Using EnableRewind()
Update. Like my dear readers pointed out then from ASP.NET Core 2.0 there is EnableRewind() method available in Microsoft.AspNetCore.Http.Internal namespace that allows us to seek with request body stream. With this we can enable seeking in request body this way,
public IActionResult SomeAction()
{
Request.EnableRewind();
using (var reader = new StreamReader(Request.Body))
{
var body = reader.ReadToEnd();
// Do something
Request.Body.Seek(0, SeekOrigin.Begin);
body = reader.ReadToEnd();
}
// More code
return View("Index");
}
It’s also possible to write middleware action to enable rewind for all requests but I consider it as a special case.
EnableRewing() allows us to define buffer limit and buffer threshold to control how big can be the memory buffer and from what point it starts using temporary disk space to save memory.
Wrapping up
Reading request body in controller action of ASP.NET Core web application is easy. There are some unexpected moments like not being able to seek in input stream but it is easy to get around these moments. Still we have to be careful and consider in advance if we face possible drawbacks like requests with big body. When we stay in safe waters we can use the simple code given in this blog post. We can always use EnableRewind() method to have more advanced and powerful request body that supports seek operation.
View Comments (15)
You can also call context.EnableRewind() to allow reading the body more than once.
Or you could just use the [FromBody] attribute in the method signature
One particular example of using such approach is reading binary data (file content) from body.
TIP: There's also an "EnableRewind" extension method on HttpContext.Request for 2.0:
https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.internal.bufferinghelper.enablerewind?view=aspnetcore-2.0#Microsoft_AspNetCore_Http_Internal_BufferingHelper_EnableRewind_Microsoft_AspNetCore_Http_HttpRequest_System_Int32_System_Nullable_System_Int64__
https://www.nuget.org/packages/Microsoft.AspNetCore.Http.Extensions/
Thanks guys for pointing out EnableRewind() method. I made update to this post.
I have tried it,
And all the [frombody] inputs of my http methood functions of the post requests became null.
After disabling the middleware they are not null.
Any idea?
you need to add request.Body.Position = 0; before returning the request
Is there a way to do this if you have 2 bits of code that both use a StreamReader. Even using Request.EnabledRewind causes a ObjectDisposedException
You can read request body to some byte array and use it this way without multiple readers.
I have modified the request, and with debugging I have seen that the modified body looks 100% ready to model bind.
Yet it just will not bind to the controller.
Thanks for the example and the article. I've seen some examples where EnableRewind() was indiscriminately applied for *all* requests. I think it's good to point out, as you have, the pitfalls of doing so and that this behaviour should only be enabled on per-need basis.
I agree with you, Igor. EnableRewind() is not the only feature we can enable for all requests and the more features we enable the more we go back to previous ASP.NET MVC era. With ASP.NET Core we should keep our requests as small as possible and use only what we actually need. This is how the whole framework is designed and it is about excellent performance of web applications.
2.1+ has a new preferred way to read the body:
https://devblogs.microsoft.com/aspnet/re-reading-asp-net-core-request-bodies-with-enablebuffering/
As of .NET Core 3.0 preview 6, Microsoft.AspNetCore.Http.Internal is not public. So EnableRewind will not work.
Great blog post, however for .NET Core 3.0 it seems there's a new way, namely EnableBuffering: https://devblogs.microsoft.com/aspnet/re-reading-asp-net-core-request-bodies-with-enablebuffering/