Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple Injector plugins

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.

like image 714
tjeuten Avatar asked Feb 14 '23 10:02

tjeuten


2 Answers

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.

like image 178
qujck Avatar answered Feb 16 '23 02:02

qujck


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.

like image 44
aevitas Avatar answered Feb 16 '23 03:02

aevitas