Using ASP.NET Core's middleware to modify response body

Hey, where's my content?

 August 25, 2016


Note: the issue referenced in this post has been fixed and will get released with ASP.NET Core 1.1.0.

While creating a small piece of middleware to modify the response body's content, I had trouble getting anything to appear in my browser. I would get a 200 response-code, but no content (i.e. Content-Length: 0). Here is an example that creates a similar setup:

Startup.cs

public class Startup
{
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.Use(async (context, next) =>
        {
            var newContent = string.Empty;

            using (var newBody = new MemoryStream())
            {
                // We set the response body to our stream so we can read after the chain of middlewares have been called.
                context.Response.Body = newBody;

                await next();

                // Reset the body so nothing from the latter middlewares goes to the output.
                context.Response.Body = new MemoryStream();

                newBody.Seek(0, SeekOrigin.Begin);

                // newContent will be `Hello`.
                newContent = new StreamReader(newBody).ReadToEnd();

                newContent += ", World!";

                // Send our modified content to the response body.
                await context.Response.WriteAsync(newContent);
            }
        });

        app.Map("", configuration =>
        {
            configuration.Use(async (context, next) =>
            {
                await context.Response.WriteAsync("Hello");
            });
        });
    }
}

Initial response

curl -i http://localhost:54122/
HTTP/1.1 200 OK
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcd2lsbGlhbS1ib2dhLWl2XERvY3VtZW50c1xwcm9qZWN0c1wxMGstYXBhcnRcc3JjXDEwa0FwYXJ0?=
X-Powered-By: ASP.NET
Date: Thu, 25 Aug 2016 22:42:51 GMT
Content-Length: 0

Turns out this is a known issue and has an easy workaround which involves storing the original response stream and setting back to it after the middlewares are called:

Updated Startup.cs

public class Startup
{
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.Use(async (context, next) =>
        {
            var newContent = string.Empty;

            // Store the "pre-modified" response stream.
            var existingBody = context.Response.Body;

            using (var newBody = new MemoryStream())
            {
                // Previous code removed for brevity.

                await next();

                // Set the stream back to the original.
                context.Response.Body = existingBody;

                newBody.Seek(0, SeekOrigin.Begin);

                // Previous code removed for brevity.
            }
        });

        // Previous code removed for brevity.
    }
}

New response

curl -i http://localhost:54122/
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcd2lsbGlhbS1ib2dhLWl2XERvY3VtZW50c1xwcm9qZWN0c1wxMGstYXBhcnRcc3JjXDEwa0FwYXJ0?=
X-Powered-By: ASP.NET
Date: Thu, 25 Aug 2016 22:49:59 GMT

Hello, World!