26

I'm using an ASP.NET Core Azure Web App to provide a RESTful API to a client, and the client doesn't handle chunking correctly.

Is it possible to completely turn off Transfer-Encoding: chunked, either at the controller level or in file web.config?

I'm returning a JsonResult somewhat like this:

[HttpPost]
[Produces("application/json")]
public IActionResult Post([FromBody] AuthRequest RequestData)
{
    AuthResult AuthResultData = new AuthResult();

    return Json(AuthResultData);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Iain Brown
  • 1,079
  • 3
  • 11
  • 23
  • 1
    Do you have the "Content-Length" header in the response? – Dmitry S. Jun 22 '16 at 14:11
  • I've added an example of what my action looks like. I don't add that header, as I have no idea how long the Json produced will be. Previously, ASP has added that header, but in recent servers it no longer does (possibly since the move to RC2) – Iain Brown Jun 22 '16 at 14:26
  • 1
    You can do this with response buffering: https://github.com/aspnet/BasicMiddleware/blob/dev/samples/ResponseBufferingSample/Startup.cs#L17 – Tratcher Jun 23 '16 at 16:58
  • 1
    Interestingly that works on a local server, but not in Azure. I'm guessing a difference between `Server: Kestrel` and `Server: Microsoft-IIS/8.0`?? – Iain Brown Jun 23 '16 at 18:15
  • 2
    Did you find a solution? I have the same problem which works on my local VS but doesn't work when I deploy it to Azure app services – Ehsan Mar 02 '17 at 18:27
  • Sadly I didn't, I had to fix the client instead – Iain Brown Mar 18 '17 at 10:53

4 Answers4

15

How to get rid of chunking in .NET Core 2.2:

The trick is to read the response body into your own MemoryStream, so you can get the length. Once you do that, you can set the content-length header, and IIS won't chunk it. I assume this would work for Azure too, but I haven't tested it.

Here's the middleware:

public class DeChunkerMiddleware
{
    private readonly RequestDelegate _next;

    public DeChunkerMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var originalBodyStream = context.Response.Body;
        using (var responseBody = new MemoryStream())
        {
            context.Response.Body = responseBody;
            long length = 0;
            context.Response.OnStarting(() =>
            {
                context.Response.Headers.ContentLength = length;
                return Task.CompletedTask;
            });
            await _next(context);

            // If you want to read the body, uncomment these lines.
            //context.Response.Body.Seek(0, SeekOrigin.Begin);
            //var body = await new StreamReader(context.Response.Body).ReadToEndAsync();

            length = context.Response.Body.Length;
            context.Response.Body.Seek(0, SeekOrigin.Begin);
            await responseBody.CopyToAsync(originalBodyStream);
        }
    }
}

Then add this in Startup:

app.UseMiddleware<DeChunkerMiddleware>();

It needs to be before app.UseMvC().

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Pharylon
  • 9,796
  • 3
  • 35
  • 59
  • Excellent fix for a bug in mono droid 1.2 https://github.com/mono/mono/issues/9511 & https://stackoverflow.com/questions/52402634/restsharp-error-getting-response-stream-readasync-receivefailure-value-canno – turkinator Mar 21 '19 at 21:55
  • Works well, though I assume there's inevitably some overhead in terms of memory and processing as you're in effect reading and copying the whole response. Just curious if you tried benchmarking this at all? I've implemented this but have added logic such that I only do this IF the incoming request had a "nochunk" header present, so clients can request an 'un-chunked' response, so I don't force all clients to live an unchunked existence. – GPW Sep 27 '19 at 12:51
  • 1
    Do not use OnStarting to set Content-Length, set it just before CopyToAsync. – Tratcher Aug 31 '20 at 15:29
4

In ASP.NET Core, this seems to work across hosts:

response.Headers["Content-Encoding"] = "identity";
response.Headers["Transfer-Encoding"] = "identity";

Indicates the identity function (i.e., no compression, nor modification). This token, except if explicitly specified, is always deemed acceptable.

This also works when you explicitly disable response buffering:

var bufferingFeature = httpContext.Features.Get<IHttpBufferingFeature>();
bufferingFeature?.DisableResponseBuffering();
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
George Tsiokos
  • 1,890
  • 21
  • 31
  • 1
    just a heads up that for me, with ASP.Net Core 2.2 neither of these solutions work. Adding those headers results in a sort of frozen response on my client end with an empty body. – billy jean Mar 08 '19 at 23:03
3

It works in .NET Core 2.0. Just set ContentLength before writing the results into the response body stream.

In the startup class:

app.Use(async (ctx, next) =>
{
    var stream = new xxxResultTranslatorStream(ctx.Response.Body);
    ctx.Response.Body = stream;

    await Run(ctx, next);

    stream.Translate(ctx);
    ctx.Response.Body = stream.Stream;
});

In xxxResultTranslatorStream:

ctx.Response.Headers.ContentLength = 40;
stream.Write(writeTargetByte, 0, writeTargetByte.Length);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
naz
  • 41
  • 2
0

I found that all my chunking problems went away if I just returned a FileStream from Get() and let ASP.NET deal with the rest.

Microsoft software tends to work best if you just give up control and trust them. It tends to work worst if you actually try to control the process.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user430788
  • 2,143
  • 2
  • 17
  • 17