Search code examples
c#dependency-injectionsimple-injector

How to register decorators conditionally based on consumer lifestyle using Simple Injector?


I have a scenario where I have two classes, let's call them SingletonWorker and ScopedWorker, registered with Singleton and Scoped lifestyles respectively. Both depend on IMetricSubmitter in their constructor. The workers submit metrics as part of their logic using the IMetricSubmitter. There's a singleton implementation for IMetricSubmitter called DefaultMetricSubmitter, which I like to decorate with EnrichMetricsDecorator for the purpose of ScopedWorker dependency, such that SingletonWorker will end up with DefaultMetricSubmitter and ScopedWorker will end up with EnrichMetricsDecorator decorating DefaultMetricSubmitter. Is there a way to create such registrations using SimpleInjector today?

The object graphs should basically look like this:

var singleton = new SingletonWorker(
    new DefaultMetricSubmitter());

var scoped = new ScopedWorker(
    new EnrichMetricsDecorator(
        new DefaultMetricSubmitter()));

To me it looks like combining the RegisterConditional, which has a predicate that knows about the consumer, with RegisterDecorator which is the way to register decorators, but I don't know of any way to combine the two. Ideally I'd like to register the decorator with a condition that is based on whether there's an active scope when it's requested as a dependency for a consuming constructor, and in that case create the decorator instance for that active scope. For the purpose of debate, the scope can be assumed to be LifetimeScope.


Solution

  • What you want to do is not possible using the RegisterDecorator methods. Instead, you will have to revert to using the RegisterConditional methods. Considering your given object graph, these registrations should look something like this:

    container.Register<ScopedWorker>(Lifestyle.Scoped);
    container.Register<SingletonWorker>(Lifestyle.Singleton);
    
    container.RegisterConditional<IMetricSubmitter, EnrichMetricsDecorator>(
        Lifestyle.Scoped,
        c => c.Consumer.ImplementationType == typeof(ScopedWorker));
    
    container.RegisterConditional<IMetricSubmitter, DefaultMetricSubmitter>(
        Lifestyle.Singleton,
        c => c.Consumer.ImplementationType == typeof(EnrichMetricsDecorator));
    

    This practice is described here in the documentation.

    UPDATE

    With your updated object graph (that includes an extra singleton decorator), the registration might look as follows:

    // Useful helper method
    static bool InjectedInto<TConsumer>(PredicateContext c) =>
        c.Consumer.ImplementationType == typeof(TConsumer);
    
    container.Register<ScopedWorker>(Lifestyle.Scoped);
    container.Register<SingletonWorker>(Lifestyle.Singleton);
    
    container.RegisterConditional<IMetricSubmitter, DefaultMetricSubmitter>(
        Lifestyle.Singleton,
        InjectedInto<CachingMetricSubmitterDecorator>);
    
    container.RegisterConditional<IMetricSubmitter, CachingMetricSubmitterDecorator>(
        Lifestyle.Singleton,
        c=> !InjectedInto<ScopedWorker>(c)&&!InjectedInto<CachingMetricSubmitterDecorator>(c));
    
    container.RegisterConditional<IMetricSubmitter, EnrichMetricsDecorator>(
        Lifestyle.Scoped, 
        InjectedInto<ScopedWorker>);