Nedo.AspNet.Request.Enrichment
1.0.2
dotnet add package Nedo.AspNet.Request.Enrichment --version 1.0.2
NuGet\Install-Package Nedo.AspNet.Request.Enrichment -Version 1.0.2
<PackageReference Include="Nedo.AspNet.Request.Enrichment" Version="1.0.2" />
<PackageVersion Include="Nedo.AspNet.Request.Enrichment" Version="1.0.2" />
<PackageReference Include="Nedo.AspNet.Request.Enrichment" />
paket add Nedo.AspNet.Request.Enrichment --version 1.0.2
#r "nuget: Nedo.AspNet.Request.Enrichment, 1.0.2"
#:package Nedo.AspNet.Request.Enrichment@1.0.2
#addin nuget:?package=Nedo.AspNet.Request.Enrichment&version=1.0.2
#tool nuget:?package=Nedo.AspNet.Request.Enrichment&version=1.0.2
Nedo.AspNet.Request.Enrichment
A middleware-based library for .NET 9 that enriches HTTP requests with contextual metadata — request IDs, correlation IDs, client details, user information, and more — making it available throughout the request lifecycle via dependency injection.
Why Request Enrichment?
In distributed systems, having rich contextual data on every request is critical for:
- Observability — Correlate logs, traces, and metrics across services.
- Debugging — Quickly identify the source and context of problematic requests.
- Security — Track client IPs, user agents, and authentication context.
- Analytics — Gather structured data about request patterns and usage.
Features
- 🔌 11 built-in enrichers — Request ID, Correlation ID, Client IP, URL, Timestamp, Client Info, User Info, Machine Info, HTTP Headers, Query Parameters, Request Body
- 🏗️ Fluent builder API — Type-safe, chainable configuration in
Program.cs - ⚙️ JSON configuration — Declarative
appsettings.jsonsupport with validation - 🔀 Pipeline strategies — Sequential (default) or Parallel execution
- 🛡️ Resilience — Configurable retries with exponential backoff, per-enricher timeouts, critical/non-critical failure handling
- 📤 Response header propagation — Automatically writes
X-Request-IdandX-Correlation-Idto response headers - 🎯 Conditional enrichment —
ShouldEnrich(HttpContext)lets enrichers skip requests based on method, path, or conditions - 📊 Built-in metrics —
System.Diagnostics.Metricscounters and histograms for observability via OpenTelemetry - 🔍 Ambient context —
IEnrichmentContextAccessorprovides AsyncLocal-based access from background services - 🎯 Path filtering — Skip enrichment for health checks, static files, etc.
- 🧩 Custom enrichers — Implement
IRequestEnricherfor domain-specific data - 💉 DI-first — Strongly-typed
EnrichmentContextinjectable into controllers, services, and minimal API endpoints - ✅ Testable — Interface-driven design, easy to mock and unit test
Quick Start
Installation
dotnet add package Nedo.AspNet.Request.Enrichment
Basic Setup
// Program.cs
using Nedo.AspNet.Request.Enrichment.DependencyInjection;
using Nedo.AspNet.Request.Enrichment.Middleware;
builder.Services.AddRequestEnrichment(enrichment =>
{
enrichment.AddRequestId();
enrichment.AddCorrelationId();
enrichment.AddClientIp();
enrichment.AddRequestTime();
});
app.UseRequestEnrichment();
Access Enriched Data
Minimal API:
app.MapGet("/info", (EnrichmentContext ctx) => new
{
ctx.RequestId,
ctx.CorrelationId,
ctx.ClientIp,
ctx.RequestTime
});
Controller:
using Nedo.AspNet.Request.Enrichment.Middleware;
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly EnrichmentContext _enrichment;
private readonly ILogger<OrdersController> _logger;
public OrdersController(
EnrichmentContext enrichment,
ILogger<OrdersController> logger)
{
_enrichment = enrichment;
_logger = logger;
}
[HttpGet]
public IActionResult Get()
{
_logger.LogInformation(
"Request {RequestId} from {ClientIp}",
_enrichment.RequestId,
_enrichment.ClientIp);
return Ok(new
{
_enrichment.RequestId,
_enrichment.CorrelationId,
_enrichment.ClientIp
});
}
}
Background Service / Non-DI Access:
using Nedo.AspNet.Request.Enrichment.Abstractions;
public class AuditService
{
private readonly IEnrichmentContextAccessor _accessor;
public AuditService(IEnrichmentContextAccessor accessor)
=> _accessor = accessor;
public void LogAction(string action)
{
var ctx = _accessor.EnrichmentContext;
Console.WriteLine($"[{ctx?.RequestId}] {action} by {ctx?.ClientIp}");
}
}
Note: The middleware automatically writes
X-Request-IdandX-Correlation-Idto every HTTP response header — no extra code needed.
Built-in Enrichers
| # | Enricher | Name | Priority | Output Type |
|---|---|---|---|---|
| 1 | RequestTimeEnricher |
RequestTime |
5 | DateTimeOffset |
| 2 | RequestIdEnricher |
RequestId |
10 | string |
| 3 | CorrelationIdEnricher |
CorrelationId |
11 | string |
| 4 | RequestUrlEnricher |
RequestUrl |
20 | string |
| 5 | RequestBodyEnricher |
RequestBody |
25 | long, string |
| 6 | ClientIpEnricher |
ClientIp |
30 | string |
| 7 | ClientInfoEnricher |
ClientInfo |
40 | ClientInfo |
| 8 | UserInformationEnricher |
UserInformation |
50 | UserInfo |
| 9 | MachineInformationEnricher |
MachineInformation |
60 | MachineInfo |
| 10 | HttpHeaderEnricher |
HttpHeader |
70 | IReadOnlyDictionary |
| 11 | QueryParameterEnricher |
QueryParameter |
80 | IReadOnlyDictionary |
Register all at once:
builder.Services.AddRequestEnrichment(enrichment =>
{
enrichment.AddAllDefaults();
});
Configuration
Fluent API with Per-Enricher Options
builder.Services.AddRequestEnrichment(enrichment =>
{
enrichment.AddClientIp(options =>
{
options.TrustForwardedHeaders = true;
options.ForwardedHeaderName = "X-Forwarded-For";
});
enrichment.AddHttpHeaders(options =>
{
options.ExcludeHeaders = ["Authorization", "Cookie", "Set-Cookie"];
});
enrichment.AddUserInformation(options =>
{
options.IncludeClaims = ["sub", "email", "role"];
});
});
JSON Configuration
{
"Enrichment": {
"EnabledEnrichers": [
"RequestId", "CorrelationId", "ClientIp",
"RequestTime", "UserInformation"
],
"EnableDebugLogging": false,
"Resilience": {
"MaxRetries": 3,
"RetryBaseDelayMs": 100,
"TimeoutMs": 5000
}
}
}
Pipeline Strategies
// Sequential (default) — predictable priority ordering
enrichment.UseSequentialPipeline();
// Parallel — concurrent execution for high-throughput
enrichment.UseParallelPipeline(maxConcurrency: 5);
Path Filtering
app.UseRequestEnrichment(options =>
{
options.ExcludePaths = ["/health", "/ready", "/metrics"];
options.ExcludeExtensions = [".css", ".js", ".png"];
});
Custom Enrichers
Implement IRequestEnricher for domain-specific data:
public class TenantEnricher : IRequestEnricher
{
public string Name => "Tenant";
public int Priority => 15;
public bool IsCritical => true;
// Only enrich API routes, skip health checks
public bool ShouldEnrich(HttpContext context)
=> context.Request.Path.StartsWithSegments("/api");
public async Task<EnrichmentResult> EnrichAsync(
HttpContext context,
CancellationToken cancellationToken = default)
{
var tenantId = context.Request.Headers["X-Tenant-Id"].FirstOrDefault();
if (string.IsNullOrEmpty(tenantId))
return EnrichmentResult.Failed(Name, "X-Tenant-Id header is required");
return new EnrichmentResult
{
EnricherName = Name,
Success = true,
Data = new Dictionary<string, object> { ["TenantId"] = tenantId }
};
}
}
Register it:
builder.Services.AddRequestEnrichment(enrichment =>
{
enrichment.AddAllDefaults();
enrichment.AddCustom<TenantEnricher>();
});
Project Structure
├── doc/ # Documentation (00–08)
├── sample/ # Sample projects
│ └── Nedo.AspNet.Request.Enrichment.Sample/
│ └── Controllers/ # Sample controllers
├── src/ # Source code
│ └── Nedo.AspNet.Request.Enrichment/
│ ├── Abstractions/ # IRequestEnricher, IEnrichmentContextAccessor, Models
│ ├── Enrichers/ # 11 built-in enrichers
│ ├── Configuration/ # EnrichmentOptions, EnrichmentBuilder
│ ├── Infrastructure/ # EnrichmentContextAccessor, EnrichmentMetrics
│ ├── Middleware/ # EnrichmentMiddleware, EnrichmentContext
│ ├── Pipeline/ # Sequential + Parallel strategies
│ └── DependencyInjection/ # ServiceCollection + ApplicationBuilder extensions
└── test/ # Unit tests (25 tests)
Getting Started
Prerequisites
Build
dotnet build
Run the Sample
dotnet run --project sample/Nedo.AspNet.Request.Enrichment.Sample
Run Tests
dotnet test
Documentation
| Doc | Title | Description |
|---|---|---|
| 00 | Overview | Core concepts and quick start |
| 01 | Architecture & Design | Internal architecture, design patterns, and project structure |
| 02 | Enrichers Reference | Complete reference for all built-in enrichers |
| 03 | Configuration Guide | JSON and fluent configuration options |
| 04 | Middleware Pipeline | How the enrichment pipeline processes requests |
| 05 | Custom Enrichers | Creating and registering your own enrichers |
| 06 | Resilience & Error Handling | Retry policies, timeouts, and error strategies |
| 07 | Testing Guide | Unit and integration testing patterns |
| 08 | Migration Guide | Migrating from Sindika.AspNet.Enrichment |
License
This project is licensed under the MIT License.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 was computed. 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. |
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.