CorrelationId.AspNetCore 0.0.2

dotnet add package CorrelationId.AspNetCore --version 0.0.2
                    
NuGet\Install-Package CorrelationId.AspNetCore -Version 0.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CorrelationId.AspNetCore" Version="0.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CorrelationId.AspNetCore" Version="0.0.2" />
                    
Directory.Packages.props
<PackageReference Include="CorrelationId.AspNetCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CorrelationId.AspNetCore --version 0.0.2
                    
#r "nuget: CorrelationId.AspNetCore, 0.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CorrelationId.AspNetCore@0.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CorrelationId.AspNetCore&version=0.0.2
                    
Install as a Cake Addin
#tool nuget:?package=CorrelationId.AspNetCore&version=0.0.2
                    
Install as a Cake Tool

CorrelationId.AspNetCore

A zero-boilerplate correlation ID tracking library for ASP.NET Core applications with automatic HTTP client integration and configurable additional headers for comprehensive distributed tracing.

Features

  • 🚀 Zero Boilerplate: No wrapper methods needed - correlation ID available everywhere automatically
  • 🔄 Automatic Flow: Correlation ID flows seamlessly through all async operations via AsyncLocal<T>
  • 📨 Automatic Header Tracking: Tracks X-Correlation-Id and configurable additional headers on all incoming requests
  • 🎯 Auto-Generation: Generates new correlation ID if header is missing
  • 📝 Automatic Logging: Adds all captured headers to log entries (both message prefix and structured properties)
  • 🔍 Structured Logging: Adds all headers as custom properties for searchable metadata
  • 🌐 HTTP Client Integration: Automatically propagates all headers to outgoing HTTP requests
  • 🏷️ Named HTTP Clients: Support for multiple configured HTTP clients with correlation
  • 🔗 Distributed Tracing: End-to-end correlation across microservices with custom headers
  • ⚡ Thread-Safe: Uses AsyncLocal<T> for thread-safe header storage
  • 🎛️ Configurable Headers: Support for additional custom headers like X-Event-Id, X-User-Id, etc.

Usage

1. Basic Setup (Correlation ID only)

// Simple setup - correlation ID only
builder.Services.AddCorrelationId();
app.UseCorrelationId();

// That's it! Correlation ID now available everywhere automatically

2. Basic Setup with Additional Headers

// Configure additional headers without HTTP client integration
builder.Services.AddCorrelationId(options =>
{
    // Configure additional headers to capture and log
    options.AdditionalHeaders.AddRange(new[]
    {
        "X-Event-Id",           // Custom event tracking header
        "X-User-Id",            // User identifier for request tracking
        "X-Request-Source"      // Source system identifier
    });
    
    // Add captured headers to response for client tracking
    options.AddAdditionalHeadersToResponse = true;
});

app.UseCorrelationId();
// Add correlation ID with HTTP client support
builder.Services.AddCorrelationIdWithHttpClient();

// Configure middleware
app.UseCorrelationId();

// Now all HTTP calls automatically include correlation headers!

4. Full Configuration with HTTP Client Integration

// Configure additional headers with HTTP client integration
builder.Services.AddCorrelationIdWithHttpClient(options =>
{
    // Configure additional headers to capture and log
    options.AdditionalHeaders.AddRange(new[]
    {
        "X-Event-Id",           // Custom event tracking header
        "X-User-Id",            // User identifier for request tracking
        "X-Request-Source",     // Source system identifier
        "X-Tenant-Id",          // Multi-tenant identifier
        "X-Session-Id"          // Session tracking
    });
    
    // Add captured headers to response for client tracking
    options.AddAdditionalHeadersToResponse = true;
    
    // Configure correlation ID header name (default: "X-Correlation-Id")
    options.CorrelationIdHeader = "X-Custom-Correlation-Id";
    
    // Control auto-generation (default: true)
    // When true: Automatically generates new correlation ID if request doesn't have one
    // When false: Only tracks correlation ID if provided in request headers
    options.AutoGenerate = true;
    
    // Add correlation ID to response headers (default: true)
    options.AddToResponseHeaders = true;
});

