Использование Ninject в архитектуре, подобной плагину

Я изучаю DI и недавно сделал мой первый проект.

В этом проекте я реализовал шаблон хранилища. У меня есть интерфейсы и конкретные реализации. Интересно, возможно ли построить реализацию моих интерфейсов в виде "плагинов", dll, что моя программа будет загружаться динамически.

Таким образом, программа может быть улучшена с течением времени без необходимости перестраивать ее, вы просто помещаете dll в папку "plugins", меняете настройки и вуаля!

Это возможно? Может ли Ninject помочь с этим?

9 ответов

Хотя решение Шона Чамберса работает в том случае, если вы управляете плагинами, оно не работает в случае, когда плагины могут быть разработаны сторонними разработчиками, и вы не хотите, чтобы они зависели от написания собственных модулей.

Это довольно легко сделать с помощью расширения конвенций для Ninject:

public static IKernel CreateKernel()
{
    var kernel = new StandardKernel();

    kernel.Scan(scanner => {
        scanner.FromAssembliesInPath(@"Path\To\Plugins");
        scanner.AutoLoadModules();
        scanner.WhereTypeInheritsFrom<IPlugin>();
        scanner.BindWith<PluginBindingGenerator<IPlugin>>();
    });

    return kernel;
}

private class PluginBindingGenerator<TPluginInterface> : IBindingGenerator
{
    private readonly Type pluginInterfaceType = typeof (TPluginInterface);

    public void Process(Type type, Func<IContext, object> scopeCallback, IKernel kernel)
    {
        if(!pluginInterfaceType.IsAssignableFrom(type))
            return;
        if (type.IsAbstract || type.IsInterface)
            return;
        kernel.Bind(pluginInterfaceType).To(type);
    }
}

Затем вы можете получить все загруженные плагины с kernel.GetAll<IPlugin>(),

Преимущества этого метода:

  1. Ваши плагины dll не должны знать, что они загружаются с ninject
  2. Конкретные экземпляры плагинов будут разрешаться с помощью ninject, поэтому они могут иметь конструкторы для внедрения типов, которые хост плагинов знает, как создавать.

Этот вопрос относится к тому же ответу, который я дал здесь: Может ли NInject загружать модули / сборки по требованию?

Я уверен, что это то, что вы ищете:

