Introduction
Today we’re going to look at how to add ASP.NET Correlation ID to your ASP.NET APIs.
Now that a lot of companies are creating distributed systems, correlation IDs are getting more important when you want to be able to trace a transaction through multiple services.
Here’s what we’re going to try to achieve:
- We want to use the Correlation ID sent in the request from the client (in the X-Correlation-Id header), if there is one
- If there’s no Correlation ID in the request, we want to generate one and return it in the response (in the X-Correlation-Id header)
- We want to have access to this Correlation ID for the duration of the request in case we want to do any custom logging e.g. using
ILogger
- We don’t want to have to add code to all the ASP.NET controllers individually so we’ll use middleware or similar
TL;DR
See instructions here to add the NuGet package and get started.
ASP.NET Correlation ID generator
First of all, we need a CorrelationIdGenerator
service which can generate or save an ASP.NET Correlation ID (depending on whether the client sends it in the request or not). Here we have a simple class withGet()
and Set()
methods to get and set the _correlationId
field.
namespace AspNet.CorrelationIdGenerator;
public class CorrelationIdGenerator : ICorrelationIdGenerator
{
private string _correlationId = Guid.NewGuid().ToString();
public string Get() => _correlationId;
public void Set(string correlationId) => _correlationId = correlationId;
}
Here is the ICorrelationIdGenerator
interface:
namespace AspNet.CorrelationIdGenerator;
public interface ICorrelationIdGenerator
{
string Get();
void Set(string correlationId);
}
In the next step, we’ll register this in our IoC container.
Register the ASP.NET Correlation ID Generator
As we want to access the ASP.NET Correlation ID for the lifetime of the request, this means we can’t register the CorrelationIdGenerator
service as a singleton and we need to register it as a scoped service instead. To do this, I’ve created a simple IServiceCollection
extension method:
using Microsoft.Extensions.DependencyInjection;
namespace AspNet.CorrelationIdGenerator.ServiceCollectionExtensions;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddCorrelationIdGenerator(this IServiceCollection services)
{
services.AddScoped<ICorrelationIdGenerator, CorrelationIdGenerator>();
return services;
}
}
You can then add to your start up. In this case, I’m using the new ASP.NET 6 Minimal Hosting model but you can also use this with the old versions of .NET and .NET Core by adding services.AddCorrelationIdGenerator()
to the ConfigureServices()
method in Startup.cs
. See line 9 below for how I’ve done it using the ASP.NET Minimal Hosting model in Program.cs
:
using AspNet.CorrelationIdGenerator.ApplicationBuilderExtensions;
using AspNet.CorrelationIdGenerator.ServiceCollectionExtensions;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddCorrelationIdGenerator();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
The next step is to add some middleware so that we can pull out the X-Correlation-Id
header from requests (or create a new Correlation ID), return it in the response and use it throughout the request for custom logging using ILogger
.
Create ASP.NET Correlation ID Middleware
ASP.NET has this great feature where you can add middleware which will get executed whenever a request comes in. You can add as many middlewares as you want so you may have this one and a custom exception handler middleware for example.
Our middleware looks like this:
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
namespace AspNet.CorrelationIdGenerator;
public class CorrelationIdMiddleware
{
private readonly RequestDelegate _next;
private const string _correlationIdHeader = "X-Correlation-Id";
public CorrelationIdMiddleware(RequestDelegate next) => _next = next;
public async Task Invoke(HttpContext context, ICorrelationIdGenerator correlationIdGenerator)
{
var correlationId = GetCorrelationId(context, correlationIdGenerator);
AddCorrelationIdHeaderToResponse(context, correlationId);
await _next(context);
}
private static StringValues GetCorrelationId(HttpContext context, ICorrelationIdGenerator correlationIdGenerator)
{
if (context.Request.Headers.TryGetValue(_correlationIdHeader, out var correlationId))
{
correlationIdGenerator.Set(correlationId);
return correlationId;
}
else
{
return correlationIdGenerator.Get();
}
}
private static void AddCorrelationIdHeaderToResponse(HttpContext context, StringValues correlationId)
=> context.Response.OnStarting(() =>
{
context.Response.Headers.Add(_correlationIdHeader, new[] { correlationId.ToString() });
return Task.CompletedTask;
});
}
For middleware to work, you need to have the constructor take a RequestDelegate
parameter and then have an Invoke()
method which takes an HttpContext
which is provided by the request pipeline.
Note that we also need to inject the ICorrelationIdGenerator
but this can’t be done using constructor injection as middleware is registered as a singleton but we need to register ICorrelationIdGenerator
as scoped as we need a new instance for each request. So, in this case, we can add it as a parameter to the Invoke()
method and ASP.NET will inject it in as needed.
Lines 21-32: Here we’re trying to find the X-Correlation-Id
header in the request and if we can find it then we’ll set this on the ICorrelationIdGenerator
so we have it available for the duration of the request so we can do custom logging if we need. If we can’t find it in the request then we’ll generate a new Correlation ID as on line 30.
Lines 34-39: Here we’ll add the X-Correlation-Id
header to the response back to the client and that will be whether or not the client sends us an X-Correlation-Id
header.
Line 18: The last thing we need to do is call the next middleware in the ASP.NET request pipeline so we do this by calling the RequestDelegate
that was injected and passing in the HttpContext
that was sent as a parameter to the Invoke()
method.
Next up, we’ll add this middleware into the ASP.NET request pipeline.
Add ASP.NET Correlation ID Middleware
To do this, we need to call the IApplicationBuilder.UseMiddleware()
method within our start up file however I’ve created a small extension method to do this and keep it clean:
using AspNet.CorrelationIdGenerator;
using Microsoft.AspNetCore.Builder;
namespace AspNet.CorrelationIdGenerator.ApplicationBuilderExtensions;
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder AddCorrelationIdMiddleware(this IApplicationBuilder applicationBuilder)
=> applicationBuilder.UseMiddleware<CorrelationIdMiddleware>();
}
We can then use this in the start up. If you’re not using the new ASP.NET 6 Minimal Hosting model then you can simply add this by putting app.AddCorrelationIdMiddleware()
to the Configure()
method in Startup.cs
. If you are using the minimal hosting model then see line 21 below:
using AspNet.CorrelationIdGenerator.ApplicationBuilderExtensions;
using AspNet.CorrelationIdGenerator.ServiceCollectionExtensions;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddCorrelationIdGenerator();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.AddCorrelationIdMiddleware();
app.Run();
Now we’re done with that, I’ll show you how to do some custom logging anywhere in the code but also pull out the Correlation ID.
Add custom ASP.NET Correlation ID logging using ILogger
So, when the request comes in, the ASP.NET Correlation ID middleware will either retrieve the Correlation ID from the request or generate a new Correlation ID. Either way, it’s stored in the scoped ICorrelationId
service so we can simply inject that where we need and retrieve the Correlation ID.
See where it’s injected in line 17 below and then we’re logging a custom message with the Correlation ID in line 27:
using AspNet.CorrelationIdGenerator;
using Microsoft.AspNetCore.Mvc;
namespace Example.Api.Controllers;
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] _summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ICorrelationIdGenerator _correlationIdGenerator;
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ICorrelationIdGenerator correlationIdGenerator,
ILogger<WeatherForecastController> logger)
{
_correlationIdGenerator = correlationIdGenerator;
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
_logger.LogInformation("CorrelationId {correlationId}: Processing weather forecast request",
_correlationIdGenerator.Get());
return Enumerable
.Range(1, 5)
.Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = _summaries[Random.Shared.Next(_summaries.Length)]
}).ToArray();
}
}
Testing out the ASP.NET Correlation ID
The example API is a simple weather forecast API with a GET operation so, let’s send a request using Postman:
As you can see, we have an X-Correlation-Id
header returned to us.
We’re also seeing it in the logs (as seen using Visual Studio Diagnostic Tools):
Conclusion
So, there you have it. That’s one way on how you can use a Correlation ID passed from the client (or generate your own Correlation ID) and then use it for any custom logging throughout the request.
Happy Coding!