app.UseCorrelationId();

5. Custom HTTP Client Configuration

// Add specific HTTP clients with correlation ID support
builder.Services.AddHttpClient("MyApiClient", client =>
{
    client.BaseAddress = new Uri("https://api.example.com");
    client.Timeout = TimeSpan.FromSeconds(30);
}).AddCorrelationId();

// Or use named clients
builder.Services.AddHttpClient(CorrelationIdHttpClientNames.ExternalApi, client =>
{
    client.BaseAddress = new Uri("https://external-api.example.com");
}).AddCorrelationId();

6. Accessing Additional Headers Programmatically

[ApiController]
public class MyController : ControllerBase
{
    private readonly ILogger<MyController> _logger;
    private readonly ICorrelationIdService _correlationIdService;

    public MyController(ILogger<MyController> logger, ICorrelationIdService correlationIdService)
    {
        _logger = logger;
        _correlationIdService = correlationIdService;
    }

    [HttpGet]
    public async Task<IActionResult> Get()
    {
        // All logs automatically include all captured headers - no wrapper needed!
        _logger.LogInformation("Processing request");
        
        // Get all captured headers (correlation ID + additional headers)
        var allHeaders = _correlationIdService.CapturedHeaders;
        
        // Get specific headers
        var eventId = _correlationIdService.GetHeader("X-Event-Id");
        var userId = _correlationIdService.GetHeader("X-User-Id");
        var correlationId = _correlationIdService.CorrelationId;
        
        // Set additional headers programmatically
        _correlationIdService.SetAdditionalHeaders(new Dictionary<string, string>
        {
            { "X-Processing-Stage", "business-logic" },
            { "X-Request-Priority", "high" }
        });
        
        _logger.LogInformation("Request completed with headers: {Headers}", 
            string.Join(", ", allHeaders.Select(h => $"{h.Key}={h.Value}")));
        
        return Ok(new 
        { 
            Result = "Success", 
            CorrelationId = correlationId,
            EventId = eventId,
            UserId = userId,
            AllHeaders = allHeaders
        });
    }
}

Configuration Options Summary

Both AddCorrelationId() and AddCorrelationIdWithHttpClient() support the same configuration options:

Method HTTP Client Integration Configuration Support Use Case
AddCorrelationId() ❌ No ✅ Yes Web apps, APIs without external HTTP calls
AddCorrelationId(options => {}) ❌ No ✅ Yes Web apps with additional headers, no HTTP calls
AddCorrelationIdWithHttpClient() ✅ Yes ❌ Default only APIs with external HTTP calls, default config
AddCorrelationIdWithHttpClient(options => {}) ✅ Yes ✅ Yes APIs with external HTTP calls + custom headers

Choose your setup:

  • Basic web app/API: Use AddCorrelationId()
  • Need additional headers but no HTTP calls: Use AddCorrelationId(options => {})
  • API calling other services: Use AddCorrelationIdWithHttpClient()
  • API with additional headers + HTTP calls: Use AddCorrelationIdWithHttpClient(options => {})

Program.cs Configuration Scenarios

Scenario 1: Standard Correlation ID with Default Settings

For most applications that need basic correlation ID tracking with the default X-Correlation-Id header:

// Program.cs - Basic setup
using CorrelationId.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

// Add service defaults & Aspire client integrations
builder.AddServiceDefaults();

// Add services to the container
builder.Services.AddProblemDetails();
builder.Services.AddControllers();

// Add Correlation ID services with HTTP client integration (default settings)
builder.Services.AddCorrelationIdWithHttpClient();

var app = builder.Build();

// Configure the HTTP request pipeline
app.UseExceptionHandler();

// Add Correlation ID middleware (should be early in the pipeline)
app.UseCorrelationId();

app.MapControllers();
app.MapDefaultEndpoints();

app.Run();

Result:

  • Uses X-Correlation-Id header
  • Auto-generates correlation ID if missing
  • Adds correlation ID to response headers
  • Propagates correlation ID to all HTTP client calls

