Using the Decorator Pattern with Autofac for Selective Service Registration

The other day, while working on a ticket at my job, I was tasked with adding a validation step to the checkout process for a specific platform. Initially, I thought this would be a simple change. I added the validation logic to the shared CheckoutProcessCommandHandler class, which looked like this:

class CheckoutProcessCommand : ICommandHandler
{
    public void Handle()
    {
        if (platform == 'specific-platform' && !Validate())
        {
            throw new InvalidOperationException();
        }

        // Other checkout logic...
    }
}

After creating a pull request (PR), I assumed the ticket was complete. However, a reviewer suggested using a decorator pattern and registering it in the platform's Autofac module instead. The challenge? There were multiple ICommandHandler implementations registered in the Autofac module, and I needed a way to apply the decorator to a single class only.

What is the Decorator Pattern?

The decorator pattern is a structural design pattern that allows you to dynamically add new functionality to an object by wrapping it in one or more decorator classes. Importantly, it does this without altering the object's original structure.

Why Use the Decorator Pattern?

  • Reusability: You can create reusable components to extend functionality.

  • Maintainability: Keeps the original codebase clean and focused on core logic.

  • Flexibility: Allows dynamic composition of behaviors at runtime.

Registering a Decorator in Autofac

Here’s a simple example of how to register a decorator in Autofac. In this example, we have two classes implementing ICommandHandler: CheckoutProcessCommand and PaymentProcessCommandHandler. Additionally, there’s a decorator class, CommandHandlerDecorator, which adds functionality by printing an additional message.

Interface Definition

ICommandHandler.cs:

interface ICommandHandler
{
    void Handle();
}

Command Handler Implementations

CheckoutProcessCommand.cs:

class CheckoutProcessCommand : ICommandHandler
{
    public void Handle()
    {
        Console.WriteLine($"{nameof(CheckoutProcessCommand)}.{nameof(Handle)}");
    }
}

PaymentProcessCommandHandler.cs:

class PaymentProcessCommandHandler : ICommandHandler
{
    public void Handle()
    {
        Console.WriteLine($"{nameof(PaymentProcessCommandHandler)}.{nameof(Handle)}");
    }
}

The Decorator

CommandHandlerDecorator.cs:

class CommandHandlerDecorator(ICommandHandler innerHandler) : ICommandHandler
{
    public void Handle()
    {
        Console.WriteLine($"{nameof(CommandHandlerDecorator)}.{nameof(Handle)}");
        innerHandler.Handle();
    }
}

Autofac Registration

Here’s how we register the command handlers and the decorator:

var builder = new ContainerBuilder();
builder.RegisterType<CheckoutProcessCommand>().As<ICommandHandler>();
builder.RegisterType<PaymentProcessCommandHandler>().As<ICommandHandler>();
builder.RegisterDecorator<CommandHandlerDecorator, ICommandHandler>();
var container = builder.Build();
foreach (var handler in container.Resolve<IEnumerable<ICommandHandler>>())
{
    handler.Handle();
}

Output

When you run the application, the output will look like this:

CommandHandlerDecorator.Handle
CheckoutProcessCommand.Handle
CommandHandlerDecorator.Handle
PaymentProcessCommandHandler.Handle

Notice that CommandHandlerDecorator.Handle is executed before each command handler’s message. This happens because the decorator is applied to all instances implementing ICommandHandler. However, this isn’t the behavior we want—we need to target a specific implementation.

The Solution: Keyed Services in Autofac

After consulting the Autofac documentation on Keyed Service, I found a solution to apply the decorator to a specific implementation. Here’s the updated code:

var builder = new ContainerBuilder();
builder.RegisterType<CheckoutProcessCommand>().As<ICommandHandler>();
builder.RegisterType<PaymentProcessCommandHandler>().Keyed<ICommandHandler>(nameof(PaymentProcessCommandHandler));
builder.RegisterDecorator<ICommandHandler>(innerInstance => new CommandHandlerDecorator(innerInstance), nameof(PaymentProcessCommandHandler));

var container = builder.Build();
foreach (var handler in container.Resolve<IEnumerable<ICommandHandler>>())
{
    handler.Handle();
}

Updated Output

CommandHandlerDecorator.Handle
CheckoutProcessCommand.Handle
CommandHandlerDecorator.Handle
PaymentProcessCommandHandler.Handle

The decorator is now only applied to PaymentProcessCommandHandler, as desired.

Conclusion

Using Autofac’s keyed services allows for precise control over which classes to apply decorators to. This approach helped me implement the required functionality without affecting other parts of the system. My PR was approved, and I was finally able to close the ticket!

Note: this blog post was edited by ChatGPT for a better reading experience.