简体   繁体   中英

How to avoid cyclic behaviour with Castle Windsor's CollectionResolver?

I am using Castle Windsor 2.5 in my application. I have a service which is a composite that acts as a distributor for objects that implement the same interface.

public interface IService { void DoStuff(string someArg); }

public class ConcreteService1 : IService {
    public void DoStuff(string someArg) { }
}

public class ConcreteService2 : IService {
    public void DoStuff(string someArg) { }
}

public class CompositeService : List<IService>, IService
{
    private readonly IService[] decoratedServices;

    CompositeService(params IService[] decoratedServices) {
        this.decoratedServices = decoratedServices;
    }

    public void DoStuff(string someArg) {
        foreach (var service in decoratedServices) {
            service.DoStuff(someArg);
        }
    }
}

The problem I have is that using config such as that shown below causes Windsor to report that "A cycle was detected when trying to resolve a dependency."

windsor.Register(
    Component
        .For<IService>()
        .ImplementedBy<CompositeService>(),

    Component
        .For<IService>()
        .ImplementedBy<ConcreteService1>(),

    Component
        .For<IService>()
        .ImplementedBy<ConcreteService2>()
);

This cyclic behaviour seems to be at odds with the standard (non-collection based) behaviour that can be used to implenent the decorator pattern, where the component being resolved is ignored and the next registered component that implements the same interface is used instead.

So what I'd like to know is whether or not there is a way to make Windsor exclude the CompositeService component when it's resolving the IService services for the decoratedServices property. The decoratedServices property should contain two items: an instance of ConcreteService1 , and an instance of ConcreteService2 . I'd like to do this without explicitly specifying the dependencies.

I know Unity but I believe your problem is a general one and relates to all DI systems. Most DI tools have the facility to explicitly name your dependency. This is particularly useful if you are implementing the same interface.

So I believe your problem is not so much circular-ness (if there is such a word) of dependency but the multiple-ness of it. As such I would explicitly name the dependencies as below:

windsor.Register( 
Component 
    .For<IService>() 
    .ImplementedBy<CompositeService>("CompositeService"), 

Component 
    .For<IService>() 
    .ImplementedBy<ConcreteService1>("ConcreteService1"), 

Component 
    .For<IService>() 
    .ImplementedBy<ConcreteService2>("ConcreteService2") 

);

But I cannot see how your DI framework can handle the constructor of your CompositeService. params is a tough one.

Here is what I would do:

1) I remove params 2) I change constructor to IEnumerable<IService> decoratedServices 3) I create another class and implement IEnumerable which is just a factory will return to me list of defined types which I initialise using their names.

Here's my current workaround. I'm not overly familiar with Windsor's internals so there could be glaring errors with this solution so use it at your own risk!

public class NonCyclicCollectionResolver : ISubDependencyResolver
{
    private readonly IKernel kernel;
    private readonly bool    allowEmptyCollections;

    public NonCyclicCollectionResolver(
        IKernel kernel,
        bool    allowEmptyCollections = false
    ) {
        this.kernel                = kernel;
        this.allowEmptyCollections = allowEmptyCollections;
    }

    public bool CanResolve(
        CreationContext        context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel         model,
        DependencyModel        dependency
    ) {
        if (dependency.TargetType == null) return false;

        var itemType = dependency.TargetType.GetCompatibileArrayItemType();
        if (itemType == null) return false;

        if (!allowEmptyCollections)
        {
            return GetOtherHandlers(itemType, model.Name).Any();
        }

        return true;
    }

    public object Resolve(
        CreationContext        context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel         model,
        DependencyModel        dependency
    ) {
        var itemType = dependency.TargetType.GetCompatibileArrayItemType();

        var handlers = GetOtherHandlers(itemType, model.Name);

        var resolved = handlers
            .Select(h => kernel.Resolve(h.ComponentModel.Name, itemType))
            .ToArray();

        var components = Array.CreateInstance(itemType, resolved.Length);
        resolved.CopyTo(components, 0);
        return components;
    }

    private IEnumerable<IHandler> GetOtherHandlers(
        Type   serviceType,
        string thisComponentName
    ) {
        return kernel
            .GetHandlers(serviceType)
            .Where(h => h.ComponentModel.Name != thisComponentName);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM