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();
}
}
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...
IHandlercallsIHandlercallsIHandler.Autofac will resolve the registered
IHandlersin anIEnumerable<IHandler>in the order registered, but there's no concept of "if I'm resolving the 12thIHandlerin the list, then if I ask for anIHandlerconstructor parameter I really want the 13th in the list." If you ask for anIHandler(single instance, notIEnumerable<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:
IHandlerinstances in order.IEnumerable<IHandler>(these will be ordered).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.