views:

139

answers:

2

How can I use StructureMap to resolve to an appropriate implementation of an interface based on a name stored in an attribute?

In my project, I have many different kinds of widgets, each descending from IWidget, and each decorated with an attribute specifying the kind of associated element.

To illustrate:

[Configuration("header")]
public class HeaderWidget : IWidget { }

[Configuration("linegraph")]
public class LineGraphWidget : IWidget { }

When processing my (XML) configuration file, I want to obtain an instance of the appropriate concrete class based on the name of the element I'm processing.

public IWidget CreateWidget(XElement definition)
{
    var kind = definition.Name.LocalName;
    var widget = // What goes here?
    widget.Configure(definition);
    return widget;
}

Each definition should result in a different widget being created - I don't need or want the instances to be shared.

In the past I've written plenty of code to do this kind of thing manually, including writing a custom "roll-your-own" IoC container for one project. However, one of my goals with this project is to become proficient with StructureMap instead of reinventing the wheel.

I think I've already managed to set up automatic scanning of assemblies so that StructureMap knows about all my IWidget implementations:

public class WidgetRegistration : Registry
{
    public WidgetRegistration()
    {
        Scan(
            scanner =>
            {
                scanner.AssembliesFromApplicationBaseDirectory();
                scanner.AddAllTypesOf<IWidget>();
            });
    }
}

However, this isn't registering the names of my widgets with StructureMap. What do I need to add to make my scenario work?

(While I am trying to use StructureMap in this project, an answer showing me how to solve this problem with a different DI/IoC tool would still be valuable.)

Update

I'm pretty sure that the consumption side involves calling GetNamedInstance like this:

public IWidget CreateWidget(XElement definition)
{
    var kind = definition.Name.LocalName;
    var widget = ObjectFactory.GetNamedInstance<IWidget>(kind);
    widget.Configure(definition);
    return widget;
}

So the remaining piece of the puzzle is how to automagically register the types within my WidgetRegistration class.

+1  A: 

We implemented a Custom Scanner:

    public class WidgetScanner : ITypeScanner
{
    public void Process(Type type, PluginGraph graph)
    {

Then use scan:

        Scan(y =>
        {
            y.AddAllTypesOf(typeof(IWidget));
            y.Assembly(Assembly.GetExecutingAssembly().FullName);
            y.With<WidgetScanner>();
        });

..and to get an instance:

_container.GetInstance<IWidget>(name)
ozczecho
A helpful answer (+1), though I didn't need to implement my own scanner in my own situation.
Bevan
A: 

Here's where I ended up - turns out everything I needed is supported out of the box by StructureMap.

My registration class takes care of all the dirty work:

public class DashboardRegistration : Registry
{
    public DashboardRegistration()
    {
        Scan(
            scanner =>
            {
                scanner.AssembliesFromApplicationBaseDirectory();
                scanner.AddAllTypesOf<IDisplay>()
                    .NameBy(DashboardXml.DisplayElement);
                scanner.AddAllTypesOf<IDataSource>()
                    .NameBy(DashboardXml.DataSourceElement);
            });
    }
}

The .NameBy() takes a delegate that provides the name required.

The appropriate methods on the DashboardXml class look like this (code abridged):

public static class DashboardXml
{
    ...

    public static string DataSourceElement(Type type)
    {
        const string DataSourceSuffix = "DataSource";

        string result = type.Name;
        if (result.EndsWith(DataSourceSuffix))
        {
            result 
                = result.Substring(0, result.Length - DataSourceSuffix.Length);
        }

        return result.ToLowerInvariant();
    }

    public static string DisplayElement(Type type)
    {
        const string DisplaySuffix = "Display";

        string result = type.Name;
        if (result.EndsWith(DisplaySuffix))
        {
            result 
                = result.Substring(0, result.Length - DisplaySuffix.Length);
        }

        return result.ToLowerInvariant();
    }
}
Bevan