管理插件顺序和依赖项

时间:2012-02-06 06:28:16

标签: .net plugins mef

我正在研究基于MEF或统一的.net框架上的插件模型。

问题是我还没有找到订购插件执行的解决方案。

假设存在一个由插件组成的执行管道,这些插件之间存在多种关系:某些插件依赖于另一个插件,只有在调用该插件后才能调用它们。应该在管道末端调用一些插件等。

配置文件可能是xml或其他任何东西,它并不重要。令我困惑的是订单算法。

依赖树可以解决,但我不知道它是否足够。有没有成熟的解决方案?关于这个的任何开源项目?或任何建议?


更多解释。

假设我正在编写文本编辑器,这个编辑器支持多个插件,在用户完成工作并保存后,将调用插件执行管道。一些插件在xaml上运行,一些在ubb代码上运行,并且有一个插件传输xaml到ubb。 因此,应首先调用xaml上的所有插件,然后调用插件将xaml调用到ubb,然后调用插件在ubb上工作。 这是插件依赖和排序的示例,这些插件之间可能存在更复杂的关系。 那么,如何以通用的方式解决这个问题呢?

2 个答案:

答案 0 :(得分:0)

如果你用mef做的话,我真的看不出问题。一切都将按要求组成。也许你可以发布一些令你头痛的代码。

答案 1 :(得分:0)

我认为您正在寻找的是能够按依赖关系排序。我使用了类似的东西,我创建了一个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)。