Search code examples
c#dependency-injectionsimple-injector

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.


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.