X

Reading request body in ASP.NET Core

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:

  1. The code here may run to troubles when request body is streamed (technically the input stream is valid also for this kind of requests).
  2. In case of requests with big body the memory copy of stream can degrade system performance.
  3. 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.

Liked this post? Empower your friends by sharing it!
Categories: ASP.NET

View Comments (15)

Related Post