2012-02-06 105 views
2

我正在基於MEF或統一的.net框架上的插件模型。管理插件順序和依賴項

問題是我還沒有找到解決方案來訂購插件執行。

假設存在一個由插件組成的執行管道,這些插件之間有很多種關係:一些插件依賴另一個插件,只有在插件被調用後才能調用它們。一些插件應該在流水線結束時調用。

配置文件可以是xml或其他任何東西,這並不重要。我感到困惑的是訂單算法。

依賴樹可以解決,但我不知道它是否足夠。有沒有成熟的解決方案?任何關於此的開源項目?或者有什麼建議?


更多解釋。

假設我正在使用文本編輯器,此編輯器支持多個插件,在用戶完成作業並保存後,將調用插件執行管道。一些插件在xaml上工作,一些在ubb代碼上工作,並且有一個插件將xaml轉移到ubb。 所以所有在xaml上工作的插件應先被調用,然後調用插件傳輸xaml到ubb,然後調用插件在ubb上工作。 這是一個插件依賴和順序的例子,這些插件之間可能存在更復雜的關係。 那麼,如何以通用的方式解決這個問題呢?

+0

我仍然不能看到mef的任何問題,因爲依賴關係是當你需要它們時組成的。你是否嘗試過遇到錯誤的地方? – blindmeis 2012-02-06 11:43:21

+0

問題不在於mef,這個作品運作良好。問題在於我已經獲得了所有必要的插件實例,如何調用它們?上面的關係要求這些插件按順序調用,我想要解決的是以通用的方式排列這些插件,不僅適用於文本編輯器,還適用於webrequest管道或其他任何東西。就像Matthew的回答一樣,他使用導出的數據庫中的插件名稱並根據它創建依賴關係。這可以解決,但我仍然在尋找更通用的解決方案。 – 2012-02-07 02:44:56

回答

0

我想你正在尋找的是能夠依賴關係進行排序。我使用了類似的東西,我創建了一個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]與它們相關的元數據(這應該造型爲可投射爲IPipelinePluginMetadata)沿着一系列IPipelinePlugin實例。考慮到這一點,在這裏它是在使用中:

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

真的依賴列表中,你的管道設計是兩個不同的領域來看待,但我希望這給你如何可能使用的想法它。

我把它當成了一個Gist(https://gist.github.com/1752325)。

+0

非常感謝!您的解決方案非常簡短明瞭。根據導入的元數據,我們可以建立依賴關係並按順序排序,但似乎我們提出了一個新問題 - 插件應該知道它所依賴的插件的合約,在演示中是IPipeLineMetadata.Name。 – 2012-02-06 14:42:58

+0

理想的解決方案是插件只應該依賴於「功能」,這意味着插件在ubb代碼上工作並不在乎誰實現IXamlUbbConverter,但有一個。所以我認爲這個解決方案可以通過添加功能合同來改善再次感謝,馬修! – 2012-02-06 14:49:08

0

我不能真正看到一個問題,如果你與mef做。所有這些都將按需求組成。也許你可以發佈一些令你頭痛的代碼。

+0

感謝您的回覆,我更新了更多細節。 – 2012-02-06 09:50:52