views:

247

answers:

2

Continuing on my quest for a good plugin implementation I have been testing the StructureMap assembly scanning features.

All plugins will inherit from abstract class PluginBase. This will provide access to common application services such as logging. Depending on it's function, each plugin may then implement additional interfaces, for example, IStartUpTask.

I am initializing my plugins like so:

            Scan(x => {
            x.AssembliesFromPath(HttpContext.Current.Server.MapPath("~/Plugins"),
                assembly => assembly.GetName().Name.Contains("Extension"));             
            x.AddAllTypesOf<PluginBase>();
        });

The difficulty I am then having is how to work against the interface (not the PluginBase) in code. It's easy enough to work with PluginBase:

            var plugins = ObjectFactory.GetAllInstances<PluginBase>();

        foreach (var plugin in plugins)
        {

        }

But specific functionality (e.g. IStartUpTask.RunTask) is tied to the interface, not the base class.

I appreciate this may not be specific to structuremap (perhaps more a question of reflection).

Thanks, Ben

+2  A: 

Do you know all of the specific interfaces at registration time? If so, you can make a custom registration convention that registers each type with the plugin "family" of the interface it implements. An IRegistrationConvention gets each type, one at a time. You could do a simple check to see if the current type implements the desired interface, and if so, add it.

if (typeof(IStartUpTask).IsAssignableFrom(currentType)){
  For<IStartUpTask>().Add(currentType);
}

Then later in the code, you can retrieve plugins for each specific interface individually:

var startupTasks = ObjectFactory.GetAllInstances<IStartUpTask>();

This approach has the benefit of allowing you to inject an enumerable of your custom interface plugins into a class that needs them, instead of making the service location call.

Alternatively, if you don't want to make a registration convention, you can just do the filtering at runtime using the handy OfType linq extension method:

var startupTasks = ObjectFactory.GetAllInstances<PluginBase>().OfType<IStartupTask>();
Joshua Flanagan
@Joshua - the OfType<T> method does exactly what I need for now, perfect. I do know all the interfaces at runtime though so will look into the IRegistrationConvention. Thanks
Ben
@Joshua - just added a custom plugin scanner. I dont seem to have the For<T>().Add(type) function available?
Ben
Ignore the above - I was using an older version of StructureMap. I have now updated to the latest version.
Ben
A: 

In case it helps others, I followed Joshua's advice and added my own registration convention:

public class PluginConvention : IRegistrationConvention
{
    public void Process(Type type, Registry registry) {
        if (type.BaseType == null) return;

        if (type.BaseType.Equals(typeof(PSAdmin.Core.Domain.PluginBase))) {
            if (typeof(IStartUpTask).IsAssignableFrom(type)) {
                registry.For<IStartUpTask>()
                    .TheDefault.Is.OfConcreteType(type);
            }
        }
    }
}

I couldn't get the .Add method to work, no matter what I tried, so had to use TheDefault.Is.OfConcreteType(type).

Then in my bootstrapper I am scanning like so:

        Scan(x => {
            x.AssembliesFromPath(HttpContext.Current.Server.MapPath("~/Plugins"), 
                assembly => assembly.GetName().Name.Contains("Extension"));
            x.Convention<PluginConvention>();
        });

I can then grab my IStartUp task types like so:

        var plugins = ObjectFactory.GetAllInstances<IStartUpTask>();              

        foreach (var plugin in plugins)
        {
            plugin.Configure();
        }

That said, after reading up on some of the new features of StructureMap, I'm not sure I need to do any of the above. For example I could just change my Scan delegate function to:

        Scan(x => {
            x.AssembliesFromPath(HttpContext.Current.Server.MapPath("~/Plugins"),
                assembly => assembly.GetName().Name.Contains("Extension"));
            x.AddAllTypesOf<PluginBase>();
        });

And to use my interface concrete types (that inherit from PluginBase):

        var tasks = ObjectFactory.Model.GetAllPossible<IStartUpTask>();

        foreach (var task in tasks)
        {
            task.Configure();
        }

Both methods seem to achieve the same thing.

Ben