BehavioralC#verifiedVerified
Chain of Responsibility Pattern in C#
Passes a request along a chain of handlers, where each handler decides to process it or pass it to the next handler in the chain.
How to Implement the Chain of Responsibility Pattern in C#
1Step 1: Define the request and abstract handler
public record Request(string Type, object Payload);
public abstract class Handler
{
private Handler? _next;
public Handler SetNext(Handler handler)
{
_next = handler;
return handler;
}2Step 2: Handle or pass to next in chain
public virtual string? Handle(Request request)
{
return _next?.Handle(request);
}
}3Step 3: Concrete handlers
public class AuthHandler : Handler
{
public override string? Handle(Request request)
{
if (request.Type == "auth")
return "AuthHandler: Handled authentication";
return base.Handle(request);
}
}
public class LoggingHandler : Handler
{
public override string? Handle(Request request)
{
Console.WriteLine($"LoggingHandler: Logging {request.Type}");
return base.Handle(request); // Always pass along
}
}
public class DefaultHandler : Handler
{
public override string? Handle(Request request) =>
$"DefaultHandler: No specific handler for {request.Type}";
}4Step 4: Build and use the chain
// var auth = new AuthHandler();
// var log = new LoggingHandler();
// var fallback = new DefaultHandler();
// auth.SetNext(log).SetNext(fallback);
// var result = auth.Handle(new Request("auth", null!));using Microsoft.Extensions.Logging;
// [step] Define strongly-typed pipeline request and response
public record PipelineContext
{
public required string RequestId { get; init; }
public required string Path { get; init; }
public required string Method { get; init; }
public Dictionary<string, string> Headers { get; init; } = [];
public object? Body { get; init; }
public Dictionary<string, object> Items { get; } = [];
public int StatusCode { get; set; } = 200;
public string? ResponseBody { get; set; }
public bool IsHandled { get; set; }
}
// [step] Async middleware delegate chain
public delegate Task PipelineDelegate(PipelineContext context);
public interface IMiddleware
{
Task InvokeAsync(PipelineContext context, PipelineDelegate next);
}
// [step] Concrete middleware implementations
public class AuthenticationMiddleware(ILogger<AuthenticationMiddleware> logger)
: IMiddleware
{
public async Task InvokeAsync(PipelineContext context, PipelineDelegate next)
{
if (!context.Headers.TryGetValue("Authorization", out var token)
|| string.IsNullOrEmpty(token))
{
context.StatusCode = 401;
context.ResponseBody = "Unauthorized";
context.IsHandled = true;
logger.LogWarning("Unauthorized request: {Id}", context.RequestId);
return;
}
context.Items["UserId"] = "user-123";
logger.LogDebug("Authenticated request: {Id}", context.RequestId);
await next(context);
}
}
public class RateLimitMiddleware(
int maxRequests, TimeSpan window,
ILogger<RateLimitMiddleware> logger) : IMiddleware
{
private readonly Queue<DateTime> _timestamps = new();
public async Task InvokeAsync(PipelineContext context, PipelineDelegate next)
{
var now = DateTime.UtcNow;
while (_timestamps.Count > 0 && now - _timestamps.Peek() > window)
_timestamps.Dequeue();
if (_timestamps.Count >= maxRequests)
{
context.StatusCode = 429;
context.ResponseBody = "Too Many Requests";
context.IsHandled = true;
logger.LogWarning("Rate limited: {Id}", context.RequestId);
return;
}
_timestamps.Enqueue(now);
await next(context);
}
}
// [step] Pipeline builder composes middleware into a delegate chain
public class PipelineBuilder
{
private readonly List<IMiddleware> _middleware = [];
public PipelineBuilder Use(IMiddleware middleware)
{
_middleware.Add(middleware);
return this;
}
public PipelineDelegate Build()
{
PipelineDelegate pipeline = _ => Task.CompletedTask;
for (var i = _middleware.Count - 1; i >= 0; i--)
{
var mw = _middleware[i];
var next = pipeline;
pipeline = ctx => mw.InvokeAsync(ctx, next);
}
return pipeline;
}
}Chain of Responsibility Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Chain of Responsibility Pattern in the Real World
“Like a customer support escalation: your call starts with a front-line agent. If they can’t resolve it, they transfer you to a specialist. If the specialist can’t help, it goes to a manager. Each level either handles it or passes it up the chain.”