Search code examples
c#dependency-injectiondryioc

DryIoc: register decorator with two interfaces, retrieve the decorator instance when resolving the other interface


Here is a somewhat simplified description of the problem I'm trying to solve: I have a service (e.g. a repository) implementing an interface that I need to inject as a dependency:

public class Service : IService { ... }

I'd like to add a decorator, for example one that add caching that also implements another interface:

public class CachingService: IService, IFlushable
{
  public CachingService(IService decoratee) { ... }

  public void Flush() { ... }
}

public interface IFlushable
{
  public void Flush();
}

Normally, I'd just register the CachingService as an implementation of IService as as decorator, using Setup.Decorator or Setup.DecoratorWith. But in this case I have an extra requirement related to the IFlushable interface. There will be several different services with their own decorators, all of them implementing the both the decorated service interface and IFlushable. I need to inject all the IFlushable decorators as a dependency to be able to flush all the caches on request.

public class CacheHandler
{
  public CacheHandler(IFlushable[] cache) { ... }

  public void FlushAllCaches() { ... }
}

The problem is that this CacheHandler must receive the same decorator instances that were applied to the Service classes.

I have tried several solutions using RegisterMapping and tried to scope the resolution of the caches to their decorated services, but I could not make it work. Either the I receive an error that the container cannot resolve the decorators (which makes sense) or I need to register the decorators themselves, but in the latter case the CacheHandler will receive a new set of IFlushable instances.

The more I think about the more I feel that what I'm trying to achieve here might not even by possible using a DI container. I mean maybe I'm solve this the wrong way. My question is if my approach is valid and/or how can I get all the applied IFLushable decorator instances as a dependency.


Solution

  • First, I would agree with @Steven to consider inverting the control and injecting the IFlushable into the CachingService.

    Second, you may realize the decorator for IService a bit differently - no need to implement it in CachingService:

        [Test]
        public void Answer()
        {
            var c = new Container();
    
            c.Register<IService, Service>(Reuse.Singleton);
            c.RegisterMany<CachingService>(Reuse.Singleton); // registers both CashingService and IFlushable with the same implementing instance
            c.RegisterDelegate<CachingService, IService>(cs => cs.GetDecoratedService(), setup: Setup.Decorator);
    
            var s = c.Resolve<IService>();
            Assert.IsNotNull(s);
            var cs = c.Resolve<CachingService>();
            Assert.IsTrue(cs.ServiceDecorated); // check the service indeed is decorated
            
            var f = c.Resolve<IFlushable>();
            Assert.AreSame(cs, f); // check that the flushable and caching service are the same instance
        }
    
        public interface IService { }
    
        public class Service : IService { }
    
        // no need to implement IService for the decorator, we may use its method instead
        public class CachingService : IFlushable
        {
            public readonly IService Service;
            public bool ServiceDecorated;
    
            public CachingService(IService service) => Service = service;
    
            public IService GetDecoratedService()
            {
                ServiceDecorated = true; // do something with decorated service
                return Service; 
            }
    
            public void Flush() { }
        }
    
        public interface IFlushable
        {
            public void Flush();
        }