var kernel = new StandardKernel();
kernel.Load( Assembly.Load("yourpath_to_assembly.dll");

Если вы посмотрите на KernelBase с отражателем в Ninject.dll, то увидите, что этот вызов рекурсивно загрузит все модули в загруженных сборках (метод Load принимает IEnumerable).

public void Load(IEnumerable<Assembly> assemblies)
{
    foreach (Assembly assembly in assemblies)
    {
        this.Load(assembly.GetNinjectModules());
    }
}

Я использую это для сценариев, где я не хочу прямой ссылки на сборку на что-то, что будет меняться очень часто, и я могу поменять сборку, чтобы предоставить другую модель для приложения (если у меня есть соответствующие тесты на месте)

Если продолжить на @ungood good answer, который основан на v.2, с v.3 Ninject (в настоящее время на RC3), то это можно сделать еще проще. Вам больше не нужен IPluginGenerator, просто напишите:

var kernel = new StandardKernel();
kernel.Bind(scanner => scanner.FromAssembliesInPath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
                                   .SelectAllClasses()
                                   .InheritedFrom<IPlugin>()
                                   .BindToAllInterfaces());

Обратите внимание, что я ищу плагины, реализующие IPlugin (поместите ваш интерфейс здесь) по тому же пути приложения.

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

В Интернете есть немало примеров, например, http://www.codeproject.com/KB/cs/c__plugin_architecture.aspx

Как правило, в вашем основном приложении вам нужно загрузить сборку, реализующую плагин, например:

ass = Assembly.Load(name);

а затем вам нужно создать экземпляр вашего плагина. Если вы знаете имя класса, оно будет выглядеть так:

ObjType = ass.GetType(typename);
IPlugin plugin = (IPlugin)Activator.CreateInstance(ObjType);

и тогда вы просто используете это.

Взгляните на Managed Extensibility Framework. http://www.codeplex.com/MEF

Я думаю, что не нужно рамки. Этот учебник решает вашу проблему http://www.codeproject.com/KB/cs/c__plugin_architecture.aspx

Я получил это как хит для Activator.CreateInstance + Ninject и просто хотел указать на что-то в этой области - надеюсь, это вдохновит кого-то придумать настоящий убийственный ответ на этот вопрос о SO.

Если вы еще не столкнулись с проблемой автоматического сканирования модулей и классов и их правильной регистрации в Ninject и по-прежнему создаете свой плагин через Activator.CreateInstance, то вы можете после CreateInstance внедрить зависимости через

IKernel k = ...
var o = Activator.CreateInstance(...);
k.Inject( o );

Конечно, это будет только временное решение на пути к чему-то вроде http://groups.google.com/group/ninject/browse_thread/thread/880ae2d14660b33c

Есть несколько способов сделать это, и вы уже достигли главной цели, чтобы достичь этого, имея конкретные реализации через предопределенные интерфейсы. Реально, если ваши интерфейсы остаются стабильными, вы должны иметь возможность собирать базовое приложение.

Однако я не уверен, как реализация будет работать с Ninject. Вы можете сделать это с помощью модели провайдера или с помощью рефлексии - хотя я думаю, что рефлексия излишня, если вам абсолютно не нужно это делать.

Используя подход модели провайдера, вы помещаете файл в папку /bin или любую другую проверяемую вами папку и настраиваете файл.config, чтобы отразить присутствие провайдера. Если у вас есть определенная папка "plugin", вы можете создать метод, вызываемый при запуске приложения, и периодически, в противном случае, сканировать новые или удаленные экземпляры и перезагружать провайдеров.

Это будет работать в ASP.NET, под C# или VB. Однако, если вы делаете какое-то другое приложение, вам нужно будет рассмотреть другой подход. Провайдер - это всего лишь вращение Microsoft в шаблоне стратегии.

Проблема в том, что вам может понадобиться перекомпилировать, если объект, который вы устанавливаете в загрузке вашего модуля, используется внутри программы. Причина в том, что ваша программа может не иметь последней версии сборки вашего класса. Например, если вы создаете новый конкретный класс для одного из ваших интерфейсов, допустим, вы изменили плагин dll. Теперь Injector загрузит его, но когда он будет возвращен в вашей программе (kernel.get(...)), ваша программа может не иметь сборки и выдаст ошибку.

Пример того, о чем я говорю:

BaseAuto auto = kernel.Get<BaseAuto>();//Get from the NInjector kernel your object. You get your concrete objet and the object "auto" will be filled up (interface inside him) with the kernel.

//Somewhere else:

public class BaseModule : StandardModule
{
        public override void Load(){
            Bind<BaseAuto>().ToSelf();
            Bind<IEngine>().To<FourCylinder>();//Bind the interface
        }     
 }

Если вы создали новый FourCylinder с именем SixCylinder, ваша настоящая программа не будет иметь никаких ссылок на ваш новый объект. Таким образом, после загрузки из плагина BaseModule.cs у вас могут возникнуть проблемы со ссылкой. Чтобы иметь возможность сделать это, вам нужно будет распространить новую dll этой конкретной реализации с вашим плагином, который будет иметь модуль, который потребуется Injector для загрузки класса Interface to Concrete. Это можно сделать без проблем, но вы начинаете иметь целое приложение, которое находится на загрузке из плагина, и это может быть проблематично в некоторых моментах. Быть в курсе

НО, если вам нужна информация о плагине, вы можете получить учебник от CodeProject.

Другие вопросы по тегам