Search code examples
asp.net-coreresponsemiddlewarepipeline

Response pipeline


I came across a difficulty while was working with Asp.net core 1.0 RTM. For example in case bellow we will see output result as "-Message_1--Message_5-":

 public class MessageMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IApplicationBuilder _app;

    public MessageMiddleware(RequestDelegate next, IApplicationBuilder app)
    {
        _next = next;
        _app = app;
    }

    public async Task Invoke(HttpContext context)
    {
        var started1 = context.Response.HasStarted;//false
        await context.Response.WriteAsync("-Message_1-");
        var test = true; // will hit this line
        var started2 = context.Response.HasStarted;//true
        await context.Response.WriteAsync("-Message_5-");

        await _next.Invoke(context);
    }
}

But in this case (header "Content-Type" was added) the result will be only "-Message_1-" and execution is really stopped:

  public class MessageMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IApplicationBuilder _app;

    public MessageMiddleware(RequestDelegate next, IApplicationBuilder app)
    {
        _next = next;
        _app = app;
    }

    public async Task Invoke(HttpContext context)
    {
        var started1 = context.Response.HasStarted;//false
        await context.Response.WriteAsync("-Message_1-");
        var started2 = context.Response.HasStarted;//true
        context.Response.ContentType = "text/html";
        var test = true; // will NOT hit this line
        var started3 = context.Response.HasStarted;//will NOT hit this line
        await context.Response.WriteAsync("-Message_5-"); //will NOT hit this line

        await _next.Invoke(context);
    }
}

I found only this remark in official documentation:

Avoid modifying HttpResponse after invoking next, one of the next components in the pipeline may have written to the response, causing it to be sent to the client.

and this question at SO: Why can't the HttpResponse be changed after 'next' call?

But it's not enough to understand interaction with props of HttpContext.Response during middleware pipeline and how this interection affects on final result - headers and body content of HttpResponse.

Could somebody explain general behaviour of processing response by ASP.NET core? For example, when response headers are send to client and how setting HttpContext.Response properties(headers, body content) affects on this? When pipeline inside(outside) middliware is terminated?

Thank you!


Solution

  • As a general rule, when the client makes a request to the server, it gets back a response. That response contains headers and a body. The headers contain many pieces of information about the response like the content type, encoding/compression used, cookies, etc. Here is an example of the headers sent back by the live.asp.net site as seen in the chrome developer tools:

    enter image description here

    The other part of the response is the body. It often contains html or json. Here is a screenshot of the body for the same response:

    enter image description here

    The easiest way to think about it is to think of these two being sent together to the client, first the headers then the body. So as a developer, your only opportunity to set any value on the response object that affects the headers is up to the point at which you start sending the body. One you begin sending the body of the response you can no longer change the headers because they are sent as the first part of the response just before the body begins sending.

    That's why @tseng said "Don't set headers after you have written something to the response stream".

    If a developer isn't familiar with http headers they might not realize that context.Response.ContentType = "text/html" is changing a header, but under the hood, that's exactly what it is doing. Likewise, setting a cookie changes a response header under the hood. In general, if you are changing some property of the response object you should ask yourself "will this change an http header?" and if the answer is "yes" then you need to do it before you make a call to Response.WriteAsync.