A high-performance, composable middleware pipeline system with built-in diagnostics and runtime inspection.
- Composable Middleware - Clear, ordered execution of middleware components
- Async-First Design - Fully async, allocation-aware execution model
- Conditional Execution -
UseWhenfor context-based middleware activation - Branch Pipelines -
UseBranchfor isolated sub-pipelines - Runtime Inspection - Inspect, remove, or modify middleware at runtime
- Zero-Cost Diagnostics - Optional debug instrumentation with no overhead when disabled
- High Performance - No reflection, no dynamic dispatch in hot path
using Core.Features.Pipeline.Runtime;
using Core.Features.Pipeline.Abstractions.Middleware;
// 1. Define context
public sealed class RequestContext
{
public string UserId { get; init; }
public string TenantId { get; init; }
public IReadOnlyCollection<string> Roles { get; init; }
}
// 2. Create middleware
public class LoggingMiddleware : IMiddleware<RequestContext>
{
public async Task InvokeAsync(
RequestContext context,
Func<Task> next,
CancellationToken cancellationToken = default)
{
Console.WriteLine($"Request started for user: {context.UserId}");
await next();
Console.WriteLine($"Request completed");
}
}
// 3. Build pipeline
var builder = new PipelineBuilder<RequestContext>();
builder.Use(new LoggingMiddleware());
builder.UseWhen(ctx => ctx.Roles.Contains("User"), new ValidationMiddleware());
builder.UseBranch(
ctx => ctx.TenantId == "tenant-456",
branch =>
{
branch.Use(new AuditMiddleware());
branch.Use(new FeatureToggleMiddleware());
}
);
// 4. Execute
var executor = new PipelineExecutor<RequestContext>(builder);
await executor.ExecuteAsync(new RequestContext
{
UserId = "user-123",
TenantId = "tenant-456",
Roles = new[] { "User" }
});public async Task Handle(RequestContext ctx)
{
await Log(ctx);
if (ctx.Roles.Contains("User"))
await Validate(ctx);
if (ctx.TenantId == "tenant-456")
{
await Audit(ctx);
await FeatureToggle(ctx);
}
}builder.Use(new LoggingMiddleware());
builder.UseWhen(ctx => ctx.Roles.Contains("User"), new ValidationMiddleware());
builder.UseBranch(
ctx => ctx.TenantId == "tenant-456",
branch =>
{
branch.Use(new AuditMiddleware());
branch.Use(new FeatureToggleMiddleware());
}
);Declarative, testable, reorderable, inspectable.
- Installation & Setup - Complete setup guide
- Basic Usage - Learn the fundamentals
- Core Concepts - Understanding the architecture
- Pipeline Guide - Complete pipeline development guide
- Conditional Middleware - Context-based execution
- Branch Pipelines - Isolated sub-pipelines
- Diagnostics & Debugging - Performance analysis
- Runtime Inspection - Dynamic pipeline manipulation
- API Reference - Complete API documentation
- Best Practices - Tips and recommendations
var builder = new PipelineBuilder<ApiRequestContext>();
// Always execute
builder.Use(new RequestLoggingMiddleware());
builder.Use(new ExceptionHandlingMiddleware());
builder.Use(new AuthenticationMiddleware());
builder.Use(new AuthorizationMiddleware());
// Conditional execution
builder.UseWhen(
ctx => ctx.Roles.Contains("Admin"),
new AdminAuditMiddleware()
);
// Process request
builder.Use(new ValidationMiddleware());
builder.Use(new RateLimitingMiddleware());
builder.Use(new RequestProcessingMiddleware());
var executor = new PipelineExecutor<ApiRequestContext>(builder);var builder = new PipelineBuilder<TenantContext>();
builder.Use(new LoggingMiddleware());
builder.Use(new TenantResolutionMiddleware());
// Branch by tenant plan
builder.UseBranch(
ctx => ctx.Plan == "Free",
freeBranch =>
{
freeBranch.Use(new RateLimitingMiddleware(limit: 100));
freeBranch.Use(new FeatureLimitMiddleware());
}
);
builder.UseBranch(
ctx => ctx.Plan == "Enterprise",
enterpriseBranch =>
{
enterpriseBranch.Use(new EnterpriseFeaturesMiddleware());
enterpriseBranch.Use(new DedicatedSupportMiddleware());
}
);
builder.Use(new ProcessingMiddleware());Optional debug layer with zero runtime overhead when disabled.
using Core.Features.Pipeline.Diagnostics;
var builder = new PipelineBuilder<RequestContext>();
builder.Use(new LoggingMiddleware());
builder.Use(new ValidationMiddleware());
builder.Use(new ProcessingMiddleware());
var executor = new PipelineExecutor<RequestContext>(builder);
// Enable diagnostics
using var scope = PipelineDebugScope.Begin(out var debug);
await executor.ExecuteAsync(context);
// Inspect execution
foreach (var step in debug.Steps)
{
Console.WriteLine(
$"{step.Middleware.GetType().Name} | " +
$"{step.Duration?.TotalMilliseconds:F3}ms | " +
$"Next={step.NextCalled}"
);
}Output:
LoggingMiddleware | 12.345ms | Next=True
ValidationMiddleware | 5.123ms | Next=True
ProcessingMiddleware | 45.678ms | Next=False
What Gets Captured:
- Middleware identity
- Execution duration
- Whether
next()was called - Execution order
Learn more about diagnostics →
Inspect and manipulate pipeline structure at runtime.
using Core.Features.Pipeline.Inspection;
var builder = new PipelineBuilder<RequestContext>();
builder.Use(new LoggingMiddleware());
builder.Use(new ValidationMiddleware());
builder.Use(new ProcessingMiddleware());
var inspector = new PipelineInspector<RequestContext>(builder.Middlewares);
// List middleware
foreach (var mw in inspector.GetMiddlewares())
{
Console.WriteLine(mw.GetType().Name);
}
// Remove middleware dynamically
inspector.Remove(mw => mw is ValidationMiddleware);
// Get metadata
foreach (var descriptor in inspector.GetDescriptors())
{
Console.WriteLine(
$"{descriptor.Name} [{descriptor.Kind}] " +
$"Conditional={descriptor.IsConditional}"
);
}Descriptors are resolved via:
- Attributes
- Metadata interfaces
- Descriptor providers
- Cached resolution (thread-safe)
Application Layer
│
▼
PipelineBuilder<TContext>
├─ Use
├─ UseWhen
├─ UseBranch
│
▼
PipelineExecutor<TContext>
├─ Sequential execution
├─ Async continuation
└─ Optional Debug Wrapper
│
▼
Middleware Chain
├─ Standard Middleware
├─ Conditional Middleware
└─ Branch Middleware
- ✅ No reflection in hot path
- ✅ No dynamic dispatch
- ✅ Single delegate allocation per middleware
- ✅ AsyncLocal only when debugging is enabled
- ✅ Middleware controls continuation (
next)
- Keep middleware small and single-purpose
- Prefer
UseWhenover internaliflogic - Use diagnostics only in development
- Treat middleware as stateless
- Perform I/O in diagnostic middleware
- Use
Console.WriteLineinside middleware - Mutate pipeline during execution
- Share middleware instances with state
Read full best practices guide →
- PipelineBuilder<TContext> - Pipeline composition
- PipelineExecutor<TContext> - Execution engine
- IMiddleware<TContext> - Middleware contract
- PipelineDebugScope - Debug scope management
- PipelineDebugContext - Debug information container
- PipelineDebugStep - Step-level diagnostics
- PipelineDebuggerMiddleware - Debug wrapper
- PipelineInspector<TContext> - Runtime inspection
- IMiddlewareDescriptor - Middleware metadata
- MiddlewareDescriptorProvider - Metadata resolution
Built with ❤️ for .NET developers