Управление порядком и зависимостями плагинов - PullRequest
2 голосов
/ 06 февраля 2012

Я работаю над моделью плагинов для .net framework, которая основана на MEF или Unity.

Проблема в том, что я не нашел решения для заказа запуска плагина.

Предположим, существует конвейер выполнения, который состоит из плагинов, между этими плагинами существует много видов связей: некоторые плагины зависят от другого плагина, который они могут быть вызваны только после того, как этот плагин был вызван. Некоторые плагины должны вызываться в конце конвейера и т. Д.

Файл конфигурации может быть xml или чем-то еще, это не важно. Что меня смущает, так это алгоритм заказа.

Зависимое дерево может решить, но я не знаю, достаточно ли этого. Есть ли зрелое решение? Любой проект с открытым исходным кодом об этом? Или любое предложение?


Больше объяснений.

Предположим, я работаю над текстовым редактором, этот редактор поддерживает несколько плагинов, после того как пользователь завершит свою работу и сохранит, будет вызван конвейер выполнения плагинов. некоторые плагины работают на xaml, некоторые работают на коде ubb, и есть плагин для передачи xaml на ubb. Поэтому все плагины, работающие на xaml, должны сначала вызываться, а затем вызывать плагин для передачи xaml в ubb, а затем вызывать плагины для ubb. Это пример плагинов зависимости и упорядочения, могут существовать более сложные отношения между этими плагинами. Итак, как решить эту проблему в общем виде?

Ответы [ 2 ]

0 голосов
/ 06 февраля 2012

Я думаю, что вы ищете возможность сортировать по зависимостям.Я использовал нечто подобное, в результате чего я создал объект Bootstrapper для управления запуском приложения.Этот Bootstrapper поддерживает 0 или более задач начальной загрузки, которые могут иметь или не иметь зависимости.Я решил это, создав новый тип коллекции, DependencyList<TModel, TKey>, который позволяет добавлять произвольное количество элементов и автоматически сортировать при первом перечислении или после любых последующих изменений коллекции.

С точки зрения того, что вы хотите сделать, мы можем воспользоваться как этим новым типом списка, так и пользовательской информацией экспорта MEF.Первое, с чего мы начнем, это наш плагин базового конвейерного контракта:

public interface IPipelinePlugin
{
    void Process(PipelineContext context);
}

public abstract class PipelinePluginBase : IPipelinePlugin
{
    public virtual void Process(PipelineContext context)
    {

    }
}

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

Следующее, что мы сделаем, определим контракт метаданных, а затем пользовательский атрибут экспорта:

public interface IPipelinePluginMetadata
{
    string Name { get; }
    string[] Dependencies { get; }
    string[] Pipelines { get; }
}

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = false)]
public class PipelineAttribute : ExportAttribute, IPipelinePluginMetadata
{
    public PipelineAttribute(string name)
        : base(typeof(IPipelinePlugin))
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new ArgumentException("A pipeline plugin requires a name.", "name");

        Name = name;
    }

    public string Name { get; private set; }
    public string[] Dependencies { get; set; }
    public string[] Pipelines { get; set; }
}

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

Далее, давайте посмотрим на пользовательский плагин.Давайте предположим, что мы хотим создать конвейер для применения украшений BBCode к нашему входному тексту, поэтому, во-первых, простой плагин:

[Pipeline("ApplyColour", Pipelines = new[] { "bbcode" })]
public class ApplyColourPipelinePlugin : PipelinePluginBase
{
    public override void Process(PipelineContext context)
    {
        context.Content = "[color=f00]" + context.Content + "[/color]";
    }
}

В приведенном выше примере просто упаковывается входной текст в теги [color].Атрибут Pipeline указывает имя плагина (ApplyColour) и то, для каких конвейеров сделать плагин доступным, в данном случае bbcode.Вот более сложный пример:

[Pipeline("MakeBold", Pipelines = new[] { "bbcode" })]
public class MakeBoldPipelinePlugin : PipelinePluginBase
{
    public override void Process(PipelineContext context)
    {
        context.Content = "[b]" + context.Content + "[/b]";
    }
}

[Pipeline("MakeItalic", Dependencies = new[] { "MakeBold" }, Pipelines = new[] { "bbcode" })]
public class MakeItalicAfterBoldPipelinePlugin : PipelinePluginBase
{
    public override void Process(PipelineContext context)
    {
        context.Content = "[i]" + context.Content + "[/i]";
    }
}

В приведенном выше примере я детализирую два дополнительных плагина, один из которых выделяет текст жирным шрифтом, а другой курсивом. Но , я ввел требование зависимости и сказал нашей системе плагинов, что MakeItalic зависит от MakeBold.Вот как мы это собрали:

[Export]
public class PipelineManager
{
    [ImportMany]
    public IEnumerable<Lazy<IPipelinePlugin, IPipelinePluginMetadata>> Plugins { get; set; }

    public Queue<IPipelinePlugin> BuildPipeline(string name)
    {
        // Get the plugins.
        var plugins = Plugins
            .Where(p => p.Metadata.Pipelines == null || p.Metadata.Pipelines.Contains(name)).ToList();

        // Create our dependency list.
        var dependencyList = new DependencyList<Lazy<IPipelinePlugin, IPipelinePluginMetadata>, string>(
            l => l.Metadata.Name,
            l => l.Metadata.Dependencies);

        // Add each available plugin to the list.
        plugins.ForEach(dependencyList.Add);

        // Create our pipeline.
        var pipeline = new Queue<IPipelinePlugin>();

        // Now, when we enumerate over it, it will be sorted.
        dependencyList.ForEach(p => pipeline.Enqueue(p.Value));

        return pipeline;
    }
}

Наш тип PipelineManager работает от MEF, поэтому он будет [Import] серией IPipelinePlugin экземпляров вместе со связанными с ними метаданными (которые должны иметь формубыть проектируемым как IPipelinePluginMetadata).Имея это в виду, здесь он используется:

class Program
{
    static void Main(string[] args)
    {
        var container = new CompositionContainer(new AssemblyCatalog(typeof(Program).Assembly));

        var manager = container.GetExportedValue<PipelineManager>();
        var pipeline = manager.BuildPipeline("bbcode");

        var context = new PipelineContext("Hello World");

        foreach (var plugin in pipeline)
            plugin.Process(context);

        Console.Write(context.Content);
        Console.ReadKey();
    }
}

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

Я выдал это также как Гист (https://gist.github.com/1752325).

0 голосов
/ 06 февраля 2012

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

...