Header Propagation in ConnectSoft Microservice Template¶
Purpose & Overview¶
Header Propagation is a mechanism for automatically forwarding HTTP headers from incoming requests to outgoing HTTP client calls. In the ConnectSoft Microservice Template, header propagation ensures that important context information such as trace IDs, correlation IDs, and distributed tracing headers are automatically carried through service-to-service communication, enabling end-to-end request tracking and observability.
Why Header Propagation?¶
Header propagation offers several critical benefits for microservices:
- Request Correlation: Automatically propagate correlation IDs across service boundaries
- Distributed Tracing: Maintain trace context across multiple services
- Observability: Enable end-to-end request tracking and logging
- Consistency: Ensure headers are consistently propagated without manual intervention
- Transparency: Automatic propagation without requiring changes to business logic
- Security Context: Propagate authentication and authorization context
- Context Preservation: Maintain request context across async boundaries
Header Propagation Philosophy
Header propagation ensures that request context is automatically maintained across service boundaries. This enables distributed tracing, correlation tracking, and observability without requiring developers to manually pass headers through every service call.
Architecture Overview¶
Header Propagation Flow¶
Incoming HTTP Request
↓
Header Propagation Middleware
├── Captures headers from incoming request
├── X-TraceId
├── X-Correlation-Id
└── traceparent (W3C Trace Context)
↓
Stored in HttpContext.Items
↓
Outgoing HttpClient Request
├── Header Propagation Message Handler
├── Automatically adds propagated headers
└── Headers forwarded to downstream service
↓
Downstream Service
├── Receives headers
├── Continues trace context
└── Can propagate further
Components¶
| Component | Purpose | Location |
|---|---|---|
| HeaderPropagationExtensions | Configuration and registration | ApplicationModel |
| HeaderPropagationMiddleware | Captures incoming headers | ASP.NET Core |
| HeaderPropagationMessageHandler | Adds headers to outgoing requests | ASP.NET Core |
| HttpClientExtensions | Configures HttpClient with header propagation | ApplicationModel |
Configuration¶
Service Registration¶
AddMicroserviceHeaderPropagation:
// HeaderPropagationExtensions.cs
internal static IServiceCollection AddMicroserviceHeaderPropagation(
this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
services.AddHeaderPropagation(options =>
{
options.Headers.Add("X-TraceId");
options.Headers.Add("X-Correlation-Id");
options.Headers.Add("traceparent");
});
return services;
}
Registered Headers:
- X-TraceId: Custom trace identifier for request tracking
- X-Correlation-Id: Business-level correlation identifier
- traceparent: W3C Trace Context header for distributed tracing
Middleware Configuration¶
UseMicroserviceHeaderPropagation:
// HeaderPropagationExtensions.cs
internal static IApplicationBuilder UseMicroserviceHeaderPropagation(
this IApplicationBuilder application)
{
ArgumentNullException.ThrowIfNull(application);
application.UseHeaderPropagation();
return application;
}
HttpClient Integration¶
AddHeaderPropagation to HttpClient:
// HttpClientExtensions.cs
internal static IServiceCollection AddMicroserviceHttpClient(
this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
// Configure HttpClient defaults
services.ConfigureHttpClientDefaults(http =>
{
if (OptionsExtensions.ServiceDiscoveryOptions.Enabled)
{
http.AddServiceDiscovery();
}
});
// Add HttpClient with header propagation
services.AddHttpClient()
.AddHeaderPropagation();
return services;
}
Integration in Program.cs¶
Service Registration:
// MicroserviceRegistrationExtensions.cs
services.AddMicroserviceHeaderPropagation();
services.AddMicroserviceHttpClient(); // Includes header propagation
Middleware Configuration:
// MicroserviceRegistrationExtensions.cs
// After routing, before endpoints
application.UseRouting();
application.UseMicroserviceRateLimiter();
application.UseMicroserviceLocalization();
application.UseMicroserviceHeaderPropagation(); // Must be after routing
Middleware Order: 1. Forwarded Headers 2. Exception Handling 3. Logging 4. Routing ← Header propagation must be after routing 5. Header Propagation ← Captures headers from current request 6. Endpoints
Propagated Headers¶
X-TraceId¶
Purpose: Custom trace identifier for request tracking.
Usage: - Set by correlation/tracing middleware - Automatically propagated to outgoing requests - Used for request correlation in logs
Example:
X-Correlation-Id¶
Purpose: Business-level correlation identifier.
Usage: - Set by conversation ID middleware - Links related operations across services - Used for business-level request tracking
Example:
traceparent¶
Purpose: W3C Trace Context header for distributed tracing.
Usage:
- Set by OpenTelemetry instrumentation
- Standard format: 00-{trace-id}-{parent-id}-{trace-flags}
- Enables distributed tracing across services
Format:
Example:
Components:
- 00: Version (00 = current version)
- 4bf92f3577b34da6a3ce929d0e0e4736: Trace ID (32 hex characters)
- 00f067aa0ba902b7: Parent ID (16 hex characters)
- 01: Trace flags (01 = sampled)
Usage¶
Automatic Propagation¶
Standard HttpClient Usage:
public class OrderService
{
private readonly HttpClient httpClient;
public OrderService(IHttpClientFactory httpClientFactory)
{
this.httpClient = httpClientFactory.CreateClient();
}
public async Task<PaymentResult> ProcessPaymentAsync(Order order)
{
// Headers are automatically propagated
var response = await this.httpClient.PostAsJsonAsync(
"https://payment-service/api/payments",
new PaymentRequest { OrderId = order.Id });
return await response.Content.ReadFromJsonAsync<PaymentResult>();
}
}
Headers are automatically added:
- No manual header handling required
- Headers from incoming request are automatically included
- Works with all HttpClient instances created via IHttpClientFactory
Named HttpClient¶
Configure Named Client:
// In service registration
services.AddHttpClient("PaymentService", client =>
{
client.BaseAddress = new Uri("https://payment-service");
client.DefaultRequestHeaders.Add("X-Api-Version", "v1");
})
.AddHeaderPropagation(); // Ensure header propagation is enabled
Use Named Client:
public class OrderService
{
private readonly IHttpClientFactory httpClientFactory;
public OrderService(IHttpClientFactory httpClientFactory)
{
this.httpClientFactory = httpClientFactory;
}
public async Task<PaymentResult> ProcessPaymentAsync(Order order)
{
var client = this.httpClientFactory.CreateClient("PaymentService");
// Headers are automatically propagated
var response = await client.PostAsJsonAsync(
"/api/payments",
new PaymentRequest { OrderId = order.Id });
return await response.Content.ReadFromJsonAsync<PaymentResult>();
}
}
Typed HttpClient¶
Define Typed Client:
public class PaymentServiceClient
{
private readonly HttpClient httpClient;
public PaymentServiceClient(HttpClient httpClient)
{
this.httpClient = httpClient;
}
public async Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request)
{
// Headers are automatically propagated
var response = await this.httpClient.PostAsJsonAsync(
"/api/payments",
request);
return await response.Content.ReadFromJsonAsync<PaymentResult>();
}
}
Register Typed Client:
// In service registration
services.AddHttpClient<PaymentServiceClient>(client =>
{
client.BaseAddress = new Uri("https://payment-service");
})
.AddHeaderPropagation(); // Ensure header propagation is enabled
Manual Header Override¶
Override Propagated Headers (if needed):
public async Task<PaymentResult> ProcessPaymentAsync(Order order)
{
var request = new HttpRequestMessage(HttpMethod.Post, "/api/payments")
{
Content = JsonContent.Create(new PaymentRequest { OrderId = order.Id })
};
// Override correlation ID if needed
request.Headers.Add("X-Correlation-Id", customCorrelationId);
// Other propagated headers still included
var response = await this.httpClient.SendAsync(request);
return await response.Content.ReadFromJsonAsync<PaymentResult>();
}
Integration with Other Features¶
Distributed Tracing¶
OpenTelemetry Integration:
Header propagation works seamlessly with OpenTelemetry:
// OpenTelemetry automatically sets traceparent header
// Header propagation automatically forwards it
services.AddOpenTelemetry()
.WithTracing(tracingBuilder =>
{
tracingBuilder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation();
});
// Header propagation forwards traceparent automatically
services.AddHeaderPropagation(options =>
{
options.Headers.Add("traceparent"); // W3C Trace Context
});
Result: - Trace context automatically propagated - Distributed traces span multiple services - End-to-end request visibility
Correlation IDs¶
Conversation ID Middleware:
// Conversation ID middleware sets X-Correlation-Id
application.UseConversationId();
// Header propagation forwards it automatically
application.UseMicroserviceHeaderPropagation();
Flow:
1. Request arrives
2. Conversation ID middleware generates/reads correlation ID
3. Sets X-Correlation-Id header
4. Header propagation middleware captures it
5. Outgoing requests automatically include it
Logging¶
Structured Logging:
public class OrderService
{
private readonly ILogger<OrderService> logger;
private readonly HttpClient httpClient;
public async Task ProcessOrderAsync(Order order)
{
// Correlation ID is in headers and logs
this.logger.LogInformation(
"Processing order {OrderId}",
order.Id);
// Call downstream service
var response = await this.httpClient.PostAsJsonAsync(
"https://payment-service/api/payments",
new PaymentRequest { OrderId = order.Id });
// Correlation ID propagated, logs linked
this.logger.LogInformation(
"Payment processed for order {OrderId}",
order.Id);
}
}
Log Correlation: - Correlation ID in headers - Correlation ID in logs - Logs linked across services - End-to-end request tracking
Advanced Configuration¶
Custom Header Configuration¶
Configure Specific Headers:
services.AddHeaderPropagation(options =>
{
// Add specific headers
options.Headers.Add("X-TraceId");
options.Headers.Add("X-Correlation-Id");
options.Headers.Add("traceparent");
// Add custom headers
options.Headers.Add("X-Request-Id");
options.Headers.Add("X-User-Id");
options.Headers.Add("X-Tenant-Id");
});
Header Value Transformation¶
Transform Header Values:
services.AddHeaderPropagation(options =>
{
options.Headers.Add("X-Correlation-Id", headerName =>
{
// Transform header value if needed
if (HttpContext.Request.Headers.TryGetValue(headerName, out var value))
{
// Add prefix or transform
return $"req-{value}";
}
return null;
});
});
Conditional Propagation¶
Conditional Header Propagation:
services.AddHeaderPropagation(options =>
{
options.Headers.Add("X-Correlation-Id", headerName =>
{
// Only propagate if condition is met
if (HttpContext.Request.Path.StartsWithSegments("/api"))
{
return HttpContext.Request.Headers[headerName];
}
return null;
});
});
Multiple Header Propagation Configurations¶
Different Configurations for Different Clients:
// Default configuration
services.AddHeaderPropagation(options =>
{
options.Headers.Add("X-Correlation-Id");
options.Headers.Add("traceparent");
});
// Named client with different headers
services.AddHttpClient("InternalService", client =>
{
client.BaseAddress = new Uri("https://internal-service");
})
.AddHeaderPropagation(options =>
{
// Only propagate traceparent for internal services
options.Headers.Add("traceparent");
});
Best Practices¶
Do's¶
-
Use Header Propagation for All HttpClient Calls
-
Configure Header Propagation After Routing
-
Use Standard Header Names
-
Integrate with Distributed Tracing
-
Document Custom Headers
Don'ts¶
-
Don't Skip Header Propagation for HttpClient
-
Don't Place Middleware Before Routing
-
Don't Propagate Sensitive Headers
-
Don't Manually Set Headers That Are Propagated
-
Don't Propagate Headers to External Services
// ❌ BAD - Propagating internal headers externally services.AddHttpClient("ExternalApi", client => { client.BaseAddress = new Uri("https://external-api.com"); }) .AddHeaderPropagation(); // May expose internal headers // ✅ GOOD - Configure separately for external services services.AddHttpClient("ExternalApi", client => { client.BaseAddress = new Uri("https://external-api.com"); }); // No header propagation for external services
Testing¶
Unit Testing¶
Test Header Propagation:
[TestMethod]
public async Task HttpClient_WithHeaderPropagation_IncludesHeaders()
{
// Arrange
var services = new ServiceCollection();
services.AddHeaderPropagation(options =>
{
options.Headers.Add("X-Correlation-Id");
});
services.AddHttpClient()
.AddHeaderPropagation();
var provider = services.BuildServiceProvider();
var factory = provider.GetRequiredService<IHttpClientFactory>();
// Create HttpContext with header
var httpContext = new DefaultHttpContext();
httpContext.Request.Headers["X-Correlation-Id"] = "test-correlation-id";
// Act
var client = factory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://example.com");
// Manually invoke header propagation
var handler = provider.GetRequiredService<HeaderPropagationMessageHandler>();
// ... test logic
// Assert
Assert.IsTrue(request.Headers.Contains("X-Correlation-Id"));
}
Integration Testing¶
Test with TestServer:
[TestMethod]
public async Task Service_WithHeaderPropagation_PropagatesHeaders()
{
// Arrange
var factory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Services configured with header propagation
});
});
var client = factory.CreateClient();
// Add header to incoming request
client.DefaultRequestHeaders.Add("X-Correlation-Id", "test-id");
// Act
var response = await client.GetAsync("/api/orders");
// Assert
// Verify downstream service received header
// (Requires mocking or test downstream service)
}
Troubleshooting¶
Issue: Headers Not Propagated¶
Symptoms: Headers from incoming requests not present in outgoing HttpClient calls.
Solutions:
1. Verify AddHeaderPropagation() is called in service registration
2. Verify UseHeaderPropagation() is called in middleware pipeline
3. Check middleware order (must be after UseRouting())
4. Verify AddHeaderPropagation() is called on HttpClient registration
5. Check header names match exactly (case-sensitive)
6. Verify headers exist in incoming request
Issue: Headers Propagated Twice¶
Symptoms: Headers appear multiple times in outgoing requests.
Solutions:
1. Check if headers are manually added in addition to propagation
2. Verify header propagation is not configured multiple times
3. Check for duplicate AddHeaderPropagation() calls
4. Review HttpClient configuration for duplicate handlers
Issue: Headers Not Available in Middleware¶
Symptoms: Headers not captured by header propagation middleware.
Solutions: 1. Verify middleware is placed after routing 2. Check if headers are set before middleware runs 3. Verify headers are in request headers (not response headers) 4. Check for middleware order issues
Issue: Headers Missing in Downstream Service¶
Symptoms: Downstream service doesn't receive propagated headers.
Solutions: 1. Verify header propagation is enabled on HttpClient 2. Check if headers are being filtered or removed 3. Verify downstream service is configured to accept headers 4. Check network/proxy configuration 5. Review HttpClient message handler pipeline
Security Considerations¶
Header Security¶
Safe Headers to Propagate:
- X-Correlation-Id: Safe for propagation
- traceparent: Safe for propagation (W3C standard)
- X-TraceId: Safe for propagation
- Custom business context headers
Avoid Propagating:
- Authorization: Security risk if propagated externally
- Cookie: Security and privacy risk
- X-API-Key: Security risk
- Sensitive authentication headers
External Service Communication¶
Configure Separately:
// Internal services - propagate headers
services.AddHttpClient("InternalService", client =>
{
client.BaseAddress = new Uri("https://internal-service");
})
.AddHeaderPropagation();
// External services - no header propagation
services.AddHttpClient("ExternalService", client =>
{
client.BaseAddress = new Uri("https://external-service.com");
});
// No AddHeaderPropagation() for external services
Performance Considerations¶
Overhead¶
Minimal Performance Impact: - Header propagation is lightweight - Middleware overhead is minimal - Message handler overhead is negligible - No significant performance impact
Best Practices: - Only propagate necessary headers - Avoid propagating large header values - Use efficient header matching - Consider caching header values if needed
Summary¶
Header propagation in the ConnectSoft Microservice Template provides:
- ✅ Automatic Propagation: Headers automatically forwarded without manual code
- ✅ Request Correlation: Correlation IDs maintained across services
- ✅ Distributed Tracing: Trace context propagated automatically
- ✅ Observability: End-to-end request tracking enabled
- ✅ Consistency: Standardized header propagation across all services
- ✅ Integration: Seamless integration with OpenTelemetry and logging
- ✅ Flexibility: Configurable header propagation with custom rules
By following these patterns, teams can:
- Maintain Context: Request context automatically maintained across services
- Enable Tracing: Distributed tracing works end-to-end
- Track Requests: Correlation IDs link logs across services
- Simplify Code: No manual header passing required
- Ensure Consistency: Standardized header propagation
- Improve Observability: Better request tracking and debugging
- Maintain Security: Control which headers are propagated
Header propagation transforms service-to-service communication from manual header management into an automatic, transparent, and observable operation that ensures request context is consistently maintained across the entire microservice ecosystem.