Chain of Responsibility Using Delegate Chaining in .NET

The Chain of Responsibility or CoR pattern is one of the most underestimated yet powerful design patterns. It allows us to build flexible, extensible processing pipelines, just like ASP.NET Core middleware or HttpClient handlers.

How .NET Uses Chain of Responsibility?

The CoR pattern is heavily used in .NET, especially in ASP.NET Core and HttpClient.

  • ASP.NET Core Middleware
    • Middleware components are chained together to process HTTP requests.
    • Each middleware can decide to process the request, pass it to the next middleware, or short-circuit the pipeline.
  • HttpClient Handlers
    • It uses a chain of HttpMessageHandler instances to process HTTP requests.
    • Each handler can modify the request, pass it to the next handler, or short-circuit the pipeline.
  • Validation Pipelines: Libraries like FluentValidation use a similar pattern to chain validation rules.

Delegate-Based CoR for Validation Rules

Instead of hardcoding validation logic, we dynamically add validation rules using delegate chaining.

  • Define the Delegate Pipeline with Short-Circuit support.
    public class ValidationPipeline
    {
        private Func<User, Task<bool>> _pipeline = user => Task.FromResult(true); // Default: Always passes
        public void AddRule(Func<User, Task<bool>> rule)
        {
            var previous = _pipeline;
            _pipeline = async user => await previous(user) && await rule(user); // Chain with AND condition
        }
        public async Task<bool> ValidateAsync(User user) => await _pipeline(user);
    }
    
  • Dynamically Add Rules
  • Validate Pipeline
    public class FeatureToggleService : IFeatureToggleService
    {
        private readonly ValidationPipeline _validateRules;
        private readonly IFeatureManagerSnapshot _featureManager;
        public FeatureToggleService(IFeatureManagerSnapshot featureManager)
        {
            _validateRules = new ValidationPipeline();
            _featureManager = featureManager;
        }
        public async Task<bool> CanAccessFeatureAsync(User user)
        {
            _validateRules.AddRule(async user => await _featureManager.IsEnabledAsync("CustomGreeting"));
            _validateRules.AddRule(user => Task.FromResult(user.Role == "Amin"));
            _validateRules.AddRule(user => Task.FromResult(user.HasActiveSubscription));
    
            return await _validateRules.ValidateAsync(user);
        }
    }
    public interface IFeatureToggleService
    {
        Task<bool> CanAccessFeatureAsync(User user);
    }
    

How Can It Help?

  • Dynamic & Extensible: Add/remove rules without modifying existing logic.
  • Follows Open-Closed Principle (OCP): New rules can be added without modifying old code.
  • Composable: Chain rules like ASP.NET Core middleware.
  • Async-Support: Works well with async validation.

The full implementation is here.

Repo: https://lnkd.in/d5ce76Em

How do you handle validation in your system?

Up Next
    Ebook Download
    View all
    Learn
    View all