Learn how the Chain of Responsibility Pattern in .NET helps pass requests dynamically, decouple handlers, and enhance flexibility in your applications. 🚀
Introduction
Have you ever called customer support and had your request transferred to multiple departments before reaching the right person? 📞
Now, imagine handling authorization, logging, or request validation in your .NET application without tightly coupling components.
This is where the Chain of Responsibility (CoR) Pattern shines! 🌟
What You’ll Learn in This Article:
✔️ What is the Chain of Responsibility Pattern?
✔️ Why and when should you use it?
✔️ How to implement it in .NET?
✔️ Real-world use cases and best practices
By the end, you’ll be able to pass requests like a pro and write clean, maintainable code. Let’s dive in! 🚀
What is the Chain of Responsibility Pattern?
The Chain of Responsibility Pattern is a behavioral design pattern that allows a request to be processed by multiple handlers, one at a time, until the right handler takes action.
Key Benefits:
✔️ Decouples sender and receiver – Handlers don’t need to know who processes the request.
✔️ Enhances flexibility – Easily add/remove handlers without modifying the existing code.
✔️ Improves maintainability – Avoids complex if-else
or switch
statements.
✔️ Supports dynamic request processing – Different handlers can process requests based on conditions.
Real-World Analogy: IT Support Desk 💻
Imagine you contact IT support for an issue.
- Level 1 Support tries to fix it.
- If they can’t, they escalate to Level 2 Support.
- If Level 2 can’t resolve it, it goes to Level 3 (Specialist Team).
Each level handles only the requests they can, passing others to the next level.
Similarly, in the Chain of Responsibility Pattern, requests move through a chain of handlers until the correct one processes them.
When to Use the Chain of Responsibility Pattern?
✅ When multiple objects can handle a request, but the exact handler isn’t known in advance.
✅ When you want to avoid large if-else
or switch
statements.
✅ When requests should be processed dynamically at runtime.
✅ When different handlers should process requests in a flexible sequence.
Implementing the Chain of Responsibility Pattern in .NET
Let’s build a real-world example: Processing customer complaints in a support system.
1️⃣ Define the Request Object
The request object contains details about the complaint.
public class SupportRequest
{
public string Message { get; }
public int Severity { get; } // 1 = Low, 2 = Medium, 3 = High
public SupportRequest(string message, int severity)
{
Message = message;
Severity = severity;
}
}
2️⃣ Create an Abstract Handler Class
This class defines a template for processing requests and linking the next handler.
public abstract class SupportHandler
{
protected SupportHandler _nextHandler;
public void SetNextHandler(SupportHandler nextHandler)
{
_nextHandler = nextHandler;
}
public abstract void HandleRequest(SupportRequest request);
}
3️⃣ Implement Concrete Handlers
Each handler processes specific severity levels and passes others to the next handler.
Low-Level Support (Level 1)
public class Level1Support : SupportHandler
{
public override void HandleRequest(SupportRequest request)
{
if (request.Severity == 1)
{
Console.WriteLine($"[Level 1 Support] Resolved: {request.Message}");
}
else if (_nextHandler != null)
{
_nextHandler.HandleRequest(request);
}
}
}
Medium-Level Support (Level 2)
public class Level2Support : SupportHandler
{
public override void HandleRequest(SupportRequest request)
{
if (request.Severity == 2)
{
Console.WriteLine($"[Level 2 Support] Resolved: {request.Message}");
}
else if (_nextHandler != null)
{
_nextHandler.HandleRequest(request);
}
}
}
High-Level Support (Level 3 - Specialist Team)
public class Level3Support : SupportHandler
{
public override void HandleRequest(SupportRequest request)
{
if (request.Severity == 3)
{
Console.WriteLine($"[Level 3 Support] Resolved: {request.Message}");
}
else
{
Console.WriteLine($"[Support System] No handler found for: {request.Message}");
}
}
}
4️⃣ Using the Chain of Responsibility Pattern
Now, let’s set up the chain and process requests.
class Program
{
static void Main()
{
// Create handlers
SupportHandler level1 = new Level1Support();
SupportHandler level2 = new Level2Support();
SupportHandler level3 = new Level3Support();
// Chain the handlers
level1.SetNextHandler(level2);
level2.SetNextHandler(level3);
// Create and process support requests
SupportRequest request1 = new SupportRequest("Cannot connect to WiFi", 1);
level1.HandleRequest(request1);
SupportRequest request2 = new SupportRequest("Application crash on startup", 2);
level1.HandleRequest(request2);
SupportRequest request3 = new SupportRequest("Server down! Critical issue!", 3);
level1.HandleRequest(request3);
}
}
🔹 Expected Output:
[Level 1 Support] Resolved: Cannot connect to WiFi
[Level 2 Support] Resolved: Application crash on startup
[Level 3 Support] Resolved: Server down! Critical issue!
Key Takeaways:
✔️ Requests are passed dynamically without tightly coupling handlers.
✔️ Flexible chain allows adding new handlers easily.
✔️ Enhances maintainability by removing unnecessary conditionals.
Best Practices for Using the Chain of Responsibility Pattern
✅ Keep handlers focused – Each handler should process a specific type of request.
✅ Ensure termination conditions – Avoid infinite loops by ensuring a request eventually gets handled.
✅ Use with Dependency Injection – Makes handler chaining more dynamic and configurable.
✅ Combine with Logging & Monitoring – Useful for debugging request flow.
Real-World Use Cases of the Chain of Responsibility Pattern
✔️ Middleware in ASP.NET Core – Request processing pipeline (Authentication, CORS, Logging).
✔️ Event Handling in UI Applications – Passing events up a UI hierarchy.
✔️ Logging Frameworks – Logging messages at different levels (Info, Warning, Error).
✔️ Authorization Systems – Multi-level user access control.
Chain of Responsibility vs. Other Design Patterns
Feature | Chain of Responsibility | Decorator Pattern | Mediator Pattern |
---|---|---|---|
Purpose | Pass requests through handlers | Extend object behavior dynamically | Centralize communication between objects |
Focus | Dynamic request handling | Adding new behavior | Decoupling object interactions |
Example | Support ticket escalation | Logging decorators | Chatroom message passing |
Conclusion
The Chain of Responsibility Pattern helps process requests dynamically, reduce dependencies, and improve maintainability.
Key Takeaways:
✔️ Eliminates tight coupling between sender and receiver.
✔️ Creates a flexible request processing pipeline.
✔️ Ideal for handling authentication, logging, and UI event propagation.
🚀 What’s Next?
💡 Try implementing a Logging Chain where each handler logs different levels of messages!
💬 Got questions? Drop them in the comments!
FAQs
1. How is the Chain of Responsibility Pattern different from the Mediator Pattern?
The Mediator Pattern centralizes communication, while the Chain of Responsibility passes requests sequentially through handlers.
2. Can I use the Chain of Responsibility Pattern with Dependency Injection?
Yes! You can dynamically configure the handler chain at runtime using DI containers.
🚀 Enjoyed this article? Share it with your developer community! 🚀