Save Request And Response Headers In ASP.NET 5 Core

In a typical request-response cycle, the request and response headers contain a lot of additional information. This article talks about how to retrieve them and store them in a database. In this article, we’ll use PostgreSQL as the database and connect to it using dotConnect for PostgreSQL.

Pre-requisites

To be able to work with the code examples demonstrated in this article, you should have the following installed in your system:

  • Visual Studio 2019 Community Edition
  • PostgreSQL
  • dotConnect for PostgreSQL

You can download .NET Core from here.

You can download Visual Studio 2019 from here.

You can download PostgreSQL from here.

You can download a trial version of dotConnect for PostgreSQL from here.

Create a new ASP.NET Core 5.0 Project in Visual Studio 2019

Assuming that the necessary software has been installed on your computer, follow the steps outlined below to create a new ASP.NET Core Web API project.

  1. First off, open the Visual Studio 2019 IDE
  2. Next, click "Create a new project" once the IDE has loaded
  3. In the "Create a new project" screen, select “ASP.NET Core Web API” as the project template.
  4. Click the "Next" button
  5. Specify the project name and location - where it should be stored in your system
  6. Optionally, click the "Place solution and project in the same directory" checkbox.
  7. Next, click the "Create" button
  8. In the "Create a new ASP.NET Core Web Application" dialog window that is shown next, select "API" as the project template.
  9. In the “Additional Information” screen, .NET 5.0 is the framework version.
  10. You should disable the "Configure for HTTPS" and "Enable Docker Support" options by disabling the respective checkboxes.
  11. Since we'll not be using authentication in this example, specify authentication as "No Authentication".
  12. Finally, click on the "Create" button to finish the process.

This will create a new ASP.NET Core Web API project in Visual Studio.

Install NuGet Package(s)

To work with dotConnect for PostgreSQL in ASP.NET Core 5, you should install the following package into your project:

Devart.Data.PostgreSQL

You have two options for installing this package: either via the NuGet Package Manager or through the Package Manager Console Window by running the following command.

PM> Install-Package Devart.Data.PostgreSQL

Request and Response Headers in ASP.NET Core

Request headers provide more information about the requested resource or the client who is requesting it. You can take advantage of request headers to add extra information to the HTTP Request.

The response headers contain additional information about the response, such as its location and the server. It is an HTTP header that can only be used in an HTTP response and doesn't have to do anything with the content of the message. To provide more context to the response, you can use response headers like Age, Server, and Location.

Both Request headers and Response headers are represented as a collection of key-value pairs that can be passed back and forward between the client and the server.

Reading Request and Response Headers in ASP.NET Core

You can read request and response headers using the Request and Response properties of the HttpContext class. These properties are of HttpRequest and HttpResponse types respectively.

You can use the following code to retrieve the request headers in ASP.NET Cre:

List < string > AllRequestHeaders = new List < string > ();
var requestHeaders = httpContext.Request.Headers.Where(x => AllRequestHeaders.All(h => h != x.Key)).Select(x => x.Key);

The following code snippet can be used to retrieve all response headers:

List < string > AllResponseHeaders = new List < string > ();
var uniqueResponseHeaders = httpContext.Response.Headers.Where(x => AllResponseHeaders.All(h => h != x.Key)).Select(x => x.Key);

Building a Middleware to Read Request and Response Headers

A middleware is a software component that processes requests and responds. ASP.Net Core typically has a series of middleware components - these middleware components either process requests or pass them to the next component in the request processing pipeline.

Create a new class in your project's root and name it as RequestResponseHeaderMiddleware. Next, replace the default code with the following: 

public class RequestResponseHeadersMiddleware {
    private readonly RequestDelegate _next;
    public RequestResponseHeadersMiddleware(RequestDelegate next) {
        _next = next;
    }
    public async Task Invoke(HttpContext httpContext) {
        await _next.Invoke(httpContext);
    }
}

This is a minimalistic implementation of middleware in ASP.NET Core. However, it doesn’t do anything other than passing the control to the next component in the pipeline.

We need to add the logic of reading requests and response headers. We’ve already written the code in the previous section. Here’s the updated version of our middleware:

public class RequestResponseHeadersMiddleware {
    private readonly RequestDelegate _next;
    public readonly List < string > AllRequestHeaders = new List < string > ();
    public readonly List < string > AllResponseHeaders = new List < string > ();
    public RequestResponseHeadersMiddleware(RequestDelegate next) {
        _next = next;
    }
    public async Task Invoke(HttpContext httpContext) {
        var requestHeaders = httpContext.Request.Headers.Where(x => AllRequestHeaders.All(h => h != x.Key)).Select(x => x.Key);
        AllRequestHeaders.AddRange(requestHeaders);
        await this._next.Invoke(httpContext);
        var uniqueResponseHeaders = httpContext.Response.Headers.Where(x => AllResponseHeaders.All(h => h != x.Key)).Select(x => x.Key);
        AllResponseHeaders.AddRange(uniqueResponseHeaders);
        await _next.Invoke(httpContext);
    }
}

