In this article, we discuss how to leverage HttpContext.Items to preserve data in a request with ASP.NET Core. We can take advantage of this property to improve the efficiency of our applications.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

What is HttpContext.Items?

HttpContext.Items is a key/value collection, otherwise known as a dictionary. Every component involved in processing an HTTP request can access the data it saves.

Unlike the lifetime of data saved in HttpContext.Session, the parameters saved in HttpContext.Items do not persist across multiple requests. They are discarded after one request is completed and HttpContext.Items begins a new collection when another request comes in.

This isolates the data and ensures it doesn’t interfere with other concurrent requests.

Enhancing Interaction and Data Sharing With HttpContext.Items

As with most web applications, API components often need to interact with each other to process HTTP requests accurately. For these components to effectively communicate, there should be a parameter passing or shared state.

Although appropriate in some solutions, explicit parameter passing or shared state comes with certain drawbacks and challenges. Some of these include concurrency issues, compromised data integrity, tight coupling, performance overhead, etc. 

To address these concerns, for scalable ASP.NET Core applications enabled for concurrent requests, we utilize a lightweight and more secure mechanism called HttpContext.Items. This property allows for middleware, filters, and controllers to share data without direct exchanges as they execute at different stages in a request lifecycle.

How to Use HttpContext.Items to Pass Data

We use HttpContext.Items to pass data across different stages of an HTTP request in ASP.NET Core when we store and retrieve data from the key/value collection. We can use any data type for the key. The key itself can point to a value of any type.

However, when we use a key of type string, we are at risk of a key collision. A better alternative is to employ an object as the item key:

public object ItemKey = new();
HttpContext.Items[ItemKey] = "A,B,C, and D"

By using the object key, this technique proves especially beneficial for shared middleware between applications.

Let us see a couple of examples that show us how we can utilize this property in an ASP.NET Core web API application. We are working with the Get() of the default WeatherForecastController class we get when we create a new ASP.NET Core web API project. 

Custom Middleware

First, we define a CustomMiddleware class:

public class CustomMiddleware
{
    private readonly RequestDelegate _next;
    public static readonly object MiddlewareObjectKey = new();

    public CustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        context.Items[MiddlewareObjectKey] = "Weather Forecast";
        await _next(context);
    }
}

public static class CustomMiddlewareExtension
{
    public static IApplicationBuilder UseCustomMiddleware(this IApplicationBuilder app)
    {
        return app.UseMiddleware<CustomMiddleware>();
    }
}

We instantiate a MiddlewareObjectKey object. In the Invoke() method, we set the value “Weather Forecast” in the HttpContext.Items dictionary using the MiddlewareObjectKey as the key.

The CustomMiddlewareExtension class defines an extension method to add the CustomMiddleware to the application’s request pipeline. 

In the Program.cs, we configure our middleware:

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.UseCustomMiddleware();

app.Run()

Custom Filter

Next, let’s add a new CustomFilter class. The class implements the IActionFilter interface:

public class CustomFilter : IActionFilter
{
    private readonly ILogger<CustomFilter> _logger;
    public static readonly object FilterObjectKey = new();

    public CustomFilter(ILogger<CustomFilter> logger)
    {
        _logger = logger;
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        context.HttpContext.Items[FilterObjectKey] = "Please wait...";

        _logger.LogInformation("{FV}", context.HttpContext.Items[FilterObjectKey].ToString());
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        context.HttpContext.Items[FilterObjectKey] = "The forecast is ready";

        _logger.LogInformation("{FV}", context.HttpContext.Items[FilterObjectKey].ToString());
    }
}

In our code, we inject the ILogger<CustomFilter> into the CustomFilter class via the constructor. This logger will log messages related to the filter’s execution on the console. In this case, it will be logging the value of the unique identifier, FilterObjectKey in the HttpContext.Items dictionary.

The value for the key is set twice, on the OnActionExecuting() and OnActionExecuted() methods. To complete this section, we register the service in Program.cs:

builder.Services.AddScoped<CustomFilter>();

HttpContext.Items In a Controller

Finally, we will be modifying the WeatherForecastController GET request action method:

[HttpGet(Name = "GetWeatherForecast")]
[ServiceFilter(typeof(CustomFilter))]
public IEnumerable<WeatherForecast> Get()
{
    HttpContext.Items.TryGetValue(CustomMiddleware.MiddlewareObjectKey, out var middlewareValue);
    _logger.LogInformation(
         "Middleware value: {MV}", middlewareValue?.ToString() ?? "Middleware value not set!");

    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
    .ToArray();
}

The ServiceFilter(typeof(CustomFilter)) attribute specifies that the CustomFilter we defined earlier will be applied to this method.

Inside the method, we use HttpContext.Items.TryGetValue()method to retrieve the value of our MiddlewareObjectKey from the HttpContext.Items dictionary. The next line logs the retrieved information using the logger injected into the class.

When our program is run and the GetWeatherForecast endpoint is called, we will see the values of our Items logged to the console.

Conclusion

We have seen a simple demonstration of using HttpContext.Items for storing and retrieving data within one request context. It is a suitable choice for temporary data exchange within the context of a single request. Unlike HttpContext.Session, HttpContext.Items automatically discards stored data upon completing the request.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!