6

Okay so I've been wracking my brain and cannot for the life of me understand why the exact same piece of code works perfectly in .Net Core 2.2 but returns an empty string in .Net Core 3.0.

The piece of code I am running is this:

public static async Task<string> GetRequestBodyAsync(this HttpRequest request,
                                                     Encoding encoding = null)
{
    if (encoding == null) encoding = Encoding.UTF8;
    var body = "";

    request.EnableBuffering();
    if (request.ContentLength == null || !(request.ContentLength > 0) || !request.Body.CanSeek) return body;

    request.Body.Seek(0, SeekOrigin.Begin);
    using (var reader = new StreamReader(request.Body, encoding, true, 1024, true))
        body = await reader.ReadToEndAsync();

    request.Body.Position = 0;
    return body;
}

And I call this extension as such:

var bodyContent = await Request.GetRequestBodyAsync();
var body = new MemoryStream(Encoding.UTF8.GetBytes(bodyContent));

In .Net Core 2.2 I get the body of the sent payload exactly as I want it, but in .Net Core 3.0 I get an empty string.

I am using the extension in my startup to add Newtonsoft to my project for .Net Core 3.0, but if I remove that it still doesn't work.

Any ideas what I might've done wrong?

  • Seems likely that your function is returning on that if-statement in the middle. Try stepping through the code or adding some log statements throughout to determine which condition is failing and if the line `body = await reader.ReadToEndAsync();` is ever reached. – Klaycon Dec 04 '19 at 22:20
  • What are the values in the condition `if (request.ContentLength == null || !(request.ContentLength > 0) || !request.Body.CanSeek)` – Train Dec 04 '19 at 22:21
  • 1
    @Klaycon ```await reader.ReadToEndAsync();``` is reached and it gets to the final line but still returns ```"";``` – Lennart Hammarström Dec 04 '19 at 22:30
  • @Train ContentLength in this case is 243 CanSeek is true. – Lennart Hammarström Dec 04 '19 at 22:32
  • https://gunnarpeipman.com/aspnet-core-request-body/ – Train Dec 04 '19 at 22:37
  • @Train yeah, I've seen that one and it's basically what I'm doing already. Even if I copy that code directly I get the same result. – Lennart Hammarström Dec 04 '19 at 22:56
  • Looks like it is a known issue. See [this q&a](https://stackoverflow.com/questions/58737391/how-can-i-read-http-request-body-in-netcore-3-more-than-once) and this [github issue](https://github.com/aspnet/AspNetCore/issues/14396) – Simply Ged Dec 05 '19 at 00:02
  • Does this answer your question? [How can I read http request body in netcore 3 more than once?](https://stackoverflow.com/questions/58737391/how-can-i-read-http-request-body-in-netcore-3-more-than-once) – Simply Ged Dec 05 '19 at 00:03
  • @LennartHammarström there are some breaking changes from .NET Core 2.2 to 3.0 related to HttpRequest functionality. Please refer this: https://learn.microsoft.com/en-us/dotnet/core/compatibility/2.2-3.0. So, I would start with checking the rest of the code (like initialization of HttpRequest headers ..etc) – sam Dec 05 '19 at 15:19
  • I have exactly the same problem. Asp net core 3.1, request body length == 19, reader.ReadToEndAsync() returns empty string "". – sabiland Sep 04 '20 at 08:33

3 Answers3

14

Add this middleware in the startup class:

app.Use((context, next) =>
{
    context.Request.EnableBuffering();
    return next();
});
סטנלי גרונן
  • 2,917
  • 23
  • 46
  • 68
Gid
  • 141
  • 1
  • 5
  • 2
    Gid - Thanks for posting a possible solution! Can you give some background as to why this would help? If it solves the question it will be good to have some background. I'm assuming you are referring to the ConfigureServices method in the startup.cs file. – Ray K May 19 '20 at 21:28
  • 1
    Usually Request.Body does not support rewinding, so it can only be read once. This solution makes it possible to read Request.Body multiple times by setting the Request.Body.Position = 0; and using a stream reader. Example https://devblogs.microsoft.com/aspnet/re-reading-asp-net-core-request-bodies-with-enablebuffering/ – Lilja Mar 01 '21 at 10:30
  • 2
    The position of the `app.Use` call is relevant, if put at the end of the `Configure` method it doesn't work. I had to put it at the start of the `Configure` method to make it work. At least in .net 6. I also had to call `Request.Body.Position = 0` before reading the request body. – Dave de Jong Jun 25 '22 at 11:28
2

I actually figured it out, in the controller-method I read the body using [FromBody] so that the body was being read twice and not rewinded the first time.

public async Task<ActionResult<string>> PaymentRequestCallbackAsync(/*[FromBody] SecretModel model*/)
{
    var body = await Request.GetRequestBodyAsync();
    var stream = new MemoryStream(Encoding.UTF8.GetBytes(body));
    return body;
}

So remove the [FromBody] and it should work just fine.

1

Here is another solution which will help

write a middleware

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 configure this middleware

app.UseMiddleware<EnableRequestBodyBufferingMiddleware>();

Now you can easily ready the body without any problem

        Request.EnableBuffering();
        Request.Body.Seek(0, SeekOrigin.Begin);
        using (StreamReader stream = new StreamReader(Request.Body))
        {
            string body = await stream.ReadToEndAsync();
        }
Muhammad Junaid
  • 129
  • 1
  • 5