huseyint
huseyint

Reputation: 15081

Resolving parent/child with Castle Windsor

I am not sure calling this parent/child but here you go, I have a similar case like this:

namespace ConsoleApplication1
{
    using System.Diagnostics;
    using System.Linq;
    using Castle.MicroKernel.Registration;
    using Castle.MicroKernel.Resolvers.SpecializedResolvers;
    using Castle.Windsor;

    class Program
    {
        static void Main(string[] args)
        {
            var container = new WindsorContainer();

            container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel));

            container.Register(
                Component.For<Parent>().LifeStyle.Singleton,
                Component.For<IChild>().ImplementedBy<Child1>().LifeStyle.Singleton);

            var p = container.Resolve<Parent>();

            // Fails...
            Debug.Assert(p.Children.First().Parent == p, "Parent should be resolved");
        }
    }

    class Parent
    {
        public IChild[] Children { get; set; }
    }

    interface IChild
    {
        Parent Parent { get; set; }
    }

    class Child1 : IChild
    {
        public Parent Parent { get; set; }
    }
}

I have CollectionResolver added to the container. Both Parent and Child1 (with IChild service) are registered as singletons in the container. Whenever I try to resolve the Parent instance, I got my Children array populated but the Child1 instance in that array has a Parent of null. What I am expecting is the Parent property of the Child1 to be set to the Parent instance I am trying to resolve at that moment. I can understand that Parent is not fully activated yet, but since its ctor is run, can't Windsor inject this property yet? Are there any way to make this work or should I manually run some code to set Parents of child objects (which is far from ideal)?

Thanks in advance!

Upvotes: 1

Views: 3095

Answers (3)

Mike Hadlow
Mike Hadlow

Reputation: 9517

Windsor won't let you create a cyclic dependency chain. If you change your child and parent definitions so that they use constructor injection rather than property injection as follows:

class Parent
{
    public Parent(IChild[] children)
    {
        Children = children;
    }

    public IChild[] Children { get; private set; }
}

interface IChild
{
    Parent Parent { get; }
}

class Child1 : IChild
{
    public Child1(Parent parent)
    {
        Parent = parent;
    }

    public Parent Parent { get; private set; }
}

When you run your test now, you'll see that Windsor complains about a dependency cycle:

Test 'M:Mike.DIDemo.WindsorSpike.ParentChild' failed: A cycle was detected when trying to resolve a dependency. The dependency graph that resulted in a cycle is: - Parameter dependency 'children' type 'Mike.DIDemo.IChild[]' for Void .ctor(Mike.DIDemo.IChild[]) in type Mike.DIDemo.Parent - Service dependency 'parent' type 'Mike.DIDemo.Parent' for Void .ctor(Mike.DIDemo.Parent) in type Mike.DIDemo.Child1 + Parameter dependency 'children' type 'Mike.DIDemo.IChild[]' for Void .ctor(Mike.DIDemo.IChild[]) in Mike.DIDemo.Parent

It's always better to use constructor injection when you have a required dependency. Using property injection tells Windsor that the dependency is optional: Supply the component if you can, otherwise just leave the property null. In this case the children were resolved first, so when it came to create the Parent dependency Windsor saw that a cycle would result and left it null.

The solution here is to populate the Parent when the children are resolved, by putting some code in the Parent constructor.

class Parent
{
    public Parent(IChild[] children)
    {
        Children = children;
        foreach (var child in children)
        {
            child.Parent = this;
        }
    }

    public IChild[] Children { get; private set; }
}

interface IChild
{
    Parent Parent { get; set;  }
}

class Child1 : IChild
{
    public Parent Parent { get; set; }
}

Upvotes: 2

h.alex
h.alex

Reputation: 902

This is a variation that's a bit nicer :)

container.Register(Component.For<SearchCommand>());
container.Register(Component.For<ShowOptionsCommand>());
container.Register(Component.For<MainWindowViewModel>().OnCreate(new Action<MainWindowViewModel>(p => p.SetUpCommands())));

public class MainWindowViewModel
{
    public ShowOptionsCommand ShowOptions { get; set; }
    public SearchCommand Search { get; set; }

    public MainWindowViewModel()
    {
    }

    public void SetUpCommands()
    {
        this.ShowOptions.Host = this;
        this.Search.Host = this;
    }
}

Upvotes: 0

Tuna Toksoz
Tuna Toksoz

Reputation: 36

Actually it is possible to create an object without invoking its constructor. All of its fields will be null, but you'll have a reference to object. This feature is not implemented in Windsor, and something that requires this is likely to be a design smell.

Upvotes: 0

Related Questions