Showing posts with label Chain of Responsibility Design Pattern. Show all posts
Showing posts with label Chain of Responsibility Design Pattern. Show all posts

Monday, July 27, 2026

Chain of Responsibility Design Pattern

 


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

  1. Introduction

  2. What is the Chain of Responsibility Pattern?

  3. Why Do We Need It?

  4. Request and Handler Concepts

  5. How the Handler Chain Works

  6. UML Class Diagram

  7. Components of the Pattern

  8. Complete C# Console Application

  9. Banking Approval Workflow

  10. ASP.NET Core Middleware Example

  11. Exception Handling Pipeline

  12. Validation Pipeline

  13. Authentication and Authorization Pipeline

  14. Real-World Enterprise Examples

  15. Advantages

  16. Disadvantages

  17. Best Practices

  18. Common Mistakes

  19. Chain of Responsibility vs Middleware

  20. Chain of Responsibility vs Decorator

  21. Chain of Responsibility vs Other Patterns

  22. Interview Questions

  23. Summary

  24. 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:

  1. Receives the request.

  2. Decides whether it should process it.

  3. Performs its responsibility if appropriate.

  4. 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.

FeatureChain of ResponsibilityASP.NET Core Middleware
CategoryBehavioral Design PatternFramework request pipeline
PurposePass request through handlersProcess HTTP requests
HandlerCustom handlerMiddleware
Next ComponentSuccessorRequestDelegate
Can Stop Chain?YesYes
Can Continue?YesYes
HTTP-specificNoYes
Framework Built-inNoYes

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.

FeatureChain of ResponsibilityDecorator
Main GoalProcess a request through handlersAdd responsibilities
Number of HandlersPotentially manyPotentially many
Can Stop ProcessingYesUsually forwards
Handler SelectionBased on request/conditionsUsually predetermined
Primary ConceptRequest processingBehavior 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

PatternPrimary Question
Chain of ResponsibilityWho should process this request?
StrategyWhich algorithm should I use?
CommandHow can I represent this operation as an object?
MediatorHow can objects communicate through a central coordinator?
ObserverWho should be notified when something changes?
DecoratorHow 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:

PartPatternStatus
4.1Chain of Responsibility✅ Completed
4.2CommandNext
4.3InterpreterUpcoming
4.4IteratorUpcoming
4.5MediatorUpcoming
4.6MementoUpcoming
4.7ObserverUpcoming
4.8StateUpcoming
4.9StrategyUpcoming
4.10Template MethodUpcoming
4.11VisitorUpcoming

🚀 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.

Don't Copy

Protected by Copyscape Online Plagiarism Checker