woensdag 20 juli 2016

ASP.NET Core Middleware pipeline 'Status code cannot be set, response has already started.'

ASP.NET Core has this beautiful concept of a pipeline of Middleware. See the introduction if you're not familiar with it.

In this tutorial you see you can always expect a 'next' to be available to invoke. But what if you have only one piece of middleware, or you are at the end of the pipeline? What is 'next' then?

The magic is in ApplicationBuilder, in the Build method:

1:      public RequestDelegate Build()  
2:      {  
3:        RequestDelegate app = context =>  
4:        {  
5:          context.Response.StatusCode = 404;  
6:          return Task.FromResult(0);  
7:        };  
8:        foreach (var component in _components.Reverse())  
9:        {  
10:          app = component(app);  
11:        }  
12:        return app;  
13:  }  

As you can see, a default RequestDelegate is appended to the end of the pipeline. It sets the StatusCode to 404 (meaning 'not found').

Now, where does the error in the title come from? It happens when you write to the httpContext.Response, and call next.Invoke(context). If none of the middleware components short-circuits the pipeline, eventually the 404 RequestDelegate above will be called. And that is where the problem starts: because you already started a response (leading to Response.HasStarted = true), you are not allowed to set the StatusCode anymore.

And what is the solution?


General rule of thumb: if you write to the Response in middleware X, end the pipeline (don't call next).

If you think about it this makes sense: X apparently knows the right response, so the request can be considered handled. Of course, the previous middleware components still get called on the way back through the pipeline. They also should not try to set the StatusCode.

Should you set the StatusCode yourself?


Not when the response is OK, because that is the default value of StatusCode. If your middleware decides that another response is appropriate, it should:
  • first set the StatusCode to an appropriate value (for example 201, Created)
  • then write to the Response
  • end the pipeline (don't call next)

Geen opmerkingen: