PASS Data Community Summit logo

PASS Data Community Summit 2023

Get all the latest announcements direct to your inbox

HTTP Response Headers in ASP.NET Core

ASP.NET Core has the flexibility to add HTTP response headers anywhere in the middleware pipeline. Dino Esposito explains what you need to know to handle the headers in ASP.NET Core.

By design, HTTP headers are additional and optional pieces of information in the form of name/value pairs that travel between the client and the server with the request and/or the response. HTTP headers belong in the initial part of the message—the header indeed. Adding headers to a request is slightly different than adding headers to a response. The reason lies in the way that the raw text of HTTP requests and responses is actually written by client components. In this article, I’ll focus on the ASP.NET Core middleware and the support it provides to flexibly add HTTP response headers nearly at any time of the request processing.

An Executive Summary of the ASP.NET Core Pipeline

Every request sent to an ASP.NET Core application runs through the pipeline of configured middleware before it is processed to generate a response. In this context, the term ‘middleware’ refers to a software component that exposes a well-defined contract and is assembled with similar components to form a chain—the ASP.NET Core request pipeline. Each middleware can be programmed to perform some work in two distinct steps: before and after the request gets processed. The figure below shows the overall diagram.

As you can see, in the entire lifecycle a request flows through any registered middleware components twice, before and after the response is generated. More precisely, a middleware is only called once by the runtime but, depending on its internal implementation, it can execute code twice in the lifecycle of the same request. When the request enters in the processing phase, middleware components are invoked in the order they have been registered in code. Each middleware has its first chance to execute code and, if it decides to yield to the next in the chain, then it will have a chance to inspect the response generated. The terminating middleware component runs at the end of the chain and sets the inversion point of the flow. Middleware which are waiting for the control to return—those which yielded to the next at some point—are invoked in the reverse order on the way back to the caller. A middleware component can be expressed as an anonymous or named method and it could even be wrapped up in a tailor-made class. Here’s the delegate that expressed the behavior expected from a middleware component.

Any middleware components can inspect the current status of the ongoing HTTP request and can even alter it. On the way back, the middleware can also inspect the response and perform changes. The way in which changes to the response are coded is critical and discussing that is the primary purpose of this article.

The delegate above summarizes what a middleware component is expected to do—processing the HTTP context and performing some tasks. However, the ASP.NET Core runtime environment also passes any middleware to the next middleware configured in the pipeline. Here’s the code for a sample middleware expressing the body via an anonymous method.

You configure the ASP.NET Core pipeline adding code to the Configure method of the application’s startup class.

Invoking the next middleware is optional, but it’s key to be aware of the consequences of not calling it. Omitting to call the next delegate will simply terminate the request pipeline abruptly and no subsequent middleware components will be invoked later. For a middleware component not passing control to the next is acceptable as long as the component is able to fully generate the response. Any middleware component is written as a single chunk of code split in two parts, before and after the instruction that yields to the next middleware.

A middleware component can do whatever the runtime conditions allow to do. As mentioned, it can inspect the current HTTP request, including HTTP headers and cookies, and can alter the state of the request. At the same time, it can start writing to the response output stream. Every middleware component is independent in what it does but must be able to play well with all other middleware components.

The terminating middleware is the piece of code that is ultimately responsible for generating some output for the request. If no preceding middleware takes the responsibility of generating the output and short-circuiting the request, the terminating middleware—the method app.Run—is invoked. When you turn on the MVC application model, it’s the UseMvc middleware that stops the requests and generates the output. The app.Run method, if defined, is only invoked if the URL is not recognized by MVC.

Adding HTTP Headers

Inspecting the current HTTP request is not particularly problematic but updating the response can be problematic. The response is made of three parts. The first line indicates the status code (for example, 200) followed by a description of the status, for example OK.

The second part is the list of HTTP response headers. The actual list depends on the web server and the application. For example, for ASP.NET Core it usually contains at least the following:

If you inspect the HTTP response while debugging an ASP.NET (Core) application, you can also find the X-SourceFiles header. That header is only generated for localhost requests and serves debugging purposes of Visual Studio and IIS Express. Finally, the response has a blank line and then the actual content. Each segment of the response has its own dedicated API. In ASP.NET Core, you set the status code via the StatusCode property on the Response object and add HTTP headers via the Headers collection. In addition, you access the actual content through a dedicated stream property named Body.

