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.
Let’s start.
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.