57

I'm trying to capture the raw request data for accountability and want to pull the request body content out of the Request object.

I've seen suggestions doing a Request.InputStream, but this method is not available on the Request object.

Any idea of how to get a string representation of the Request.Content body?

Watch variable

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Mark Kadlec
  • 8,036
  • 17
  • 60
  • 96

6 Answers6

95

In your comment on @Kenneth's answer you're saying that ReadAsStringAsync() is returning empty string.

That's because you (or something - like model binder) already read the content, so position of internal stream in Request.Content is on the end.

What you can do is this:

public static string GetRequestBody()
{
    var bodyStream = new StreamReader(HttpContext.Current.Request.InputStream);
    bodyStream.BaseStream.Seek(0, SeekOrigin.Begin);
    var bodyText = bodyStream.ReadToEnd();
    return bodyText;
}
Gh61
  • 9,222
  • 4
  • 28
  • 39
53

You can get the raw data by calling ReadAsStringAsAsync on the Request.Content property.

string result = await Request.Content.ReadAsStringAsync();

There are various overloads if you want it in a byte or in a stream. Since these are async-methods you need to make sure your controller is async:

public async Task<IHttpActionResult> GetSomething()
{
    var rawMessage = await Request.Content.ReadAsStringAsync();
    // ...
    return Ok();
}

EDIT: if you're receiving an empty string from this method, it means something else has already read it. When it does that, it leaves the pointer at the end. An alternative method of doing this is as follows:

public IHttpActionResult GetSomething()
{
    var reader = new StreamReader(Request.Body);
    reader.BaseStream.Seek(0, SeekOrigin.Begin); 
    var rawMessage = reader.ReadToEnd();

    return Ok();
}

In this case, your endpoint doesn't need to be async (unless you have other async-methods)

Ahmed El-Hansy
  • 160
  • 1
  • 15
Kenneth
  • 28,294
  • 6
  • 61
  • 84
  • 10
    Thanks Kenneth, but the rawMessage returns an empty string, have I done it right? I can see the Content contains data, why is the ReadAsStringAsync() returning an empty string? – Mark Kadlec Feb 24 '16 at 02:40
  • same issue as @MarkKadlec , this solution doesn't work – Dan Hastings Jul 12 '18 at 14:03
  • 1
    Check if you already read it before `ReadAsStringAsync`. It is a stream that this method will read to the end and leave the pointer there. – Miro J. Dec 19 '19 at 22:04
  • 2
    You cannot seek on the Request.Body stream. – zwcloud Jun 22 '20 at 05:34
  • 1
    If you are debugging and want to view the content as a string in the Watch window. Use this: `Request.Content.ReadAsStringAsync().Result` – David Klempfner Jan 11 '21 at 00:30
26

For other future users who do not want to make their controllers asynchronous, or cannot access the HttpContext, or are using dotnet core (this answer is the first I found on Google trying to do this), the following worked for me:

[HttpPut("{pathId}/{subPathId}"),
public IActionResult Put(int pathId, int subPathId, [FromBody] myViewModel viewModel)
{

    var body = new StreamReader(Request.Body);
    //The modelbinder has already read the stream and need to reset the stream index
    body.BaseStream.Seek(0, SeekOrigin.Begin); 
    var requestBody = body.ReadToEnd();
    //etc, we use this for an audit trail
}
C Bauer
  • 5,003
  • 4
  • 33
  • 62
  • 8
    Important: If your method has no arguments, then don't use `Seek` otherwise you'll get "NotSupportedException: Specified method is not supported. Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream.Seek" – JustAMartin Aug 22 '19 at 19:39
11

If you need to both get the raw content from the request, but also need to use a bound model version of it in the controller, you will likely get this exception.

NotSupportedException: Specified method is not supported. 

For example, your controller might look like this, leaving you wondering why the solution above doesn't work for you:

public async Task<IActionResult> Index(WebhookRequest request)
{
    using var reader = new StreamReader(HttpContext.Request.Body);

    // this won't fix your string empty problems
    // because exception will be thrown
    reader.BaseStream.Seek(0, SeekOrigin.Begin); 
    var body = await reader.ReadToEndAsync();

    // Do stuff
}

You'll need to take your model binding out of the method parameters, and manually bind yourself:

public async Task<IActionResult> Index()
{
    using var reader = new StreamReader(HttpContext.Request.Body);

    // You shouldn't need this line anymore.
    // reader.BaseStream.Seek(0, SeekOrigin.Begin);

    // You now have the body string raw
    var body = await reader.ReadToEndAsync();

    // As well as a bound model
    var request = JsonConvert.DeserializeObject<WebhookRequest>(body);
}

It's easy to forget this, and I've solved this issue before in the past, but just now had to relearn the solution. Hopefully my answer here will be a good reminder for myself...

Chris
  • 7,996
  • 11
  • 66
  • 98
  • This helped me on the right path. Note: I was using dotnet core and had to remove `[FromBody] myObject` from the header. This was reading the stream and putting the seek at the end. – benk Apr 20 '22 at 11:06
  • This should now be the correct/accepted answer. – sharpc Jul 15 '22 at 10:37
1

Here's this answer as an extension method:

using System.IO;
using System.Text;

namespace System.Web.Http
{
    public static class ApiControllerExtensions
    {
        public static string GetRequestBody(this ApiController controller)
        {
            using (var stream = new MemoryStream())
            {
                var context = (HttpContextBase)controller.Request.Properties["MS_HttpContext"];
                context.Request.InputStream.Seek(0, SeekOrigin.Begin);
                context.Request.InputStream.CopyTo(stream);
                var requestBody = Encoding.UTF8.GetString(stream.ToArray());
                return requestBody;
            }
        }
    }
}

Jesus is Lord
  • 14,971
  • 11
  • 66
  • 97
0

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>()
);
j4jada
  • 334
  • 1
  • 9