views:

54

answers:

2

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.

A: 

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.

Aliostad
Damian explicitly stated "I'd like to do this without explicitly specifying the dependencies." The fact that Windsor reports cycle has nothing to do with `params` and is just a safety fuse. In other words Windsor is being more strict than it probably should.
Krzysztof Koźmic
I cannot see how you can do that without explicitly specifying it. But the point of my solution is that such definition is moved to a factory. At the end of the day someone somewhere has to have the list of these names - at least it is how it is in the Unity world.
Aliostad
Thanks for your reply, @Aliostad. In this question, I'm relying on Windsor to resolve the composite's dependencies by type. Windsor is pretty clever though - it already knows how to prevent cyclic dependencies when implementing the Decorator Pattern (i.e. a single dependency of the same type); my question is really about getting the same behaviour when resolving multiple services rather than just one. See the workaround in my answer below.
Damian Powell
@Aliostad you'd be surprised :)
Krzysztof Koźmic
A: 

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);
    }
}
Damian Powell