Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to resolve collection with filtering parameter?

Can Castle Windsor resolve a collection filtered by a string parameter?

interface IViewFactory
{
   IView[] GetAllViewsInRegion(string regionName);
}

My application defines regions as groups of IView-derived types. When I display a particular region at runtime, I want to resolve an instance of every IView type within it (a la Prism).

I've tried doing it with the Castle's Typed Factory Facility, ComponentModel Construction Contributors, and Handler Selectors, but I can't figure out how to map multiple types to a string in a way that Castle can access, nor how to extend Castle to check the string when it decides which types to try to resolve and return in the container.

like image 545
Joel V. Earnest-DeYoung Avatar asked Feb 22 '12 12:02

Joel V. Earnest-DeYoung


1 Answers

Is selection by string strictly necessary? Would it be possible to instead have all IView implementations in the same "region" implement a dedicated interface that derives from IView? Then you could use WindsorContainer.ResolveAll() (passing your region-specific IView as T) to resolve the implementations for the region in question (or you could use one of the Collection Resolvers to perform constructor injection).

In general, when trying to do things like this with Windsor, I make every effort to use the type system (and Windsor's support thereof) before resorting to string-based solutions.

Update: since we confirmed that selection by string is necessary in this case, the best solution I see is to simply inspect the list of handlers in the kernel that satisfy the IView service, then filter for the implementers where the region (defined via attribute) matches what we want, then resolve those implementers. This feels a bit hackish, but if you're okay with having a direct reference to the container in your IViewFactory implementation, this appears to work fine. Below is a passing test case demonstrating the solution.

    [Test]
    public void Test()
    {
        using (var factory = new ViewFactory())
        {

            var regionOneViews = factory.GetAllViewsInRegion("One");
            Assert.That(regionOneViews, Is.Not.Null);
            Assert.That(regionOneViews, Has.Length.EqualTo(2));
            Assert.That(regionOneViews, Has.Some.TypeOf<RegionOneA>());
            Assert.That(regionOneViews, Has.Some.TypeOf<RegionOneB>());

            var regionTwoViews = factory.GetAllViewsInRegion("Two");
            Assert.That(regionTwoViews, Is.Not.Null);
            Assert.That(regionTwoViews, Has.Length.EqualTo(1));
            Assert.That(regionTwoViews, Has.Some.TypeOf<RegionTwoA>());
        }
    }
}

public interface IViewFactory
{
    IView[] GetAllViewsInRegion(string regionName);
}

public class ViewFactory : IViewFactory, IDisposable
{
    private readonly WindsorContainer _container;

    public ViewFactory()
    {
        _container = new WindsorContainer();
        _container.Register(
            Component.For<IView>().ImplementedBy<RegionOneA>(),
            Component.For<IView>().ImplementedBy<RegionOneB>(),
            Component.For<IView>().ImplementedBy<RegionTwoA>()
            );
    }

    public IView[] GetAllViewsInRegion(string regionName)
    {
        return _container.Kernel.GetHandlers(typeof (IView))
            .Where(h => IsInRegion(h.ComponentModel.Implementation, regionName))
            .Select(h => _container.Kernel.Resolve(h.ComponentModel.Name, typeof (IView)) as IView)
            .ToArray();
    }

    private bool IsInRegion(Type implementation,
                            string regionName)
    {
        var attr =
            implementation.GetCustomAttributes(typeof (RegionAttribute), false).SingleOrDefault() as RegionAttribute;
        return attr != null && attr.Name == regionName;
    }

    public void Dispose()
    {
        _container.Dispose();
    }
}

public interface IView {}

[Region("One")]
public class RegionOneA : IView {}

[Region("One")]
public class RegionOneB : IView {}

[Region("Two")]
public class RegionTwoA : IView {}

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class RegionAttribute : Attribute
{
    private readonly string _name;

    public RegionAttribute(string name)
    {
        _name = name;
    }

    public string Name
    {
        get { return _name; }
    }
}
like image 158
Stuart Lange Avatar answered Oct 13 '22 02:10

Stuart Lange