Использование Ninject в плагиноподобной архитектуре - PullRequest
27 голосов
/ 01 декабря 2008

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

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

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

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

Ответы [ 9 ]

25 голосов
/ 13 декабря 2011

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

Это довольно легко сделать с помощью расширения Conventions для 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. Ваши подключаемые модули не должны знать, что они загружаются с помощью ninject
  2. Конкретные экземпляры плагинов будут разрешаться с помощью ninject, поэтому у них могут быть конструкторы для внедрения типов, которые хост плагинов знает, как создавать.
12 голосов
/ 14 ноября 2009

Этот вопрос относится к тому же ответу, который я дал здесь: Может ли 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());
    }
}

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

11 голосов
/ 15 марта 2012

Расширение на @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 (укажите ваш интерфейс здесь) по тому же пути к приложению.

3 голосов
/ 01 декабря 2008

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

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

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

ass = Assembly.Load(name);

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

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

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

1 голос
/ 05 ноября 2009

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

0 голосов
/ 29 ноября 2011

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

0 голосов
/ 28 января 2009

Я получил это как хит для 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

0 голосов
/ 01 декабря 2008

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

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

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

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

0 голосов
/ 01 декабря 2008

Проблема в том, что вам может понадобиться перекомпилировать, если объект, который вы установили в загрузке вашего модуля, используется внутри программы. Причина в том, что ваша программа может не иметь последней версии сборки вашего класса. Например, если вы создаете новый конкретный класс для одного из ваших интерфейсов, допустим, вы изменили плагин 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 .

...