Search code examples
c#dependency-injectionioc-containersimple-injector

Simple Injector - RegisterCollection and RegisterDecorator using Composite pattern and interface inheritance


How can I register types as IPollingService<TContext> so that they'll be decorated via container.RegisterDecorator(typeof(IPollingService<>), typeof(ContextAwarePollingServiceDecorator<>)), but also register them as a collection of IPollingService so that they'll be injected into the constructor of CompositePollingService via container.RegisterCollection(typeof(IPollingService), types)?

I have the following interfaces:

public interface IPollingService { Task Poll(); }
public interface IPollingService<TContext> : IPollingService
    where TContext : CustomContext { TContext Context { get; } }

and a Composite-pattern implementation of IPollingService:

class CompositePollingService : IPollingService
{
    private readonly IEnumerable<IPollingService> _pollingServices;

    public CompositePollingService(IEnumerable<IPollingService> pollingServices) => 
        _pollingServices = pollingServices;

    public async Task Poll() => 
        await Task.WhenAll(_pollingServices.Select(s => s.Poll()));
}

All my other implementations of IPollingService actually implement IPollingService<TContext> but I'm passing them into the CompositePollingService constructor as IEnumerable<IPollingService> because there will be a mix of IPollingService<TContext>, and the CompositeService only needs to know about IPollingService.Poll().

I add the types to a list conditionally based on configuration, and use the list to register the collection for the constructor of the CompositePollingService:

List<Type> types = new List<Type>();
if (// config says to use TEST Context)
   types.Add(typeof(PollingService<TestContext>)); // impl. of IPollingService<TContext>
if (// config says to use LIVE Context)
   types.Add(typeof(PollingService<LiveContext>)); // impl. of IPollingService<TContext>

container.RegisterCollection(typeof(IPollingService), types);

I have a decorator for IPollingService<TContext>:

class ContextAwarePollingServiceDecorator<TContext>
    : IPollingService<TContext> where TContext: CustomContext
{
    private readonly IPollingService<TContext> _decoratee;

    public ContextAwarePollingServiceDecorator(IPollingService<TContext> decoratee) 
        => _decoratee = decoratee;

    public async Task Poll() {
        // decoration here...
        await _decoratee.Poll(cancellationToken);
    }

    public TContext Context => _decoratee.Context;
}

However, my decorator is not applied to my IPollingService<TContext> implementations, when I register it like this:

container.RegisterDecorator(typeof(IPollingService<>),
    typeof(ContextAwarePollingServiceDecorator<>));

How can I register types as IPollingService<TContext> but also register them as a collection of IPollingService so that they'll be injected into my CompositePollingService implementation?


Solution

  • You can do this with the following code:

    var ls = Lifestyle.Transient;
    
    // Create a collection of InstanceProducer instances for IPollingService<T> registrations
    var producers = new InstanceProducer[]
    {
       ls.CreateProducer<IPollingService<TestContext>, PollingService<TestContext>>(container),
       ls.CreateProducer<IPollingService<LiveContext>, PollingService<LiveContext>>(container),
    };
    
    // Register a decorator that wraps around all IPollingService<T> instances
    container.RegisterDecorator(typeof(IPollingService<>),
        typeof(ContextAwarePollingServiceDecorator<>));
    
    // Register the IEnumerable<IPollingService> that contains all IPollingService<T> instances
    container.RegisterInstance(producers.Select(p => (IPollingService)p.GetInstance()));
    
    // Register the composite
    container.Register<IPollingService, CompositePollingService>(Lifestyle.Singleton);