Create an Extension Method for the Middleware

We’ll now create an extension method for our middleware to be able to conveniently configure the middleware in the Configure method of the Startup class.

To do this, create a new class named RequestResponseHeadersMiddlewareExtensions in a file called RequestResponseHeadersMiddlewareExtensions.cs with the following code in there:

public static class RequestResponseHeadersMiddlewareExtensions {
    public static IApplicationBuilder UseRequestResponseHeadersMiddleware(this IApplicationBuilder builder) {
        return builder.UseMiddleware < RequestResponseHeadersMiddleware > ();
    }
}

Configure the Middleware in the Startup Class

You can write the following code in the Configure method of the Startup class to enable the custom middleware we have built thus far.

app.UseRequestResponseHeadersMiddleware();

Create the Model

Now create the following class in a file of the same name having an extension .cs. We'll use it to store request and response header values.

public class RequestResponseHeader {
    public string RequestHeader {
        get;
        set;
    }
    public string ResponseHeader {
        get;
        set;
    }
}

Create the DbManager Class

The DbManager class wraps all calls made to the database using dotConnect for Postgresql. It has just one method named Save to persist the request and response header values in the Postgresql database.

public class DbManager {
    private readonly string connectionString = "Some connection string...";
    public int Save(RequestResponseHeader requestResponseHeader) {
        try {
            using(PgSqlConnection pgSqlConnection = new PgSqlConnection(connectionString)) {
                using(PgSqlCommand cmd = new PgSqlCommand()) {
                    cmd.CommandText = "INSERT INTO
                    public.requestresponseheaders " +    
                    "(requestheader, responseheader) " + "VALUES (@requestheader, @responseheader)";
                    cmd.Connection = pgSqlConnection;
                    cmd.Parameters.AddWithValue("requestkey", requestResponseHeader.RequestHeader);
                    cmd.Parameters.AddWithValue("responsevalue", requestResponseHeader.ResponseHeader);
                    if (pgSqlConnection.State != System.Data.ConnectionState.Open) pgSqlConnection.Open();
                    return cmd.ExecuteNonQuery();
                }
            }
        } catch {
            throw;
        }
    }
}

Call the Save Method from the Middleware

We’ll now modify the middleware we’ve implemented to store the request and response headers in a PostgreSQL database using dotConnect for PostgreSQL.

You can use the following code snippet to call the Save method of the DbManager class from the middleware we implemented earlier.

for (int index = 0; index < AllRequestHeaders.Count; index++) {
    RequestResponseHeader requestResponseHeader = new
    RequestResponseHeader();
    requestResponseHeader.RequestHeader = AllRequestHeaders[index];
    requestResponseHeader.ResponseHeader = AllResponseHeaders[index];
    dbManager.Save(requestResponseHeader);
}

As you can see here, there are two string collections namely AllRequestHeaders and AllResponseHeaders that hold the request and response header values respectively. We saw how it is done earlier in the article.

In this code snippet, an instance of RequestResponseHeader is created, its properties initialized and then this instance is passed as a parameter to the Save method.

Here is the complete code of the middleware for your reference:

public class RequestResponseHeadersMiddleware {
    private readonly RequestDelegate _next;
    public readonly List < string > AllRequestHeaders = new List < string > ();
    public readonly List < string > AllResponseHeaders = new List < string > ();
    private readonly DbManager dbManager = new DbManager();
    public RequestResponseHeadersMiddleware(RequestDelegate next) {
        _next = next;
    }
    public async Task Invoke(HttpContext httpContext) {
        var requestHeaders = httpContext.Request.Headers.Where(x => AllRequestHeaders.All(h => h != x.Key)).Select(x => x.Key);
        AllRequestHeaders.AddRange(requestHeaders);
        await this._next.Invoke(httpContext);
        var uniqueResponseHeaders = httpContext.Response.Headers.Where(x => AllResponseHeaders.All(h => h != x.Key)).Select(x => x.Key);
        AllResponseHeaders.AddRange(uniqueResponseHeaders);
        await _next.Invoke(httpContext);
        for (int index = 0; index < AllRequestHeaders.Count; index++) {
            RequestResponseHeader requestResponseHeader = new
            RequestResponseHeader();
            requestResponseHeader.RequestHeader = AllRequestHeaders[index];
            requestResponseHeader.ResponseHeader = AllResponseHeaders[index];
            dbManager.Save(requestResponseHeader);
        }
    }
}

Summary

In ASP.NET Core, you can read request and response headers in your application easily. Request headers allow you to work with optional data that is stored as key-value pairs. Response headers include extra information about the response that allows for a more comprehensive context of the response to be sent to the client.