Плагины Simple Injector

Я создаю систему на .NET 4.5, которая будет иметь разные реализации (т.е. реализована локально у разных клиентов). У каждого клиента будет своя собственная инфраструктура и структура базы данных, поэтому я строю систему, сильно полагаясь на луковичную архитектуру, которая сама по себе полагается на интерфейсы и DI. Таким образом, я могу использовать клиентские реализации «Репозиторий» и «Сервис».

Моя цель - без перекомпиляции установить систему на клиентский сервер (точка входа в систему - это, по сути, служба Windows, содержащая периодически запускаемую бизнес-логику, а также размещение служб WCF). Чтобы это работало, я имею в виду своего рода папку «Зависимости» или «Плагины» в качестве подпапки папки, содержащей исполняемый файл службы Windows, которая будет содержать специфичную для клиента DLL, в которой есть конкретные классы, реализующие все необходимые интерфейсы. на котором полагается приложение.

Я пытаюсь добиться этого с помощью Simple Injector. Я ознакомился с сборкой SimpleInjector.Packaging, а также с абзацем «Динамическая регистрация плагинов» здесь, но я все еще застрял и не знаю, с чего начать, например, что я должен определить в какой сборке.

Мне нужен конкретный образец того, как этого добиться.

Можно ли использовать для этой цели узел упаковки SimpleInjector Packaging, или я вижу это неправильно? Если да, то как?

Кто-нибудь, пожалуйста, просветите меня.

Спасибо

ps: чтобы быть на 100% ясным: интерфейсы и конкретные реализации явно разделены на разные сборки. Это вопрос о том, как динамически подключать все с помощью Simple Injector.


person tjeuten    schedule 24.03.2014    source источник
comment
Этот вопрос ограничен простым инжектором?   -  person aevitas    schedule 24.03.2014
comment
Вы читали эту страницу вики? Я думаю, это ближе к тому, чего вы пытаетесь достичь.   -  person Steven    schedule 24.03.2014
comment
@aevitas нет, я использую Simple Injector в данный момент на этапе разработки, но если у вас есть предложения по другому инструменту DI / IOC, чтобы легко добиться этого, пожалуйста, не стесняйтесь их упоминать. Я новичок в DI / IOC, поэтому открыт для всего.   -  person tjeuten    schedule 24.03.2014
comment
@ Стивен: да, понимал, но не уверен, что правильно понимаю. Означает ли это, что, например, мой конкретный PersonRepository, который реализует мой интерфейс IPersonRepository, также должен реализовывать самоопределяемый интерфейс IPlugin?   -  person tjeuten    schedule 24.03.2014
comment
@tjeuten Ни один из этих тщательно разработанных ответов не решил вашу проблему?   -  person aevitas    schedule 25.03.2014
comment
@aevitas извините, у меня еще не было времени проверить и протестировать. Сделаю сегодня вечером.   -  person tjeuten    schedule 25.03.2014


Ответы (3)


Прелесть выполнения этого в сочетании с контейнером IoC, таким как Simple Injector, заключается в том, что очень легко добавить общую логику ко всем плагинам. Недавно я написал утилиту для массового конвертирования изображений, которая позволяла подключать новые конвертеры изображений.

Это интерфейс

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

Регистрация выполняется следующим образом (обратите внимание на проверку ошибок для зависимостей подключаемых модулей, которые не являются сборками .NET)

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;
}

Общая логика, необходимая для всех операций надстройки, обрабатывается декораторами.

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

Этот последний класс более конкретен, но я добавил его в качестве примера того, как вы можете найти требуемый плагин, не принимая прямую зависимость от контейнера.

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();
    }
}

Вы регистрируете IPluginManager в контейнере, а Simple Injector сделает все остальное:

container.Register<IPluginManager, PluginManager>();

Надеюсь, это поможет.

person qujck    schedule 25.03.2014
comment
Спасибо. Однако я не совсем понимаю использование декораторов и их назначение. Не могли бы вы подробнее рассказать об этом? - person tjeuten; 25.03.2014
comment
@tjeuten Декораторы - это форма аспектно-ориентированного программирования - методика добавления сквозных проблем к нескольким реализациям одной и той же абстракции (например, интерфейса). В этом примере я использовал декораторы, чтобы добавить общий код ко всем плагинам. Диспетчер преобразователей изображений управляется данными, и независимо от того, вызывается ли он подключаемым модулем для преобразования из tif в bmp или из jpg в gif, диспетчер всегда должен обновлять базу данных для каждого изображения. - person qujck; 25.03.2014
comment
Я на самом деле собираюсь указать вам на статью, написанную @Steven, который является создателем Simple Injector и который также дал ответ на этот вопрос (черт знает, почему он был отклонен!) - статью можно найти здесь - он познакомил меня с шаблоном декоратора и может тебе тоже помочь. - person qujck; 25.03.2014

Вы, вероятно, ищете вот что:

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));
        }
    }
}

Все, что вам нужно сделать, это вызвать Assembly.ReflectionOnlyLoad сборки из каталога Plugins и передать их этому методу.

Если вы, например, объявляете IPlugin во всех классах плагинов в ваших сборках, вы будете использовать его как new TypeLoader<IPlugin>().Load(assemblies);, и в итоге получите аккуратный список всех ваших объектов, реализующих IPlugin.

person aevitas    schedule 24.03.2014

Вам нужно проверить каталог плагина при запуске и использовать API отражения .NET для получения типов репозитория из динамически загружаемых сборок. Есть много способов сделать это, в зависимости от того, что именно вам нужно. В Simple Injector нет API для этого просто потому, что есть много способов сделать это, а написание нескольких пользовательских операторов LINQ часто гораздо более читабельно и гибко.

Вот пример того, как это может выглядеть:

// 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);
}

Приведенный выше код представляет собой вариант примера кода на динамической регистрации подключаемых модулей вики-страницы из документации Simple Injector.

person Steven    schedule 24.03.2014