2012-02-06 9 views
2

MEFまたは単一性に基づいた.netフレームワークのプラグインモデルを作成しています。プラグインの順序と依存性を管理する

問題は、プラグインの実行を注文するための解決策が見つかりませんでした。

プラグインで構成された実行パイプラインがあると仮定すると、これらのプラグイン間には多くの種類の関係があります。プラグインによっては、そのプラグインが呼び出された後でしか呼び出せない別のプラグインに依存します。一部のプラグインは、パイプラインの最後などで呼び出される必要があります。

設定ファイルはxmlでも何でもかまいませんが、重要ではありません。私が混乱しているのは、注文アルゴリズムです。

依存ツリーは解決できるが、十分かどうかわからない。成熟した解決策はありますか?これについてのオープンソースプロジェクトはどれですか?または任意の提案?


詳しい説明。

テキストエディタで作業しているとします。このエディタは複数のプラグインをサポートしています。ユーザーがジョブを完了して保存すると、プラグイン実行パイプラインが呼び出されます。いくつかのプラグインはxamlで動作し、いくつかはubbコードで動作し、ubbへのプラグイン転送xamlがあります。 したがって、すべてのプラグインがxamlで最初に呼び出され、次にxamlをubbに転送してから、プラグインがubbで動作するように呼び出す必要があります。 これは依存関係と順序付けをプラグインする例です。これらのプラグイン間にはより複雑な関係が存在することがあります。 この問題を一般的な方法で解決するにはどうすればよいですか?

+0

私はまだ依存関係が必要なときに構成されるので、私はmefの問題を見ることができません。あなたはエラーに遭遇するところで何かを試しましたか? – blindmeis

+0

問題はmefではなく、構成がうまく機能します。問題は、すべてのプラグインインスタンスを作成した後、それらを呼び出す方法です。上記の関係は、これらのプラグインを順番に呼び出すように頼んでいますが、解決しようとしているのは、これらのプラグインをテキストエディタだけでなく、Web要求パイプラインなどの汎用的な方法で並べることです。 Matthewの答えと同じように、彼はエクスポートされたマテリアルにプラグイン名を使用し、それに応じて依存関係を作成します。これは解決できるかもしれませんが、私はまだより一般的な解決策を見いだしています。 –

答えて

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

は、私は彼らがしていることを確認するために私の輸出の形状を定義することができますすべて正しい情報をエクスポートします。

次に、カスタムプラグインを見てみましょう。、最初の単純なプラグインをのは、我々は、入力されたテキストにBBコードの装飾を適用するためのパイプラインを作成したいと仮定しましょう、そう:

[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]"; 
    } 
} 

、私は2つの追加プラグイン、テキストを太字になり、その上イタリック体である1を詳述しています。 しかし、私は依存関係の要件を導入しており、MakeItalicMakeBoldに依存していることを私たちのプラグインシステムに語っています。

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

本当に依存リスト、およびあなたのパイプライン設計は見て2つの別々の領域ですが、私は、これはあなたにどのようでし利用のアイデアを与える願っていますそれ。

私はこれをGist(https://gist.github.com/1752325)として投げました。

+0

ありがとう!あなたのソリューションはかなり簡潔で明確です。インポートされたメタデータに基づいて、依存関係と注文を作成することができますが、新しい問題が発生したように見えます。デモで依存するプラグインの契約がIPipeLineMetadata.Nameです。 –

+0

理想的な解決策は、プラグインは "機能"にのみ依存する必要があります。つまり、プラグインはubbコードで動作しますが、誰がIXamlUbbConverterを実装しているかは気にしませんが、だから私は、このソリューションは機能性契約を追加することで改善できると思います。ありがとう、マシュー! –

0

私はあなたがmefでそれをするなら、私は本当に問題を見ます。すべてが必要に応じて構成されます。たぶん、頭痛の原因となるコードを投稿することができます。

+0

あなたの返信のおかげで、私は詳細を更新しました。 –

関連する問題