Showing posts with label MEF. Show all posts
Showing posts with label MEF. Show all posts

Tuesday, September 16, 2008

Resolving arrays with Windsor

Here's a class that takes an array of ISubThing as a constructor parameter:

public class Thing
{
    readonly List<ISubThing> subThings = new List<ISubThing>();
    public Thing(params ISubThing[] subThings)
    {
        this.subThings.AddRange(subThings);
    }
    public List<ISubThing> SubThings
    {
        get { return subThings; }
    }
}

Now can you guess what happens if we resolve this using MicroKernel? I naively thought that all my registered ISubThing services would get passed to the constructor, but in fact you get a handler exception saying that the container cannot find anything to supply the subThings property:

[Test, ExpectedException(typeof(HandlerException))]
public void DoesNotResolveArraysByDefault()
{
    var kernel = new DefaultKernel();
    kernel.Register(
        Component.For<Thing>(),
        Component.For<ISubThing>().ImplementedBy<First>(),
        Component.For<ISubThing>().ImplementedBy<Second>(),
        Component.For<ISubThing>().ImplementedBy<Third>()
        );
    var thing = kernel.Resolve<Thing>();
}

Hammett explains the problem here (check the comments) and shows an ArrayResolver that can be added to the kernel to enable the behaviour I was expecting. Now this test works:

[Test]
public void ShouldResolveArrayOfDependencies()
{
    var kernel = new DefaultKernel();
    kernel.Resolver.AddSubResolver(new ArrayResolver(kernel));
    kernel.Register(
        Component.For<Thing>(),
        Component.For<ISubThing>().ImplementedBy<First>(),
        Component.For<ISubThing>().ImplementedBy<Second>(),
        Component.For<ISubThing>().ImplementedBy<Third>()
        );
    var thing = kernel.Resolve<Thing>();
    Assert.That(thing.SubThings.Count, Is.EqualTo(3));
    Assert.That(thing.SubThings[0], Is.InstanceOfType(typeof(First)));
    Assert.That(thing.SubThings[1], Is.InstanceOfType(typeof(Second)));
    Assert.That(thing.SubThings[2], Is.InstanceOfType(typeof(Third)));
}

As Hammett explains in his post, this is a dangerous thing to add to your container, because the implementation he gives doesn't check for circular dependencies. Say we have this class which implements ISubThing:

public class Circular : ISubThing
{
    public Thing Thing { get; private set; }
    public Circular(Thing thing)
    {
        this.Thing = thing;
    }
}

As you can see it takes a Thing in its constructor, so we'd expect a circular reference exception to be raised by the container. But this test shows that doesn't happen:

[Test]
public void DoesNotDiscoverCircularDependencies()
{
    var kernel = new DefaultKernel();
    kernel.Resolver.AddSubResolver(new ArrayResolver(kernel));
    // a circular reference exception should be thrown here
    kernel.Register(
        Component.For<Thing>(),
        Component.For<ISubThing>().ImplementedBy<Circular>()
        );
    // this crashes the test framework!
    // var thing = kernel.Resolve<Thing>();
}

Instead, if you un-comment the last line the test framework crashes.

Andrey Shchekin has a great series of posts comparing different IoC containers:

http://blog.ashmind.com/index.php/2008/09/08/comparing-net-di-ioc-frameworks-part-2/

It's interesting that only StructureMap actually resolves array dependencies out of the box. It's such a nice feature with endless applications that I would have expected it to be more common. Interestingly MEF (which is trying very hard not to be seen as an IoC container) does this as one its core competencies. but then being able to discover a collection of components that supply a particular service is almost an essential requirement of its stated role as a plug-in framework.

Monday, September 08, 2008

Managed Extensibility Framework: Why?

Sometimes... no often... I'm quite dumb. I've been skimming through posts about the MEF thinking that people were talking about the Entity Framework (Microsoft Entity Framework :). I only realised that it was something new today. So what is it. Well it looks like an IoC container, but the spin is that it's different from other IoC containers because it's primarily aimed at providing an extensibility model; a common way for applications and frameworks to load types from dropped-in assemblies. This is from the CodePlex MEF site:

"MEF presents a simple solution for the runtime extensibility problem. Until now, any application that wanted to support a plugin model needed to create its own infrastructure from scratch. Those plug-ins would often be application-specific and could not be reused across multiple implementations.

MEF provides a standard way for the host application to expose itself and consume external extensions. Extensions, by their nature, can be reused amongst different applications. However, an extension could still be implemented in a way that is application-specific. Extensions themselves can depend on one another and MEF will make sure they are wired together in the correct order (another thing you won't have to worry about).

MEF offers a set of discovery approaches for your application to locate and load available extensions.

MEF allows tagging extensions with additional metadata which facilitates rich querying and filtering"

The thing is, although they're selling it as a plug-in framework, it does look very much like any other IoC container,  so it's odd that they're making the plug-in pitch when they could be providing a generic IoC framework. There doesn't seem to be anything about it which precludes using it in the general IoC role, except that they say it's not optimised for that.

So it begs the question: are the MEF team suggesting that we support two IoC frameworks in our applications? One for plug-in extensibility, the other to support internal component based architecture?

It seems to me that it would not be very hard to provide an IoC container that can successfully cover both these requirements. I haven't tried it, but I imagine it would be quite simple to provide a facility for Windsor to load assemblies from a file location, for example.

This one is going to run and run...