Scenario 2: Custom Correlation ID Header with Additional Headers

For applications that need a custom correlation ID header name and want to track additional headers:

// Program.cs - Custom configuration
using CorrelationId.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

// Add service defaults & Aspire client integrations
builder.AddServiceDefaults();

// Add services to the container
builder.Services.AddProblemDetails();
builder.Services.AddControllers();

// Add Correlation ID services with custom configuration
builder.Services.AddCorrelationIdWithHttpClient(options =>
{
    // Configure custom correlation ID header name (instead of default "X-Correlation-Id")
    options.CorrelationIdHeader = "X-Custom-Correlation-Id";
    
    // Configure additional headers to capture and log alongside correlation ID
    options.AdditionalHeaders.AddRange(new[]
    {
        "X-Event-Id",        // Custom event tracking header
        "X-User-Id",         // User identifier for request tracking
        "X-Request-Source",  // Source system identifier
        "X-Tenant-Id"        // Multi-tenant identifier
    });
    
    // Add captured headers to response for client tracking
    options.AddAdditionalHeadersToResponse = true;
    
    // Control auto-generation behavior
    options.AutoGenerate = true;
    
    // Add correlation ID to response headers
    options.AddToResponseHeaders = true;
});

var app = builder.Build();

// Configure the HTTP request pipeline
app.UseExceptionHandler();

// Add Correlation ID middleware (should be early in the pipeline)
app.UseCorrelationId();

app.MapControllers();
app.MapDefaultEndpoints();

app.Run();

Result:

  • Uses X-Custom-Correlation-Id header instead of default X-Correlation-Id
  • Captures and tracks additional headers: X-Event-Id, X-User-Id, X-Request-Source, X-Tenant-Id
  • Adds all captured headers to response headers for client tracking
  • Includes all headers in log entries automatically (both message prefix and structured properties)
  • Propagates custom correlation ID header AND all additional headers to outgoing HTTP client calls
  • HTTP clients will use X-Custom-Correlation-Id instead of the default header name
  • All downstream services receive the complete header context

When to Use Each Scenario

Use Scenario 1 (Default) when:

  • You're fine with the standard X-Correlation-Id header name
  • You only need basic correlation ID tracking
  • You want minimal configuration
  • You're building a simple API or web application

Use Scenario 2 (Custom) when:

  • Your organization uses a different correlation ID header name
  • You need to comply with specific naming conventions
  • You want to track additional context headers (user ID, tenant ID, etc.)
  • You need enhanced tracing across multiple systems
  • You want to include captured headers in API responses

✅ HTTP Propagation Guarantee: Both the custom correlation ID header name and all additional headers are automatically propagated to ALL outgoing HTTP client calls. The HTTP message handler respects your configuration and ensures complete header context flows through your distributed system.

Configuration Options Explained

CorrelationIdOptions Properties

public class CorrelationIdOptions
{
    // Header name for correlation ID (default: "X-Correlation-Id")
    public string CorrelationIdHeader { get; set; } = "X-Correlation-Id";
    
    // List of additional headers to capture and track
    public List<string> AdditionalHeaders { get; set; } = new();
    
    // Auto-generate correlation ID if not provided in request (default: true)
    public bool AutoGenerate { get; set; } = true;
    
    // Add correlation ID to response headers (default: true)
    public bool AddToResponseHeaders { get; set; } = true;
    
    // Add captured additional headers to response headers (default: false)
    public bool AddAdditionalHeadersToResponse { get; set; } = false;
}

AutoGenerate Behavior Examples

AutoGenerate = true (Default):

Incoming Request: GET /api/data
(no correlation header)

Middleware Action:
✅ Generates new correlation ID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
✅ Sets in context for logging and processing
✅ Adds to response headers

Response: 200 OK
Header: X-Correlation-Id: a1b2c3d4-e5f6-7890-abcd-ef1234567890

Logs: [CorrelationId: a1b2c3d4-e5f6-7890-abcd-ef1234567890] Processing request

