castleproject / Windsor

Castle Windsor is a best of breed, mature Inversion of Control container available for .NET
http://www.castleproject.org
Apache License 2.0
1.51k stars 455 forks source link

Injecting IEnumerable<T> into an open generic class Class<T>which being injected into a decorated service #499

Open dimako opened 5 years ago

dimako commented 5 years ago

Hi guys, I have come across the issue while implementing some sort of a strategy locator. I want to be able to inject a collection of strategies of a certain type into a service that should be responsible for picking up the correct one based on the name of a strategy. My code is:

using Castle.MicroKernel.Registration;
using Castle.MicroKernel.Resolvers.SpecializedResolvers;
using Castle.Windsor;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Text;

namespace Ioc.Generics
{
    // attributes
    public class ServiceAttribute : Attribute { }

    public class SecurityDecoratorAttribute : Attribute { }

    // definition:

    public interface IStrategy
    {
        string Name { get; }
    }

    public interface IGetProductCountStrategy : IStrategy
    {
        int GetProductCount();
    }

    public interface IStrategyLocator<TStrategy> where TStrategy : IStrategy
    {
        TStrategy GetStrategy(string name);
    }

    public interface IProductService
    {
        int GetProductCount();
    }

    // implementation
    [Service]
    public class StrategyLocator<TStrategy> : IStrategyLocator<TStrategy> where TStrategy : IStrategy
    {
        private readonly IEnumerable<TStrategy> _strategies;

        public StrategyLocator(IEnumerable<TStrategy> strategies)
        {
            _strategies = strategies;
        }

        public TStrategy GetStrategy(string name)
        {
            var enumerator = _strategies.GetEnumerator();
            while (enumerator.MoveNext())
            {
                if (enumerator.Current.Name == name)
                {
                    return enumerator.Current;
                }
            }

            throw new InvalidOperationException("strategy not found");
        }
    }

    [Service]
    public class DefaultGetProductCountStrategy : IGetProductCountStrategy
    {
        public string Name => "Default";
        public int GetProductCount()
        {
            return 1;
        }
    }

    [Service]
    public class ProductService : IProductService
    {
        private readonly IStrategyLocator<IGetProductCountStrategy> _strategyLocator;
        public ProductService(IStrategyLocator<IGetProductCountStrategy> strategyLocator)
        {
            _strategyLocator = strategyLocator;
        }

        public int GetProductCount()
        {
            return _strategyLocator.GetStrategy("Default").GetProductCount();
        }
    }

    [SecurityDecorator]
    public class ProductServiceSecurityDecorator : IProductService
    {
        private readonly IProductService _productService;

        public ProductServiceSecurityDecorator(IProductService productService)
        {
            _productService = productService;
        }

        public int GetProductCount()
        {
            return _productService.GetProductCount();
        }
    }

    // configuration:
    public class Di
    {
        private static readonly WindsorContainer _container;

        static Di()
        {
            _container = new WindsorContainer();

            _container.Kernel.Resolver.AddSubResolver(new CollectionResolver(_container.Kernel));
            _container.Register(
                Classes.FromAssembly(typeof(ServiceAttribute).Assembly)
                    .Where(t => Attribute.IsDefined(t, typeof(SecurityDecoratorAttribute)))
                    .WithService.FirstInterface().LifestyleTransient(),

                Classes.FromAssembly(typeof(ServiceAttribute).Assembly)
                    .Where(t => Attribute.IsDefined(t, typeof(ServiceAttribute)))
                    .WithService.FirstInterface().LifestyleTransient()
                );
        }

        public static TService GetService<TService>()
        {
            return _container.Resolve<TService>();
        }
    }

    // test

    [TestClass]
    public class ResolverFixture
    {
        // test
        [TestMethod]
        public void ItShouldResolveServiceWithDecoratorAndInjectedGeneric()
        {
            var service = Di.GetService<IProductService>();
        }
    }
}

When I run the test I get the following exception message:

Message: Test method Ioc.Generics.ResolverFixture.ItShouldResolveServiceWithDecoratorAndInjectedGeneric threw exception: Castle.MicroKernel.Handlers.HandlerException: Can't create component 'Ioc.Generics.ProductServiceSecurityDecorator' as it has dependencies to be satisfied.

'Ioc.Generics.ProductServiceSecurityDecorator' is waiting for the following dependencies:
- Service 'Ioc.Generics.IProductService' which points back to the component itself.
A dependency cannot be satisfied by the component itself, did you forget to make this a service override and point explicitly to a different component exposing this service?

The following components also expose the service, but none of them can be resolved:
'Ioc.Generics.ProductService' is waiting for the following dependencies:
- Service 'Ioc.Generics.StrategyLocator`1' which was registered but is also waiting for dependencies.
'Ioc.Generics.StrategyLocator`1' is waiting for the following dependencies:
- Service 'IEnumerable`1' which was not registered.

Stack Trace: at DefaultHandler.AssertNotWaitingForDependency() at DefaultHandler.ResolveCore(CreationContext context, Boolean requiresDecommission, Boolean instanceRequired, Burden& burden) at DefaultHandler.Resolve(CreationContext context, Boolean instanceRequired) at DefaultKernel.ResolveComponent(IHandler handler, Type service, Arguments additionalArguments, IReleasePolicy policy, Boolean ignoreParentContext) at IKernelInternal.Resolve(Type service, Arguments arguments, IReleasePolicy policy, Boolean ignoreParentContext) at DefaultKernel.Resolve(Type service, Arguments arguments) at WindsorContainer.Resolve[T]() at Di.GetService[TService]() in ResolverFixture.cs line: 128 at ResolverFixture.ItShouldResolveServiceWithDecoratorAndInjectedGeneric() in ResolverFixture.cs line: 140

If I don't decorate the service or if I inject the StrategyLocator directly into the decorator everything works fine

generik0 commented 4 years ago

Hi @dimako, Have you tried where you where you register the class you want injected as IProductService with IsDefault (and a name). Ie. Do all your fluent registration with IsFallback, and then specifically register ProductService as default.

Isn't Windsor trying to inject ProductServiceSecurityDecorator as IProductService and not ProductService. Hnece the issue?