When it comes to writing the overall response output a fixed order exists between writing the headers and writing the body. All HTTP headers must be strictly written before a single byte of the body is written to the output stream. An exception will be thrown otherwise. This rather natural and established order of steps poses an issue for developers writing middleware components in ASP.NET Core.

HTTP Header-related Callbacks

The ASP.NET Core request pipeline is composed in the Configure method of the application’s startup class. The Configure method is invoked only once at the start of the application, but the built pipeline is kept in memory and every request runs through the linked components. Only the developer of the ASP.NET Core application knows the exact sequence of components. The middleware code just receives the memory address to the next components with no view of who came first and who’s coming next. This is no big deal if all the middleware components are under the responsibility of the team writing the application. But what if you’re a middleware author? In this case, your general-purpose component can be chained to a variety of other components in a variety of different orders. If your component needs to append a HTTP header or inspect the body it needs to play by the rules and behave like a good citizen to its unknown neighbors.

In ASP.NET Core, the Response object exposes the OnStarting method that gets automatically invoked just before the first byte is written to the response body. The method accepts a callback function with the specific purpose of performing whatever task must be performed before the body is written. Here’s one possible way to attach a callback function to the OnStarting method.

The callback can be expressed in two forms:

In the former case, the callback takes no input parameters and returns a Task object. In the latter, it also accepts an object representing some external state to process within the callback and still returns a Task object. The code snippet above implements the former case—no input parameters—and just registers a callback that will add a custom header at the last useful minute.

An OnStarting callback should always be registered in the first pass of a middleware component. Consider that the terminating middleware—whatever form it may take—will very likely be writing some content to the output stream. Therefore, registering a OnStarting callback in the second pass is definitely too late and will cause a HTTP 500 error.

Not strictly related to HTTP headers but still worth noting is that the Response object also features an OnCompleted callback that mostly exists for logging purposes and fires the provided function once the output has been successfully consumed by the client.

Inspecting the Body of the Response

Sometimes, especially if you’re writing low-level tools like loggers or monitoring APIs, you need to inspect the actual content being returned with the response before deciding which header to add or, more likely, which content to assign to it. In a similar scenario, an interesting technique you want to use is temporarily replacing the original Body stream—the one being monitored for adding headers—with an in-memory stream. Any middleware components, including the terminating middleware, will transparently write to the in-memory stream. Next, in the second pass of the middleware the content accumulated in memory will be read and the content to append as a header will be computed.

Let’s see how to add a sample HTTP header that replicates the content being sent in the body. The code below shows a full implementation of the Configure method of a startup class.

The first middleware just registers a callback to add a static content HTTP header and yields to the next component. The second middleware replaces the Body stream with an aptly created in-memory stream and ceases to the subsequent middleware. The flow proceeds until the termination of the pipeline is reached. When this happens, the terminating middleware writes a string to the output stream of the response object. This content, however, ends up saved to the underlying in-memory stream. On the second pass, the middleware which inserted the memory stream regains control and reads the current content of the stream to a local variable. Next it adds a new HTTP header named Body and sets it to the string saved in the local variable. Finally, it resets things to the natural form copying any content of the in-memory stream to the original stream and attaching it back to the Body property.

If you use a few breakpoints, you’ll see that just when the Body property is being assigned the original stream the control jumps back up to the OnStarting callback to add the sample Site header. The figure below shows the final output as reported by Fiddler.

Note that the first header you see is Body and verify that its content is exactly the same content of the response body. The Site header is the second added. This also makes sense because of the order of middleware components in the pipeline.

Summary

Adding custom headers to specific requests is not a very common task but, at the same time, it is a task that every developer needs to be familiar with for the obvious reason that, well, sooner or later everyone will face it. The different architecture of the request pipeline in classic ASP.NET and ASP.NET Core requires a significantly different approach even for a very basic task like appending a custom header. At the same time, the extended stream composition mechanism, already introduced in classic ASP.NET, makes it possible to build some buffering system on top of the output stream thus leading to write headers based on the content. You can find the full source of this example at http://bit.ly/2mgBfAO.