AutoGenerate = false:

Incoming Request: GET /api/data
(no correlation header)

Middleware Action:
❌ No correlation ID generated
❌ CorrelationId remains null/empty
❌ No response header added

Response: 200 OK
(no correlation header)

Logs: Processing request (no correlation ID in logs)

With AutoGenerate = false but header provided:

Incoming Request: GET /api/data
Header: X-Correlation-Id: user123abc

Middleware Action:
✅ Uses provided correlation ID: "user123abc"
✅ Sets in context for logging
✅ Adds to response headers

Response: 200 OK
Header: X-Correlation-Id: user123abc

Logs: [CorrelationId: user123abc] Processing request

When to Use AutoGenerate = false

Use AutoGenerate = false when:

  • Strict tracking only: You only want to track requests that already have correlation IDs
  • Gateway scenarios: External gateway handles correlation ID generation
  • Optional correlation: Correlation ID is optional for your application
  • Performance: Slight performance improvement by not generating GUIDs

Use AutoGenerate = true (default) when:

  • Complete tracing: You want every request to have a correlation ID
  • Microservices: Each service should generate IDs for requests without them
  • Debugging: Easier to trace all requests, even those from tools/health checks

Usage Examples

1. Basic Controller Usage (Automatic - No Wrappers Needed!)

[ApiController]
public class MyController : ControllerBase
{
    private readonly ILogger<MyController> _logger;
    private readonly ICorrelationIdService _correlationIdService;

    public MyController(ILogger<MyController> logger, ICorrelationIdService correlationIdService)
    {
        _logger = logger;
        _correlationIdService = correlationIdService;
    }

    [HttpGet]
    public async Task<IActionResult> Get()
    {
        // All logs automatically include correlation ID - no wrapper needed!
        _logger.LogInformation("Processing request");
        
        // Correlation ID flows automatically through async operations
        await SomeAsyncWork();
        
        // Get correlation ID anytime
        var correlationId = _correlationIdService.CorrelationId;
        
        _logger.LogInformation("Request completed");
        return Ok(new { Result = "Success", CorrelationId = correlationId });
    }
    
    private async Task SomeAsyncWork()
    {
        // Correlation ID automatically available in nested methods
        _logger.LogDebug("Doing async work");
        await Task.Delay(100);
        _logger.LogDebug("Async work completed");
    }
}
[ApiController]
public class ExternalApiController : ControllerBase
{
    private readonly ICorrelatedHttpClient _httpClient;
    private readonly ILogger<ExternalApiController> _logger;

    public ExternalApiController(ICorrelatedHttpClient httpClient, ILogger<ExternalApiController> logger)
    {
        _httpClient = httpClient;
        _logger = logger;
    }

    [HttpGet("external-data")]
    public async Task<IActionResult> GetExternalData()
    {
        _logger.LogInformation("Calling external API");

        // Correlation ID is automatically added to the outgoing request
        var data = await _httpClient.GetAsync<ExternalData>("https://api.example.com/data");

        _logger.LogInformation("External API call completed");
        return Ok(data);
    }

    [HttpPost("process")]
    public async Task<IActionResult> ProcessData([FromBody] ProcessRequest request)
    {
        _logger.LogInformation("Starting data processing");

        // Multiple HTTP calls with automatic correlation ID propagation
        var tasks = new[]
        {
            _httpClient.GetAsync<UserInfo>($"https://user-service.com/users/{request.UserId}"),
            _httpClient.PostAsJsonAsync<OrderRequest, OrderResponse>(
                "https://order-service.com/orders", 
                new OrderRequest { UserId = request.UserId, Items = request.Items })
        };

        var results = await Task.WhenAll(tasks);

        _logger.LogInformation("Data processing completed");
        return Ok(new { User = results[0], Order = results[1] });
    }
}

3. Named HTTP Client Usage

[ApiController]
public class IntegrationController : ControllerBase
{
    private readonly HttpClient _externalApiClient;
    private readonly HttpClient _internalServiceClient;
    private readonly ILogger<IntegrationController> _logger;

