Question

I am building a system in .NET 4.5 which will have different implementations (i.e. implemented on premise at different customers). Each customer will have its own infrastructure and database structure, hence I am building the system heavily relying on the onion architecture, in itself relying on interfaces and DI. In that way, I can use customer specific "Repository" and "Service" implementations.

My aim is to, without recompiling, be able to install the system on a customers server (the system entry point is basically a Windows service containing business logic periodically fired, and also hosting WCF services). For that to work, what I have in mind is some sort of "Dependencies" or "Plugins" folder as a subfolder of the folder containing the Windows service executable, which would contain a customer specific DLL which has concrete classes implementing all the necessary interfaces on which the application relies.

I'm trying to achieve this with Simple Injector. I have had a look at the SimpleInjector.Packaging assembly, and also at the paragraph about "Registering plugins dynamically" here, but I'm still kind of stuck and don't know where to start, like what should I define in which assembly.

I'm in need of some concrete sample on how to achieve this.

Is the SimpleInjector Packaging assembly to be used for this purpose, or am I seeing this wrong ? If so, how ?

Anybody please enlighten me.

Thanks

ps: to be 100% clear: the interfaces and concrete implementations are obviously separated into different assemblies. This question is about how to wire all things up dynamically using Simple Injector.

Was it helpful?

Solution

The beauty of doing this in combination with an IoC Container such as Simple Injector is that it is so easy to add common logic to all of the plug-ins. I recently wrote a bulk image converter utility that allowed plugging in new image converters.

This is the interface

public interface IImageConverter : IDisposable
{
    string Name { get; }
    string DefaultSourceFileExtension { get; }
    string DefaultTargetFileExtension { get; }
    string[] SourceFileExtensions { get; }
    string[] TargetFileExtensions { get; }
    void Convert(ImageDetail image);
}

Registration is done like this (note the error checking for plug-in dependencies that are not .NET assemblies)

private void RegisterImageConverters(Container container)
{
    var pluginAssemblies = this.LoadAssemblies(this.settings.PluginDirectory);

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

    container.RegisterAll<IImageConverter>(pluginTypes);
}

private IEnumerable<Assembly> LoadAssemblies(string folder)
{
    IEnumerable<string> dlls =
        from file in new DirectoryInfo(folder).GetFiles()
        where file.Extension == ".dll"
        select file.FullName;

    IList<Assembly> assemblies = new List<Assembly>();

    foreach (string dll in dlls) {
        try {
            assemblies.Add(Assembly.LoadFile(dll));
        }
        catch { }
    }

    return assemblies;
}

Common logic required for all plug-in operations is handled by decorators

container.RegisterDecorator(
    typeof(IImageConverter), 
    typeof(ImageConverterChecksumDecorator));
container.RegisterDecorator(
    typeof(IImageConverter), 
    typeof(ImageConverterDeleteAndRecycleDecorator));
container.RegisterDecorator(
    typeof(IImageConverter), 
    typeof(ImageConverterUpdateDatabaseDecorator));
container.RegisterDecorator(
    typeof(IImageConverter), 
    typeof(ImageConverterLoggingDecorator));

This final class is more specific but I have added it as an example of how you can find the required plug-in without taking a direct dependency on the container

public sealed class PluginManager : IPluginManager
{
    private readonly IEnumerable<IImageConverter> converters;

    public PluginManager(IEnumerable<IImageConverter> converters) {
        this.converters = converters;
    }

    public IList<IImageConverter> List() {
        return this.converters.ToList();
    }

    public IList<IImageConverter> FindBySource(string extension) {
        IEnumerable<IImageConverter> converters = this.converters
            .Where(x =>
                x.SourceFileExtensions.Contains(extension));

        return converters.ToList();
    }

    public IList<IImageConverter> FindByTarget(string extension) {
        IEnumerable<IImageConverter> converters = this.converters
            .Where(x =>
                x.TargetFileExtensions.Contains(extension));

        return converters.ToList();
    }

    public IList<IImageConverter> Find(
        string sourceFileExtension, 
        string targetFileExtension)
    {
        IEnumerable<IImageConverter> converter = this.converters
            .Where(x =>
                x.SourceFileExtensions.Contains(sourceFileExtension) &&
                x.TargetFileExtensions.Contains(targetFileExtension));

        return converter.ToList();
    }

    public IImageConverter Find(string name) {
        IEnumerable<IImageConverter> converter = this.converters
            .Where(x => x.Name == name);

        return converter.SingleOrDefault();
    }
}

You register IPluginManager in the container and Simple Injector does the rest:

container.Register<IPluginManager, PluginManager>();

I hope this helps.

OTHER TIPS

What you are probably looking for is this:

public class TypeLoader<T> : List<T>
{
    public const BindingFlags ConstructorSearch =
        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance |
        BindingFlags.Instance;

    private void Load(params Assembly[] assemblies)
    {
        foreach (
            Type t in
                assemblies.SelectMany(
                    asm =>
                        asm.GetTypes()
                            .Where(t => t.IsSubclassOf(typeof (T)) || t.GetInterfaces().Any(i => i == typeof (T)))))
        {
            Add((T) Activator.CreateInstance(t, true));
        }
    }
}

All you need to do is call Assembly.ReflectionOnlyLoad the assemblies from your Plugins directory and pass them to this method.

If you, for example declare IPlugin in all your plugin classes in your assemblies, you'd use it like new TypeLoader<IPlugin>().Load(assemblies); and you'll end up with a neat list of all your objects that implement IPlugin.

You need probe the plugin directory at startup and use the .NET reflection API to get the repository types from the dynamically loaded assemblies. There are many ways to do this, depending on what you exactly need. There's no API for this in Simple Injector, simply because there are a lot of ways to do this, and writing a few custom LINQ statements is often much more readable, and flexible.

Here's an example of how this might look:

// Find all repository abstractions
var repositoryAbstractions = (
    from type in typeof(ICustomerRepository).Assembly.GetExportedTypes()
    where type.IsInterface
    where type.Name.EndsWith("Repository")
    select type)
    .ToArray();

string pluginDirectory =
    Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");

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

// Find all repository abstractions
var repositoryImplementationTypes =
    from assembly in pluginAssemblies
    from type in assembly.GetExportedTypes()
    where repositoryAbstractions.Any(r => r.IsAssignableFrom(type))
    where !type.IsAbstract
    where !type.IsGenericTypeDefinition
    select type;

// Register all found repositories.
foreach (var type in repositoryImplementationTypes)
{
    var abstraction = repositoryAbstractions.Single(r => r.IsAssignableFrom(type));
    container.Register(abstraction, type);
}

The code above is a variation of the code sample in the Registering plugins dynamically wiki page from the Simple Injector documentation.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top