How to implement Chain of Responsibility design pattern using Autofac

31 views Asked by At

I am trying to implement a chain of responsibility design pattern using Autofac to wire everything up for dependency injection. When I run my sample project, I get a message from Autofac that the component has not been registered.

I know that I can set this particular example up by using RegisterDecorator, but that will not always work for Chain Of Responsibility. If I need to use a SetNextHandler method, how would I wire that up? Is there something better than RegisterDecorator for this scenario?

Below is sample code that I have used to reproduce the error. The Autofac registration was one of several ways that ChatGPT suggested to wire it up.

IHandler

namespace ChainOfResponsibilitySample;

internal interface IHandler {
    void Handle();
}

ConcreteHandlerA

namespace ChainOfResponsibilitySample;

internal class ConcreteHandlerA : IHandler {
    private readonly IHandler _next;

    public ConcreteHandlerA(IHandler next) {
        _next = next;
    }

    public void Handle() {
        // do something

        _next.Handle();
    }
}

ConcreteHandlerB

namespace ChainOfResponsibilitySample;

internal class ConcreteHandlerB : IHandler {
    private readonly IHandler _next;

    public ConcreteHandlerA(IHandler next) {
        _next = next;
    }

    public void Handle() {
        // do something

        _next.Handle();
    }
}

ConcreteHandlerC

namespace ChainOfResponsibilitySample;

internal class ConcreteHandlerC : IHandler {
    public void Handle() {
        // do something
    }
}

AutofacModule

using Autofac;

namespace ChainOfResponsibilitySample;

internal class AutofacModule : Module {
    protected override void Load(ContainerBuilder builder) {
        builder.RegisterType<ConcreteHandlerC>()
               .AsImplementedInterfaces();
        builder.RegisterType<ConcreteHandlerB>()
               .AsImplementedInterfaces()
               .WithParameter((pi, ctx) => pi.ParameterType == typeof(IHandler),
                              (pi, ctx) => ctx.Resolve<IHandler>());
        builder.RegisterType<ConcreteHandlerA>()
               .AsImplementedInterfaces()
               .WithParameter((pi, ctx) => pi.ParameterType == typeof(IHandler),
                              (pi, ctx) => ctx.Resolve<IHandler>());

        base.Load(builder);
    }
}

MainLogic

using Microsoft.Extensions.Hosting;

namespace ChainOfResponsibilitySample;

internal class MainLogic : IHostedService {
    private readonly IHandler _handler;

    public MainLogic(IHandler handler) {
        _handler = handler;
    }

    public Task StartAsync(CancellationToken cancellationToken) {
        return Task.Run(() => _handler.Handle(), cancellationToken); 
    }

    public Task StopAsync(CancellationToken cancellationToken) {
        return Task.CompletedTask;
    }
}

Program.cs

using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ChainOfResponsibilitySample;

public class Program {
    public static void Main(string[] args) {
        Host.CreateDefaultBuilder()
            .UseServiceProviderFactory(new AutofacServiceProviderFactory())
            .ConfigureContainer<ContainerBuilder>(cb => cb.RegisterModule<AutofacModule>())
            .ConfigureServices(services => {
                 services.AddHostedService<MainLogic>();
             })
            .Build()
            .Run();
    }
}
1

There are 1 answers

0
Travis Illig On

I think you're going to have a lot of trouble getting this to auto-wire without some helper code. The problem is that chain of responsibility is all about...

  • Order: You have to know what order the handlers go in.
  • Same interface: IHandler calls IHandler calls IHandler.

Autofac will resolve the registered IHandlers in an IEnumerable<IHandler> in the order registered, but there's no concept of "if I'm resolving the 12th IHandler in the list, then if I ask for an IHandler constructor parameter I really want the 13th in the list." If you ask for an IHandler (single instance, not IEnumerable<IHandler>), you're always going to get the last one registered.

That's why the whole "same interface" thing is a problem.

The best you could do is have something like:

  • Register the IHandler instances in order.
  • Helper code to...
    • Resolve the IEnumerable<IHandler> (these will be ordered).
    • Foreach over the handlers and do something like currentHandler.SetNext(nextHandler); - you won't be able to have the next handler injected in the constructor.

But that's not Autofac just magically wiring those together - that's you doing it. It wouldn't be too much different from creating an array of those things on your own and then manually foreach-ing over it and wiring it up.

Instead of looking to Autofac to do this magic, I'd recommend looking at how the middleware stuff in .NET core works. That's the stuff that'd let you build up your app with handlers because middleware in ASP.NET is this exact pattern.