    public IntegrationController(IHttpClientFactory httpClientFactory, ILogger<IntegrationController> logger)
    {
        _externalApiClient = httpClientFactory.CreateClient(CorrelationIdHttpClientNames.ExternalApi);
        _internalServiceClient = httpClientFactory.CreateClient(CorrelationIdHttpClientNames.InternalService);
        _logger = logger;
    }

    [HttpGet("integration")]
    public async Task<IActionResult> IntegrationCall()
    {
        _logger.LogInformation("Starting integration calls");

        // Both clients automatically include correlation ID headers
        var externalTask = _externalApiClient.GetAsync("https://external-api.com/status");
        var internalTask = _internalServiceClient.GetAsync("https://internal-service.com/health");

        var responses = await Task.WhenAll(externalTask, internalTask);

        _logger.LogInformation("Integration calls completed");
        return Ok(new { 
            ExternalStatus = (int)responses[0].StatusCode,
            InternalStatus = (int)responses[1].StatusCode
        });
    }
}

Automatic Structured Logging

The implementation automatically adds correlation ID as structured properties to every log entry with no additional setup required:

Automatic Properties (Added to Every Log)

  • CorrelationId: The correlation ID value

How It Works Automatically

// Just log normally - correlation ID properties are added automatically
_logger.LogInformation("Processing order {OrderId}", orderId);

// Results in log entry with:
// Message: "[CorrelationId: 12345678-1234-1234-1234-123456789abc] Processing order 12345"
// Structured Properties: 
//   - CorrelationId: "12345678-1234-1234-1234-123456789abc"
//   - OrderId: 12345

Benefits for Log Analysis

  • Automatic: No manual setup required - just use _logger normally
  • Searchable: Query logs by CorrelationId in your logging system
  • Filterable: Filter logs by correlation ID across all services
  • Groupable: Group related log entries for request tracing
  • Consistent: Every log entry automatically includes correlation context

Usage Examples

[ApiController]
[Route("api/[controller]")]
public class WeatherController : ControllerBase
{
    private readonly ILogger<WeatherController> _logger;
    private readonly ICorrelationIdService _correlationIdService;

    public WeatherController(ILogger<WeatherController> logger, ICorrelationIdService correlationIdService)
    {
        _logger = logger;
        _correlationIdService = correlationIdService;
    }

    [HttpGet]
    public async Task<IActionResult> GetWeatherAsync()
    {
        // Correlation ID automatically available - no wrapper needed!
        var correlationId = _correlationIdService.CorrelationId;
        
        _logger.LogInformation("Processing weather request");
        
        // Correlation ID flows automatically through async operations
        await Task.Delay(100);
        await ProcessWeatherDataAsync();
        
        var weather = new { Temperature = 72, Condition = "Sunny", CorrelationId = correlationId };
        
        _logger.LogInformation("Weather request completed");
        
        return Ok(weather);
    }
    
    private async Task ProcessWeatherDataAsync()
    {
        // Correlation ID automatically available in all nested methods
        _logger.LogDebug("Processing weather data");
        await Task.Delay(50);
        _logger.LogDebug("Weather data processed");
    }
}

When You DO Need Helper Methods (Rare Cases)

The CorrelationIdHelper methods are only needed for these specific scenarios:

[ApiController]
[Route("api/[controller]")]
public class BackgroundTaskController : ControllerBase
{
    private readonly ICorrelationIdService _correlationIdService;
    private readonly ILogger<BackgroundTaskController> _logger;
    private readonly IServiceProvider _serviceProvider;

    public BackgroundTaskController(
        ICorrelationIdService correlationIdService, 
        ILogger<BackgroundTaskController> logger,
        IServiceProvider serviceProvider)
    {
        _correlationIdService = correlationIdService;
        _logger = logger;
        _serviceProvider = serviceProvider;
    }

