How do I use the Decorator Pattern with Unity without explicitly specifying every parameter in the InjectionConstructor

后端 未结 7 705
一整个雨季
一整个雨季 2020-12-01 03:09

This helpful article from David Haydn (EDIT: scam link removed, it could have been this article) shows how you can use the InjectionConstructor class to help y

相关标签:
7条回答
  • 2020-12-01 03:20

    While I was waiting for answers on this, I came up with a rather hacky workaround. I've created an extension method on IUnityContainer that lets me register a decorator chain using reflection to create the InjectionConstructor parameters:

    static class DecoratorUnityExtensions
    {
        public static void RegisterDecoratorChain<T>(this IUnityContainer container, Type[] decoratorChain)
        {
            Type parent = null;
            string parentName = null;
            foreach (Type t in decoratorChain)
            {
                string namedInstance = Guid.NewGuid().ToString();
                if (parent == null)
                {
                    // top level, just do an ordinary register type                    
                    container.RegisterType(typeof(T), t, namedInstance);
                }
                else
                {
                    // could be cleverer here. Just take first constructor
                    var constructor = t.GetConstructors()[0];
                    var resolvedParameters = new List<ResolvedParameter>();
                    foreach (var constructorParam in constructor.GetParameters())
                    {
                        if (constructorParam.ParameterType == typeof(T))
                        {
                            resolvedParameters.Add(new ResolvedParameter<T>(parentName));
                        }
                        else
                        {
                            resolvedParameters.Add(new ResolvedParameter(constructorParam.ParameterType));
                        }
                    }
                    if (t == decoratorChain.Last())
                    {
                        // not a named instance
                        container.RegisterType(typeof(T), t, new InjectionConstructor(resolvedParameters.ToArray()));
                    }
                    else
                    {
                        container.RegisterType(typeof(T), t, namedInstance, new InjectionConstructor(resolvedParameters.ToArray()));
                    }
                }
                parent = t;
                parentName = namedInstance;
            }
        }
    }
    

    This allows me to configure my container with a much more readable syntax:

    [Test]
    public void ResolveWithDecorators2()
    {
        UnityContainer c = new UnityContainer();
        c.RegisterInstance<IDatabaseConnection>(new Mock<IDatabaseConnection>().Object);
        c.RegisterInstance<ILogger>(new Mock<ILogger>().Object);
        c.RegisterInstance<ICacheProvider>(new Mock<ICacheProvider>().Object);
    
        c.RegisterDecoratorChain<IProductRepository>(new Type[] { typeof(ProductRepository), typeof(CachingProductRepository), typeof(LoggingProductRepository) });
    
        Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>());
    
    }
    

    I'd still be interested to know if there is a more elegant solution to this with Unity

    0 讨论(0)
  • 2020-12-01 03:26

    See this article on implementing a decorator container extension. This should get you to where you want to be with regards to not needing to modify your configuration if your constructor signatures change.

    0 讨论(0)
  • 2020-12-01 03:26

    Another solution involves adding type parameters to your code base in order to help Unity resolving your decorated types. Luckily Unity is perfectly capable of resolving type parameters and their dependencies on its own, so we don't have to care about constructor parameters when defining the decorator chain.

    The registration would look as follows:

    unityContainer.RegisterType<IService, Logged<Profiled<Service>>>();
    

    Here is a basic example implementation. Note the templated decorators Logged<TService> and Profiled<TService>. Look below for some drawbacks I've noticed so far.

    public interface IService { void Do(); }
    
    public class Service : IService { public void Do() { } }
    
    public class Logged<TService> : IService where TService : IService
    {
        private TService decoratee;
        private ILogger logger;
    
        public Logged(ILogger logger, TService decoratee) {
            this.decoratee = decoratee;
            this.logger = logger;
        }
    
        public void Do() {
            logger.Debug("Do()");
            decoratee.Do();
        }
    }
    
    public class Profiled<TService> : IService where TService : IService
    {
        private TService decoratee;
        private IProfiler profiler;
    
        public Profiled(IProfiler profiler, TService decoratee) {
            this.decoratee = decoratee;
            this.profiler = profiler;
        }
    
        public void Do() {
            profiler.Start();
            decoratee.Do();
            profiler.Stop();
        }
    }
    

    Drawbacks

    • A faulty registration like uC.RegisterType<IService, Logged<IService>>(); will result in an infinite recursion that stack-overflows your application. This can be a vulnerability in a plug-in architecture.
    • It uglyfies your code base to some degree. If you ever give up Unity and switch to a different DI framework those template parameters will make no sense to anyone anymore.
    0 讨论(0)
  • 2020-12-01 03:26

    I know that this post is a little bit outdated, but in fact there is no fully functional Unity decorator implementation for the latest releases (there are a lots of breaking changes, see Unity wiki).

    I've took @garryp answer (which is, in my opinion, the only correct answer here) and modified it according to the latest Unity container API changes:

    public static IContainerRegistry RegisterDecorator<TInterface, TDecorator>(this IContainerRegistry container, ITypeLifetimeManager lifetimeManager, Type[] additionalInterfaces, params InjectionMember[] injectionMembers)
        where TDecorator : class, TInterface
    {    
        var unityContainer = container.GetContainer();
    
        var existingRegistration = unityContainer.Registrations.LastOrDefault(r => r.RegisteredType == typeof(TInterface));
    
        if (existingRegistration == null)
        {
            throw new ArgumentException("No existing registration found for the type " + typeof(TInterface));
        }
    
        var existing = existingRegistration.MappedToType;
        var uniqueId = Guid.NewGuid().ToString();
    
        // 1. Create a wrapper. This is the actual resolution that will be used
        if (lifetimeManager != null)
        {
            unityContainer.RegisterType<TDecorator>(uniqueId, lifetimeManager, injectionMembers);
        }
        else
        {
            unityContainer.RegisterType<TDecorator>(uniqueId, injectionMembers);
        }
    
        unityContainer.RegisterType<TInterface, TDecorator>();
    
        if (additionalInterfaces != null)
        {
            foreach (var additionalInterface in additionalInterfaces)
            {
                unityContainer.RegisterType(additionalInterface, typeof(TDecorator));
            }
        }
    
        unityContainer.RegisterFactory<TDecorator>(DecoratorFactory);
    
        return container;
    
        object DecoratorFactory(IUnityContainer c)
        {
            // 3. We get the decorated class instance TBase
            var baseObj = c.Resolve(existing);
    
            // 4. We reference the wrapper TDecorator injecting TBase as TInterface to prevent stack overflow
            return c.Resolve<TDecorator>(uniqueId, new DependencyOverride<TInterface>(baseObj));
        }
    }
    

    The differences are:

    • IContainerRegistry type is being used instead of IUnityContainer - that's because I use PRISM wrappers over Unity container
    • additionalInterfaces optional parameter is added in order to be able to register decorators which also implements other interfaces
    • Logic is modified so that it fits the current Unity API implementation
    0 讨论(0)
  • 2020-12-01 03:37

    I knocked up a fairly crude extension method for this, which behaved as expected when I ran it:

    public static class UnityExtensions
    {
        public static IUnityContainer Decorate<TInterface, TDecorator>(this IUnityContainer container, params InjectionMember[] injectionMembers)
            where TDecorator : class, TInterface
        {
            return Decorate<TInterface, TDecorator>(container, null, injectionMembers);
        }
    
        public static IUnityContainer Decorate<TInterface, TDecorator>(this IUnityContainer container, LifetimeManager lifetimeManager, params InjectionMember[] injectionMembers)
            where TDecorator : class, TInterface
        {
            string uniqueId = Guid.NewGuid().ToString();
            var existingRegistration = container.Registrations.LastOrDefault(r => r.RegisteredType == typeof(TInterface));
            if(existingRegistration == null)
            {
                throw new ArgumentException("No existing registration found for the type " + typeof(TInterface));
            }
            var existing = existingRegistration.MappedToType;
    
            //1. Create a wrapper. This is the actual resolution that will be used
            if (lifetimeManager != null)
            {
                container.RegisterType<TInterface, TDecorator>(uniqueId, lifetimeManager, injectionMembers);
            }
            else
            {
                container.RegisterType<TInterface, TDecorator>(uniqueId, injectionMembers);
            }
    
            //2. Unity comes here to resolve TInterface
            container.RegisterType<TInterface, TDecorator>(new InjectionFactory((c, t, sName) =>
            {
                //3. We get the decorated class instance TBase
                var baseObj = container.Resolve(existing);
    
                //4. We reference the wrapper TDecorator injecting TBase as TInterface to prevent stack overflow
                return c.Resolve<TDecorator>(uniqueId, new DependencyOverride<TInterface>(baseObj));
            }));
    
            return container;
        }
    }
    

    And in your setup:

    container.RegisterType<IProductRepository, ProductRepository>();
    
    // Wrap ProductRepository with CachingProductRepository,
    // injecting ProductRepository into CachingProductRepository for
    // IProductRepository
    container.Decorate<IProductRepository, CachingProductRepository>();
    
    // Wrap CachingProductRepository with LoggingProductRepository,
    // injecting CachingProductRepository into LoggingProductRepository for
    // IProductRepository
    container.Decorate<IProductRepository, LoggingProductRepository>();
    
    0 讨论(0)
  • 2020-12-01 03:38

    Another approach, thanks to a suggestion from @DarkSquirrel42, is to use an InjectionFactory. The downside is that the code still needs updating every time a new constructor parameter is added to something in the chain. The advantages are much easier to understand code, and only a single registration into the container.

    Func<IUnityContainer,object> createChain = container =>
        new LoggingProductRepository(
            new CachingProductRepository(
                container.Resolve<ProductRepository>(), 
                container.Resolve<ICacheProvider>()), 
            container.Resolve<ILogger>());
    
    c.RegisterType<IProductRepository>(new InjectionFactory(createChain));
    Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>());
    
    0 讨论(0)
提交回复
热议问题