Mastering Design Patterns in C# and ASP.NET Core
Part 4.1 – Chain of Responsibility Design Pattern
Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Behavioral Design Pattern
Difficulty: ⭐⭐⭐⭐☆ Intermediate to Advanced
Prerequisites: C#, OOP, Interfaces, Delegates, Dependency Injection, ASP.NET Core Middleware
Table of Contents
Introduction
What is the Chain of Responsibility Pattern?
Why Do We Need It?
Request and Handler Concepts
How the Handler Chain Works
UML Class Diagram
Components of the Pattern
Complete C# Console Application
Banking Approval Workflow
ASP.NET Core Middleware Example
Exception Handling Pipeline
Validation Pipeline
Authentication and Authorization Pipeline
Real-World Enterprise Examples
Advantages
Disadvantages
Best Practices
Common Mistakes
Chain of Responsibility vs Middleware
Chain of Responsibility vs Decorator
Chain of Responsibility vs Other Patterns
Interview Questions
Summary
Coming Up Next
1. Introduction
In enterprise applications, a request often needs to pass through multiple processing steps before it reaches its final destination.
For example, consider an ASP.NET Core Web API request:
HTTP Request
↓
Exception Handling
↓
Logging
↓
Authentication
↓
Authorization
↓
Validation
↓
Business Logic
↓
Database
↓
HTTP Response
Each component has a specific responsibility.
The important question is:
How can we organize these processing steps without putting everything into one large class?
This is one of the problems solved by the Chain of Responsibility Design Pattern.
2. What is the Chain of Responsibility Pattern?
Definition
The Chain of Responsibility Design Pattern is a behavioral design pattern that allows a request to pass through a sequence of handlers.
Each handler:
Receives the request.
Decides whether it should process it.
Performs its responsibility if appropriate.
Optionally passes the request to the next handler.
In simple terms:
Pass a request through a chain of objects until the appropriate handler processes it.
The basic structure is:
Client
↓
Handler 1
↓
Handler 2
↓
Handler 3
↓
Handler 4
3. Why Do We Need It?
Imagine an order-processing application.
Without Chain of Responsibility, you might write:
public void ProcessOrder(Order order)
{
ValidateOrder(order);
CheckAuthentication(order);
CheckAuthorization(order);
CheckInventory(order);
ProcessPayment(order);
SendNotification(order);
SaveAuditLog(order);
}
This class is now responsible for many unrelated tasks.
Problems include:
Large methods
Tight coupling
Difficult testing
Difficult modification
Difficult reordering
Difficult reuse
Violates Single Responsibility Principle
With a chain:
Request
↓
Validation Handler
↓
Authentication Handler
↓
Authorization Handler
↓
Inventory Handler
↓
Payment Handler
Each handler has one primary responsibility.
4. Request and Handler Concepts
The pattern has two important concepts:
Request
The information that needs to be processed.
Example:
public class OrderRequest
{
public int OrderId { get; set; }
public decimal Amount { get; set; }
public string UserRole { get; set; } = string.Empty;
}
Handler
A component responsible for processing some aspect of the request.
For example:
ValidationHandler
AuthenticationHandler
AuthorizationHandler
PaymentHandler
Each handler can decide whether to continue the request.
5. How the Handler Chain Works
Suppose we have:
Client
↓
ValidationHandler
↓
AuthenticationHandler
↓
AuthorizationHandler
↓
PaymentHandler
The request travels through the chain:
Request
↓
Validation
↓
Valid?
├── No → Stop
└── Yes
↓
Authentication
↓
Authenticated?
├── No → Stop
└── Yes
↓
Authorization
↓
Authorized?
├── No → Stop
└── Yes
↓
Payment
↓
Completed
This gives us an important capability:
A handler can stop the chain when the request cannot continue.
6. UML Class Diagram
A typical Chain of Responsibility structure looks like this:
+------------------+
| Handler |
+------------------+
| - nextHandler |
+------------------+
| + SetNext() |
| + Handle() |
+--------^---------+
|
+-------------+-------------+
| | |
| | |
+----------------+ +----------------+ +----------------+
| Validation | | Authorization | | Payment |
| Handler | | Handler | | Handler |
+----------------+ +----------------+ +----------------+
| Handle() | | Handle() | | Handle() |
+----------------+ +----------------+ +----------------+
↑
|
Client
7. Components of the Pattern
The main components are:
1. Client
Creates and sends the request.
2. Handler
Defines how handlers process requests and pass them onward.
3. Concrete Handler
Performs a specific responsibility.
4. Successor
The next handler in the chain.
Handler A
↓
Handler B
↓
Handler C
8. Complete C# Console Application
Let's build a complete example using an expense approval workflow.
Suppose employees submit expenses.
Different approval levels are required depending on the amount:
$0 – $1,000
↓
Manager
$1,001 – $5,000
↓
Director
Above $5,000
↓
VP
Step 1 – Request
public class ExpenseRequest
{
public string EmployeeName { get; set; } = string.Empty;
public decimal Amount { get; set; }
}
Step 2 – Handler Interface
public interface IExpenseApprover
{
IExpenseApprover SetNext(IExpenseApprover next);
void Approve(ExpenseRequest request);
}
Step 3 – Manager Handler
public class ManagerApprover : IExpenseApprover
{
private IExpenseApprover? _next;
public IExpenseApprover SetNext(IExpenseApprover next)
{
_next = next;
return next;
}
public void Approve(ExpenseRequest request)
{
if (request.Amount <= 1000)
{
Console.WriteLine(
$"Manager approved ${request.Amount}.");
return;
}
_next?.Approve(request);
}
}
Step 4 – Director Handler
public class DirectorApprover : IExpenseApprover
{
private IExpenseApprover? _next;
public IExpenseApprover SetNext(IExpenseApprover next)
{
_next = next;
return next;
}
public void Approve(ExpenseRequest request)
{
if (request.Amount <= 5000)
{
Console.WriteLine(
$"Director approved ${request.Amount}.");
return;
}
_next?.Approve(request);
}
}
Step 5 – VP Handler
public class VicePresidentApprover : IExpenseApprover
{
public IExpenseApprover SetNext(IExpenseApprover next)
{
return next;
}
public void Approve(ExpenseRequest request)
{
Console.WriteLine(
$"VP approved ${request.Amount}.");
}
}
Step 6 – Client
class Program
{
static void Main()
{
var manager = new ManagerApprover();
var director = new DirectorApprover();
var vp = new VicePresidentApprover();
manager.SetNext(director)
.SetNext(vp);
var request1 = new ExpenseRequest
{
EmployeeName = "John",
Amount = 500
};
var request2 = new ExpenseRequest
{
EmployeeName = "Mary",
Amount = 3000
};
var request3 = new ExpenseRequest
{
EmployeeName = "David",
Amount = 10000
};
manager.Approve(request1);
manager.Approve(request2);
manager.Approve(request3);
}
}
Output
Manager approved $500.
Director approved $3000.
VP approved $10000.
The client doesn't need to determine which approver should process the request.
The chain handles that responsibility.
9. Banking Approval Workflow
The Chain of Responsibility Pattern is particularly useful for banking workflows.
Imagine a loan approval process:
Loan Request
↓
Initial Validation
↓
Credit Score Check
↓
Manager Approval
↓
Branch Manager
↓
Regional Manager
↓
Risk Department
Different loan amounts may require different approval levels.
Loan ≤ $10,000
↓
Officer
Loan ≤ $50,000
↓
Manager
Loan ≤ $250,000
↓
Regional Manager
Loan > $250,000
↓
Risk Committee
The client simply submits:
loanChain.Process(loanRequest);
The chain decides who should process the request.
10. ASP.NET Core Middleware Example
This is one of the most important applications of the Chain of Responsibility concept.
ASP.NET Core middleware forms a request-processing pipeline.
For example:
HTTP Request
↓
Exception Middleware
↓
Logging Middleware
↓
Authentication Middleware
↓
Authorization Middleware
↓
Routing
↓
Endpoint
Each middleware can perform work before and after calling the next component.
Custom Middleware
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
public RequestLoggingMiddleware(
RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(
HttpContext context)
{
Console.WriteLine(
$"Request: {context.Request.Path}");
await _next(context);
Console.WriteLine(
$"Response: {context.Response.StatusCode}");
}
}
Register Middleware
In Program.cs:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseMiddleware<RequestLoggingMiddleware>();
app.MapControllers();
app.Run();
The important part is:
await _next(context);
This forwards the request to the next component in the pipeline.
11. Exception Handling Pipeline
A common ASP.NET Core pattern is to place exception handling near the beginning of the pipeline.
Conceptually:
Request
↓
Exception Handler
↓
Logging
↓
Authentication
↓
Authorization
↓
Controller
Example:
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
public ExceptionHandlingMiddleware(
RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(
HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
context.Response.StatusCode = 500;
await context.Response.WriteAsync(
"An unexpected error occurred.");
}
}
}
The middleware can catch exceptions generated by downstream components.
12. Validation Pipeline
Validation is another excellent use case.
For example:
Request
↓
Required Field Validation
↓
Format Validation
↓
Business Validation
↓
Authorization
↓
Process Request
A handler could look like:
public interface IRequestHandler
{
IRequestHandler SetNext(IRequestHandler next);
bool Handle(OrderRequest request);
}
A validation handler might stop the chain:
public class OrderValidationHandler : IRequestHandler
{
private IRequestHandler? _next;
public IRequestHandler SetNext(
IRequestHandler next)
{
_next = next;
return next;
}
public bool Handle(OrderRequest request)
{
if (request.OrderId <= 0)
{
Console.WriteLine(
"Invalid Order ID.");
return false;
}
return _next?.Handle(request) ?? true;
}
}
The chain stops if validation fails.
13. Authentication and Authorization Pipeline
Authentication and authorization can also be understood through the chain concept.
HTTP Request
↓
Authentication
↓
Is User Authenticated?
↓
Authorization
↓
Is User Authorized?
↓
Endpoint
For example:
Request
↓
Authentication Handler
↓
Authorization Handler
↓
Role Handler
↓
Endpoint
A failed security check can terminate the request before it reaches the business logic.
14. Real-World Enterprise Examples
The Chain of Responsibility Pattern is useful in many enterprise scenarios.
Banking
Loan approval
Payment authorization
Fraud checks
Transaction validation
Risk assessment
E-Commerce
Order validation
Discount validation
Inventory checks
Payment verification
Shipping validation
ASP.NET Core
Middleware pipeline
Authentication
Authorization
Exception handling
Request logging
Customer Support
Support Agent
↓
Team Lead
↓
Manager
↓
Director
Document Approval
Employee
↓
Manager
↓
Department Head
↓
Director
↓
Executive
Validation
Basic Validation
↓
Business Validation
↓
Security Validation
↓
Database Validation
15. Advantages
1. Reduces Coupling
The sender doesn't need to know which handler will process the request.
2. Supports Single Responsibility
Each handler can focus on one task.
3. Easy to Add New Handlers
You can add another handler without heavily modifying existing handlers.
4. Flexible Processing Order
Handlers can be reordered.
For example:
Logging
↓
Authentication
↓
Validation
could become:
Authentication
↓
Logging
↓
Validation
depending on requirements.
5. Supports Early Termination
A handler can stop processing when a condition fails.
6. Good for Pipelines
It is naturally suited to sequential processing.
16. Disadvantages
1. Request May Not Be Handled
If no handler processes the request, the request can reach the end of the chain without being handled.
2. Debugging Can Be More Difficult
The request may pass through many classes.
Handler A
↓
Handler B
↓
Handler C
↓
Handler D
Finding where something went wrong may require tracing the chain.
3. Long Chains
A very long chain can become difficult to understand.
4. Order Matters
Changing handler order can change application behavior.
For example:
Authentication → Authorization
is generally very different from:
Authorization → Authentication
5. Runtime Overhead
Every request may pass through multiple handlers.
17. Best Practices
1. Keep Each Handler Focused
Follow the Single Responsibility Principle.
Good:
ValidationHandler
AuthenticationHandler
LoggingHandler
Avoid:
EverythingHandler
2. Make Handler Ordering Explicit
Document the expected order.
3. Define Clear Termination Rules
Every chain should clearly define:
When processing stops
When processing continues
What happens if nobody handles the request
4. Keep Handlers Independent
Handlers should avoid unnecessary knowledge about other handlers.
5. Use Dependency Injection
In ASP.NET Core, handlers can be registered through DI.
builder.Services.AddScoped<ValidationHandler>();
builder.Services.AddScoped<AuthorizationHandler>();
builder.Services.AddScoped<PaymentHandler>();
6. Keep Chains Testable
Each handler should be unit-testable independently.
7. Don't Create Chains Just for the Sake of Patterns
If you have only one simple operation, a chain may introduce unnecessary complexity.
18. Common Mistakes
Mistake 1 – Creating a Giant Handler
A handler should not contain unrelated responsibilities.
Mistake 2 – Hard-Coding the Entire Chain
Avoid tightly coupling every handler to specific implementations.
Mistake 3 – Forgetting to Call the Next Handler
A handler that should continue the chain must invoke the successor.
For example:
_next?.Handle(request);
Mistake 4 – No Termination Strategy
Always determine what happens if:
No handler handles the request
Mistake 5 – Wrong Handler Ordering
Ordering is especially important in:
Authentication
Authorization
Validation
Middleware
19. Chain of Responsibility vs Middleware
ASP.NET Core Middleware and Chain of Responsibility have very similar concepts, but they aren't identical.
| Feature | Chain of Responsibility | ASP.NET Core Middleware |
|---|---|---|
| Category | Behavioral Design Pattern | Framework request pipeline |
| Purpose | Pass request through handlers | Process HTTP requests |
| Handler | Custom handler | Middleware |
| Next Component | Successor | RequestDelegate |
| Can Stop Chain? | Yes | Yes |
| Can Continue? | Yes | Yes |
| HTTP-specific | No | Yes |
| Framework Built-in | No | Yes |
Conceptual similarity
Chain of Responsibility:
Handler
↓
Next Handler
ASP.NET Core:
Middleware
↓
RequestDelegate
↓
Next Middleware
The concepts are closely related, but ASP.NET Core Middleware is a framework mechanism rather than simply a direct implementation of the GoF pattern.
20. Chain of Responsibility vs Decorator
These patterns can look similar because both may wrap or forward to another object.
But their intent is different.
| Feature | Chain of Responsibility | Decorator |
|---|---|---|
| Main Goal | Process a request through handlers | Add responsibilities |
| Number of Handlers | Potentially many | Potentially many |
| Can Stop Processing | Yes | Usually forwards |
| Handler Selection | Based on request/conditions | Usually predetermined |
| Primary Concept | Request processing | Behavior extension |
Example
Chain:
Request
↓
Handler A
↓
Handler B
↓
Handler C
Decorator:
Client
↓
Logging Decorator
↓
Caching Decorator
↓
Real Service
The distinction is based primarily on intent, not merely class structure.
21. Chain of Responsibility vs Other Patterns
| Pattern | Primary Question |
|---|---|
| Chain of Responsibility | Who should process this request? |
| Strategy | Which algorithm should I use? |
| Command | How can I represent this operation as an object? |
| Mediator | How can objects communicate through a central coordinator? |
| Observer | Who should be notified when something changes? |
| Decorator | How can I add behavior to an object? |
This is a useful way to identify patterns during architecture discussions and interviews.
22. Interview Questions
Beginner Level
1. What is the Chain of Responsibility Pattern?
It is a behavioral pattern where a request is passed through a sequence of handlers until it is handled or reaches the end of the chain.
2. What are the main components?
Typically:
Client
Handler
Concrete Handler
Successor
3. Can multiple handlers process the same request?
Yes.
The pattern can be designed so that:
Handler A → Handler B → Handler C
all perform processing before the request completes.
Alternatively, a handler can stop the chain once it handles the request.
4. Can a handler stop the chain?
Yes.
For example:
if (!IsValid(request))
{
return;
}
Intermediate Level
5. What are real-world examples?
Examples include:
ASP.NET Core Middleware
Approval workflows
Validation pipelines
Authentication/authorization
Exception handling
Support escalation
Payment validation
6. What is the main benefit?
The primary benefit is reduced coupling between the request sender and the specific object responsible for handling it.
7. What happens if no handler processes the request?
The request reaches the end of the chain.
The application should explicitly decide what happens in that situation, such as:
Return an error
Reject the request
Use a default handler
Log the problem
8. How does Chain of Responsibility support SOLID?
It can support:
Single Responsibility Principle: Each handler performs one responsibility.
Open/Closed Principle: New handlers can be added without heavily modifying existing ones.
Dependency Inversion: Handlers can depend on abstractions.
Advanced Level
9. Is ASP.NET Core Middleware exactly the Chain of Responsibility Pattern?
Not exactly.
ASP.NET Core Middleware implements a pipeline concept that is closely related to Chain of Responsibility.
Middleware uses RequestDelegate to pass execution to the next middleware component.
10. What is the difference between Chain of Responsibility and Strategy?
Chain of Responsibility determines how a request passes through handlers.
Strategy selects one algorithm or behavior from a set of alternatives.
11. What is the difference between Chain of Responsibility and Mediator?
Chain of Responsibility passes a request sequentially through handlers.
Mediator centralizes communication among multiple objects.
12. Can Chain of Responsibility be used in microservices?
Yes.
It can be useful for:
Request validation
Authentication
Authorization
Fraud detection
Message processing
Event processing
API request pipelines
13. What are the disadvantages?
Common disadvantages include:
Long chains
Debugging complexity
Runtime overhead
Handler ordering problems
Possibility of an unhandled request
23. Summary
The Chain of Responsibility Design Pattern provides a flexible way to process requests through multiple handlers.
The core concept is:
Request
↓
+---------------+
| Handler 1 |
+---------------+
↓
+---------------+
| Handler 2 |
+---------------+
↓
+---------------+
| Handler 3 |
+---------------+
↓
Result
Each handler can:
Process
↓
Stop
OR
Process
↓
Pass to Next
The pattern is particularly useful for:
Validation pipelines
Approval workflows
Authentication
Authorization
Exception handling
Logging
Request processing
ASP.NET Core Middleware
Enterprise workflows
The most important idea to remember is:
Chain of Responsibility separates request processing into independent handlers and allows the request to move through those handlers without tightly coupling the sender to a particular handler.
Behavioral Design Patterns – Progress
We have now started the Behavioral Design Patterns section:
| Part | Pattern | Status |
|---|---|---|
| 4.1 | Chain of Responsibility | ✅ Completed |
| 4.2 | Command | Next |
| 4.3 | Interpreter | Upcoming |
| 4.4 | Iterator | Upcoming |
| 4.5 | Mediator | Upcoming |
| 4.6 | Memento | Upcoming |
| 4.7 | Observer | Upcoming |
| 4.8 | State | Upcoming |
| 4.9 | Strategy | Upcoming |
| 4.10 | Template Method | Upcoming |
| 4.11 | Visitor | Upcoming |
🚀 Coming Up Next: Part 4.2 – Command Design Pattern
In the next article, we'll explore the Command Design Pattern, including:
What is the Command Pattern?
Why do we need it?
Command, Receiver, Invoker, and Client
UML Class Diagram
Complete C# Console Application
ASP.NET Core implementation
Command and Handler architecture
CQRS connection
Undo/Redo functionality
Queue and background processing
Banking transaction example
E-commerce order example
Real-world enterprise scenarios
Advantages and disadvantages
Best practices
Common mistakes
Command vs Strategy
Command vs Mediator
Command vs Chain of Responsibility
Interview questions
The Command Pattern will be especially useful for understanding CQRS, background jobs, message queues, undo/redo operations, and clean command-handler architectures in modern .NET applications.

No comments:
Post a Comment