views:

40

answers:

2

Consider the following classes:

public interface IView
{
    /// <summary>
    /// Ignore this (its only for the assertion)
    /// </summary>
    ITask Task { get; }
}

public class ViewA : IView
{
    private readonly ITask _task;

    public ViewA(ITask task)
    {
        _task = task;
    }

    public ITask Task
    {
        get { return _task; }
    }
}
public class ViewB : IView
{
    private readonly ITask _task;

    public ViewB(ITask task)
    {
        _task = task;
    }

    public ITask Task
    {
        get { return _task; }
    }
}

public class ViewManager
{
    private readonly List<IView> _views;

    public ViewManager(IView[] views)
    {
        _views = new List<IView>(views);
    }

    public ReadOnlyCollection<IView> Views
    {
        get { return new ReadOnlyCollection<IView>(_views); }
    }
}

And finally what I am trying to accomplish:

    [Fact]
    public void Configure_TwoServicesWithDependencyToTransientComponent_BothShareComponent()
    {
        // arrange
        var windsorContainer = new WindsorContainer();

        windsorContainer.Kernel.Resolver.AddSubResolver(new ArrayResolver(windsorContainer.Kernel));

        windsorContainer.Register(Component.For<ITask>()
            .ImplementedBy<TaskWithNoDependencies>()
            .Named("task")
            .LifeStyle.Transient);

        windsorContainer.Register(Component.For<IView>().ImplementedBy<ViewA>().LifeStyle.Transient);
        windsorContainer.Register(Component.For<IView>().ImplementedBy<ViewB>().LifeStyle.Transient);

        windsorContainer.Register(Component.For<ViewManager>()
            .LifeStyle.Transient);

        // act
        ViewManager service = windsorContainer.Resolve<ViewManager>();

        // assert
        Assert.Same(service.Views[0].Task, service.Views[1].Task);
    }

How can I make Windsor create an instance of ITask so thats its shared by ViewA and ViewB when resolving ViewManager? I tried the following but it did not work:

        windsorContainer.Register(Component.For<ViewManager>()
            .DynamicParameters((k, d) =>
                                   {
                                       d["task"] = k.Resolve<ITask>();

                                   })
            .LifeStyle.Transient);
A: 
  1. Why do you want to do it like this? What you're trying to do is to bind an instance to a context of dependency tree. Perhaps the context is broader, like a web request? What's the reason why you want to do it like this.

  2. It is possible, but I know clean way of doing this with Windsor 2.5. Previous version probably requires some trickery.

Krzysztof Koźmic
This is a winforms application and I don't have any static web-request context or anything to bind this to. The context-boundary is defined by resolving "AnotherServiceWithTaskDependency". I have access to the latest version of Windsor so please tell me the clean solution :-)
Marius
Ok, but why is it a context boundary? What is the reason that you want to share an instance between these two objects, and not between some other? What is the relation between them?
Krzysztof Koźmic
I have a "ViewManager" which takes a list of "Views" as input arguments. The "Views" has to share some components, because they will have to respond to change between each other. "Views" within two different "ViewManagers" should not share the fore mentioned components. So resolving a "ViewManager" represents the context boundary. Basically the ViewManagers ends up in different UI containers. I can mail you some code if that helps.
Marius
ok, and how do you partition the view managers? What are they bound to? A screen? A shell? something else?
Krzysztof Koźmic
A "ViewManager" is used internally by a UI container called a LayoutContainer. You can add as many Layoutcontainers as you like to the main canvas. So the relationship is: One screen, many layoutcontainers, and for each layout container: one ViewManager.
Marius
A: 

I found a solution, but I'm hoping that someone can suggest a better one. Please give me some critic on the selected solution:

    [Fact]
    public void Configure_TwoServicesWithDependencyToTransientComponent_BothShareComponent()
    {
        // arrange
        var windsorContainer = new WindsorContainer();

        windsorContainer.Kernel.Resolver.AddSubResolver(new ArrayResolver(windsorContainer.Kernel));

        windsorContainer.Register(Component.For<ITask>().ImplementedBy<TaskWithNoDependencies>().LifeStyle.Custom(typeof(PerScopeOrTransientLifestyleManager)));

        windsorContainer.Register(Component.For<IView>().ImplementedBy<ViewA>().LifeStyle.Transient);
        windsorContainer.Register(Component.For<IView>().ImplementedBy<ViewB>().LifeStyle.Transient);

        windsorContainer.Register(Component.For<ViewManager>()
            .LifeStyle.Transient);

        // act
        ViewManager service;

        using (new LifeStyleScope())
        {
            service = windsorContainer.Resolve<ViewManager>();
        }

        // assert
        Assert.Same(service.Views[0].Task, service.Views[1].Task);
    }

I did two things:

  1. Created a custom lifestyle manager which added the ability to say that components would act as transient outside of a lifestyle scope and as a singleton within one.

  2. Created a lifestyle scope which I can use to specify a "context-boundary".

There is one thing I don't like about this solution and that is the fact that you have to know about the context-issue from the call-site. I could abstract this by moving the lifestyle scope into the viewmanager, but then the viewmanager needs to have an ioc dependency which will require some extra stubbing when testing the viewmanager.

Marius

related questions