    [HttpPost("process-order")]
    public async Task<IActionResult> ProcessOrder([FromBody] OrderRequest request)
    {
        _logger.LogInformation("Starting order processing for Order ID: {OrderId}", request.OrderId);

        // ✅ Normal async operations - correlation ID flows automatically
        await ValidateOrderAsync(request);
        await SaveOrderAsync(request);

        // ⚠️ Background task - needs explicit correlation context
        Task.Run(() => CorrelationIdHelper.ExecuteWithCorrelationId(_correlationIdService, () =>
        {
            // This runs on a background thread pool thread
            _logger.LogInformation("Processing order fulfillment in background for Order ID: {OrderId}", request.OrderId);
            
            // Heavy processing that doesn't block the request
            ProcessOrderFulfillment(request.OrderId);
            SendOrderConfirmationEmail(request.CustomerEmail);
            UpdateInventorySystem(request.Items);
            
            _logger.LogInformation("Background order processing completed for Order ID: {OrderId}", request.OrderId);
        }));

        _logger.LogInformation("Order processing initiated for Order ID: {OrderId}", request.OrderId);
        return Ok(new { OrderId = request.OrderId, Status = "Processing", Message = "Order processing started" });
    }

    [HttpPost("schedule-report")]
    public IActionResult ScheduleReport([FromBody] ReportRequest request)
    {
        _logger.LogInformation("Scheduling report generation: {ReportType}", request.ReportType);

        // ⚠️ Timer callbacks need explicit correlation context
        var timer = new System.Timers.Timer(TimeSpan.FromMinutes(request.DelayMinutes).TotalMilliseconds);
        timer.AutoReset = false;
        timer.Elapsed += (sender, e) => CorrelationIdHelper.ExecuteWithCorrelationId(_correlationIdService, () =>
        {
            _logger.LogInformation("Timer triggered - generating scheduled report: {ReportType}", request.ReportType);
            
            GenerateReport(request.ReportType, request.Parameters);
            
            _logger.LogInformation("Scheduled report generation completed: {ReportType}", request.ReportType);
            timer.Dispose();
        });
        
        timer.Start();
        return Ok(new { Message = $"Report scheduled to run in {request.DelayMinutes} minutes" });
    }

    [HttpPost("batch-process")]
    public async Task<IActionResult> BatchProcess([FromBody] BatchRequest request)
    {
        _logger.LogInformation("Starting batch processing for {ItemCount} items", request.Items.Count);

        // ⚠️ Parallel background processing with correlation context
        var tasks = request.Items.Select(item => 
            Task.Run(async () => await CorrelationIdHelper.ExecuteWithCorrelationIdAsync(_correlationIdService, async () =>
            {
                _logger.LogInformation("Processing batch item: {ItemId}", item.Id);
                
                // Each background task maintains correlation context
                await ProcessBatchItemAsync(item);
                await UpdateProgressAsync(item.Id, "Completed");
                
                _logger.LogInformation("Batch item completed: {ItemId}", item.Id);
                return item.Id;
            }))
        ).ToArray();

        // ✅ Awaiting tasks - correlation ID flows normally
        var completedItems = await Task.WhenAll(tasks);
        
        _logger.LogInformation("Batch processing completed for {CompletedCount} items", completedItems.Length);
        return Ok(new { CompletedItems = completedItems, Message = "Batch processing completed" });
    }

    [HttpPost("queue-message")]
    public IActionResult QueueMessage([FromBody] MessageRequest request)
    {
        _logger.LogInformation("Queuing message for processing: {MessageType}", request.MessageType);

        // ⚠️ Simulating message queue processing - needs correlation context
        ThreadPool.QueueUserWorkItem(_ => CorrelationIdHelper.ExecuteWithCorrelationId(_correlationIdService, () =>
        {
            _logger.LogInformation("Processing queued message: {MessageType}", request.MessageType);
            
            // Simulate message processing
            Thread.Sleep(1000);
            ProcessMessage(request);
            
            _logger.LogInformation("Queued message processed: {MessageType}", request.MessageType);
        }));

        return Ok(new { Message = "Message queued for processing" });
    }

