Plugin Architecture and Simple Injector

Sep 30, 2014 at 12:09 PM
Edited Sep 30, 2014 at 12:11 PM
Hi,

We are a bunch of C++ developers who have recently started using C# and therefore Simple Injector. Our end game is to develop and application that is plugin based, and we would like to use Simple Injector for IoC.

Following the guidelines documented in the advanced section, we've created a host application as well as a few plugins. For example,

HostApp <---- This is the application that loads modules
FooClient
BarClient
IFooClient
IBarClient
IPlugin <---- This is the shared interface

IFooClient and IBarClient both inherit from IPlugin. FooClient inherits from IFooClient and BarClient inherits from IBarClient.

When loading the DLLs using the example, at the stage when we verify the container, we receive an exception "The configuration is invalid. Creating the instance for type IPlugin failed. The registered delegate for type IPlugin threw an exception. No registration for type IFooClient could be found."
            container = new Container();
            string pluginDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory);

            var pluginAssemblies = from file in new DirectoryInfo(pluginDirectory).GetFiles() where file.Extension.ToLower() == ".dll" select Assembly.LoadFile(file.FullName);

            var pluginTypes = from assembly in pluginAssemblies from type in assembly.GetExportedTypes() where typeof(IModule).IsAssignableFrom(type) where !type.IsAbstract where !type.IsGenericTypeDefinition select type;

            container.RegisterAll<IModule>(pluginTypes);
            container.Verify();
Could someone please point me in the general direction of a solution? Hope the question makes sense.
Coordinator
Sep 30, 2014 at 12:25 PM
Is this the complete configuration? Do you have any other registration for IPlugin? Who is consuming IFooClient?

What you need to realize is that Simple Injector registers mappings between an abstraction and an implementation. In basics you can see Simple Injector as a smart dictionary where the abstraction becomes a key in a dictionary and the definition of what and how to resolve becomes the value. That means that if you request a certain abstraction (by calling container.GetInstance<T>() or by letting another type depend on that abstraction in its constructor), the container will look up that exact abstraction in its dictionary. So if you registered IPlugin, Simple Injector will never resolve an IFooClient, even though the registered IPlugin implementation might implement it. In Simple Injector you often have to be very explicit, and the container won't do much guessing.
Sep 30, 2014 at 12:31 PM
Edited Sep 30, 2014 at 12:32 PM
Removing the IFooClient and IBarClient gets things loaded. I do require the functionality of these, but I will continue to experiment.

Thanks for the useful library
Coordinator
Sep 30, 2014 at 12:46 PM
You're welcome.

What you're doing now is loading all plugins and making them available to other components using the IEnumerable<IPlugin> abstraction. It seems to me that each plugin has its own unique interface and other components needs to depend on those unique interfaces. Is that correct? Is there a one-to-one mapping between such specific plugin abstraction and an implementation or might there be more implementations? That difference is quite significant.
Sep 30, 2014 at 1:25 PM
Hi,

You are correct in your assumption that each plugin has it's own unique interface and that other components need to depend on those interfaces. There is a one-to-one mapping between the abstraction and the implementation.

It seems to me that the GetAllInstances method keeps on creating new instantiations of my classes. Is there anyway to combine RegisterAll and the LifeStyle.Singleton property? There is an overloaded method for RegisterAll that takes a collection to registers, but I can't quite figure out how to use it with the code below
        string pluginDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory);
            var pluginAssemblies = from file in new DirectoryInfo(pluginDirectory).GetFiles() where file.Extension.ToLower() == ".dll" select Assembly.LoadFile(file.FullName);
            var pluginTypes = from assembly in pluginAssemblies from type in assembly.GetExportedTypes() where typeof(IPlugin).IsAssignableFrom(type) where !type.IsAbstract where !type.IsGenericTypeDefinition select type;
            container.RegisterAll<IPlugin>(pluginTypes);
            container.Verify();
            var s = container.GetAllInstances<IPlugin>(); // Should instantiate a bunch of singletons
Sep 30, 2014 at 1:50 PM
Ok, I think the proper solution is to call GetAllInstances only once and save the result in a global container.

Thanks again,
Coordinator
Sep 30, 2014 at 2:10 PM
Edited Sep 30, 2014 at 3:15 PM
Collections are streams in Simple Injector. That means that each time you iterate the collection, the call is forwarded to the container to resolve an instance according to its configured lifestyle. You can see the IEnumerable<T> as a factory. So if you need singleton behavior in your collections, you can do something like this:
container.Register<FooClient>(Lifestyle.Singleton);
container.Register<BarClient>(Lifestyle.Singleton);

container.RegisterAll<IPlugin>(typeof(FooClient), typeof(BarClient));
Or something like this:
Type[] pluginTypes = ...

container.RegisterAll(typeof(IPlugin),
    from type in pluginTypes
    select Lifestyle.Singleton.CreateRegistration(typeof(IPlugin), type, container));
But it seems to me that you don't want to register all collection of IPlugin types at all, since you won't have any consumer that will depend on IEnumerable<IPlugin> but always on a single IFooClient or IBarClient. So I think you need something like this:
string pluginDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory);

var pluginAssemblies = 
    from file in new DirectoryInfo(pluginDirectory).GetFiles() 
    where file.Extension.ToLower() == ".dll" 
    select Assembly.LoadFile(file.FullName);
    
var pluginTypes = 
    from assembly in pluginAssemblies 
    from type in assembly.GetExportedTypes() 
    where typeof(IPlugin).IsAssignableFrom(type) 
    where !type.IsAbstract 
    where !type.IsGenericTypeDefinition
    select type;
    
var pluginRegistrations =
    from implementation in pluginTypes
    let pluginInterfaces =
        from intface in implementation.GetInterfaces()
        where typeof(IPlugin).IsAssignableFrom(intface)
        where intface != typeof(IPlugin)
        select intface
    select new
    {
        service = pluginInterfaces.Single(),
        implementation
    };
    
foreach (var registration in pluginRegistrations)
{
    container.Register(registration.service, registration.implementation, 
        Lifestyle.Singleton);
}
This would cause the following manual registration:
container.Register<IFooClient, FooClient>(Lifestyle.Singleton);
container.Register<IBarClient, BarClient>(Lifestyle.Singleton);
Marked as answer by dot_NET_Junkie on 11/3/2014 at 1:25 AM