    // ✅ Normal async methods - correlation ID flows automatically
    private async Task ValidateOrderAsync(OrderRequest request)
    {
        _logger.LogDebug("Validating order: {OrderId}", request.OrderId);
        await Task.Delay(100); // Simulate validation
        _logger.LogDebug("Order validation completed: {OrderId}", request.OrderId);
    }

    private async Task SaveOrderAsync(OrderRequest request)
    {
        _logger.LogDebug("Saving order to database: {OrderId}", request.OrderId);
        await Task.Delay(200); // Simulate database save
        _logger.LogDebug("Order saved: {OrderId}", request.OrderId);
    }

    // Methods called from background tasks - correlation ID available via helper
    private void ProcessOrderFulfillment(int orderId)
    {
        _logger.LogInformation("Processing fulfillment for order: {OrderId}", orderId);
        Thread.Sleep(2000); // Simulate heavy processing
    }

    private void SendOrderConfirmationEmail(string email)
    {
        _logger.LogInformation("Sending confirmation email to: {Email}", email);
        Thread.Sleep(500); // Simulate email sending
    }

    private void UpdateInventorySystem(List<OrderItem> items)
    {
        _logger.LogInformation("Updating inventory for {ItemCount} items", items.Count);
        Thread.Sleep(1000); // Simulate inventory update
    }

    private void GenerateReport(string reportType, Dictionary<string, object> parameters)
    {
        _logger.LogInformation("Generating report: {ReportType}", reportType);
        Thread.Sleep(3000); // Simulate report generation
    }

    private async Task ProcessBatchItemAsync(BatchItem item)
    {
        _logger.LogDebug("Processing batch item: {ItemId}", item.Id);
        await Task.Delay(500); // Simulate async processing
    }

    private async Task UpdateProgressAsync(int itemId, string status)
    {
        _logger.LogDebug("Updating progress for item {ItemId}: {Status}", itemId, status);
        await Task.Delay(100); // Simulate progress update
    }

    private void ProcessMessage(MessageRequest request)
    {
        _logger.LogInformation("Processing message: {MessageId}", request.MessageId);
        Thread.Sleep(800); // Simulate message processing
    }
}

HTTP Client Integration Features

Automatic Header Propagation

  • Configurable Correlation ID Header: Uses the configured header name (default: X-Correlation-Id)
    • If you set options.CorrelationIdHeader = "X-Custom-Correlation-Id", all HTTP calls use that header
  • Additional Headers: Automatically propagates ALL captured additional headers to outgoing requests
    • Headers like X-User-Id, X-Event-Id, X-Tenant-Id are automatically included
  • Message Handler: Intelligently adds headers only if not already present in the request
  • Logging Integration: Logs all HTTP requests/responses with full correlation context
  • Error Handling: Maintains correlation context even when HTTP calls fail
  • Thread Safety: Works correctly with async/await and parallel HTTP calls

Available HTTP Clients

  • ICorrelatedHttpClient: High-level typed client with built-in JSON serialization
  • Named HttpClients: Pre-configured clients for different scenarios
    • CorrelationIdHttpClientNames.Default: General purpose client
    • CorrelationIdHttpClientNames.ExternalApi: For external API calls
    • CorrelationIdHttpClientNames.InternalService: For internal service calls

Header Propagation Example with Custom Configuration

// Program.cs - Custom header configuration
builder.Services.AddCorrelationIdWithHttpClient(options =>
{
    options.CorrelationIdHeader = "X-Custom-Correlation-Id";
    options.AdditionalHeaders.AddRange(new[] { "X-User-Id", "X-Tenant-Id" });
});
Incoming Request: GET /api/data
Headers: 
  X-Custom-Correlation-Id: user123abc
  X-User-Id: user789
  X-Tenant-Id: tenant456

Your API processes request and makes HTTP call:

Outgoing HTTP Request: GET https://external-api.com/data
Headers automatically added:
  X-Custom-Correlation-Id: user123abc    ← Custom correlation ID header
  X-User-Id: user789                      ← Additional header propagated
  X-Tenant-Id: tenant456                  ← Additional header propagated

External API receives all context headers!

Correlation Flow Example

Incoming Request: GET /api/data
Header: X-Correlation-Id: user123abc

Your API logs:
[CorrelationId: user123abc] Processing request
[CorrelationId: user123abc] Sending HTTP GET request to https://external-api.com/data

External API receives:
Header: X-Correlation-Id: user123abc

External API logs (if using correlation ID):
[CorrelationId: user123abc] External API processing request

Your API logs:
[CorrelationId: user123abc] Received HTTP 200 response from https://external-api.com/data
[CorrelationId: user123abc] Request processing completed

Response: 200 OK
Header: X-Correlation-Id: user123abc

How It Works

  1. Middleware: CorrelationIdMiddleware intercepts all requests

    • Checks for X-Correlation-Id header
    • Generates new ID if missing (full GUID)
    • Sets correlation ID in response headers
    • Stores correlation ID in thread-local storage using AsyncLocal<T>
  2. Automatic Context Flow: Correlation ID flows automatically through your entire request

    • AsyncLocal<T>: Ensures correlation ID is available in all async operations
    • No Wrappers Needed: Just use _correlationIdService.CorrelationId anywhere
    • Thread-Safe: Works correctly with parallel async operations
    • Nested Methods: Correlation ID available in all nested method calls
  3. Logging: Custom logger wrapper automatically adds correlation ID to log messages

    • Message Format: [CorrelationId: 12345678-1234-1234-1234-123456789abc] Your log message
    • Structured Properties: Adds CorrelationId as searchable properties
    • Scoped Logging: Uses BeginScope() to add correlation context to all nested log calls
  4. HTTP Client Integration: Automatically propagates correlation ID to outgoing requests

    • Message Handler: Adds X-Correlation-Id header to all HTTP calls
    • Named Clients: Works with all configured HTTP clients
    • Error Handling: Maintains correlation context even when HTTP calls fail

When Helper Methods Are Needed

The CorrelationIdHelper.ExecuteWithCorrelationId* methods are only needed for:

  • Background Tasks: Task.Run(), ThreadPool.QueueUserWorkItem()
  • New Threads: new Thread() or similar
  • Timer Callbacks: System.Timers.Timer events
  • Message Queues: Processing outside HTTP request context

For normal controller operations, the correlation ID is automatically available everywhere!

Example API Flow

Request:  GET /api/weather
Header:   X-Correlation-Id: user123abc

Response: 200 OK
Header:   X-Correlation-Id: user123abc

Logs:
[CorrelationId: user123abc] Getting weather forecast
[CorrelationId: user123abc] Generating weather forecast data

If no correlation ID header is provided:

Request:  GET /api/weather
(no correlation header)

Response: 200 OK
Header:   X-Correlation-Id: a1b2c3d4-e5f6-7890-abcd-ef1234567890

Logs:
[CorrelationId: a1b2c3d4-e5f6-7890-abcd-ef1234567890] Getting weather forecast
[CorrelationId: a1b2c3d4-e5f6-7890-abcd-ef1234567890] Generating weather forecast data

Configuration

Zero configuration required! The implementation is intentionally simple and opinionated:

  • Uses X-Correlation-Id header (fixed name)
  • Generates full GUID correlation IDs automatically
  • Automatically applies to all API flows via middleware
  • Automatically adds correlation ID to all logs
  • Automatically propagates to HTTP client calls
  • No configuration options by design for maximum simplicity

Key Benefits

Zero Boilerplate: No wrapper methods needed in controllers ✅ Automatic Flow: Correlation ID available everywhere automatically
Thread-Safe: Works with async/await and parallel operations ✅ HTTP Integration: Automatic header propagation to outgoing calls ✅ Logging Integration: All logs automatically include correlation ID ✅ Error Handling: Correlation context maintained during exceptions ✅ Distributed Tracing: End-to-end correlation across microservices

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.2 20 8/23/2025
0.0.1 23 8/23/2025