2012-09-13 2 views
6

Ich fand diese Frage schwierig zu äußern (besonders in der Titelform), also bitte ertragen Sie mit mir.Wie kann ich MEF verwenden, um voneinander abhängige Module zu verwalten?

Ich habe eine Anwendung, die ich ständig ändern, um verschiedene Dinge zu tun. Es scheint, als wäre MEF eine gute Möglichkeit, die verschiedenen Funktionen zu verwalten. Im Großen und Ganzen gibt es drei Abschnitte der Anwendung, die eine Pipeline von Arten bilden:

  1. Acquisition
  2. Transformation
  3. Expression

In seiner einfachsten Form, ich jeder dieser Stufen ausdrücken als Schnittstelle (usw.). Die Probleme beginnen, wenn ich Erwerbskomponenten verwenden möchte, die reichhaltigere Daten als Standard zur Verfügung stellen. Ich möchte Module entwerfen, die diese reicheren Daten verwenden, aber ich kann mich nicht darauf verlassen, dass sie da sind.

Ich könnte natürlich alle Daten der Schnittstellenspezifikation hinzufügen. Ich könnte mit schlechteren Datenquellen umgehen, indem ich eine Ausnahme ausspreche oder einen Nullwert zurückgebe. Dies scheint weit vom Ideal entfernt zu sein.

Ich bevorzuge die MEF-Bindung in drei Stufen, so dass Module dem Benutzer nur angeboten werden, wenn sie mit den zuvor ausgewählten kompatibel sind.

Also meine Frage: Kann ich Metadaten angeben, die die Menge der verfügbaren Importe einschränkt?

Ein Beispiel:

Acquision1 Basicdata bietet nur

Acquision2 bietet Basicdata und AdvancedData

Transformation1 Basic

Transformation2 erfordert Basic und AdvancedData

Erwerbungen erfordert Das Modul wird zuerst ausgewählt.

Wenn Acquisition1 ausgewählt ist, bieten Sie keine Transformation 2 an, anderenfalls bieten Sie beide an.

Ist das möglich? Wenn das so ist, wie?

Antwort

4

Ihre Frage schlägt eine Struktur wie folgt aus:

public class BasicData 
{ 
    public string Basic { get; set; } // example data 
} 

public class AdvancedData : BasicData 
{ 
    public string Advanced { get; set; } // example data 
} 

Jetzt haben Sie Ihre Akquisition, Transformation und Expression Komponenten.Sie möchten mit verschiedenen Arten von Daten umgehen zu können, so dass sie generic:

public interface IAcquisition<out TDataKind> 
{ 
    TDataKind Acquire(); 
} 

public interface ITransformation<TDataKind> 
{ 
    TDataKind Transform(TDataKind data); 
} 

public interface IExpression<in TDataKind> 
{ 
    void Express(TDataKind data); 
} 

Und jetzt wollen Sie aus ihnen eine Pipeline zu bauen, die wie folgt aussieht:

IExpression.Express(ITransformation.Transform(IAcquisition.Acquire)); 

Fangen wir also an eine Pipeline Builder bauen:

using System; 
using System.Collections.Generic; 
using System.ComponentModel.Composition; 
using System.ComponentModel.Composition.Hosting; 
using System.ComponentModel.Composition.Primitives; 
using System.Linq; 
using System.Linq.Expressions; 

// namespace ... 

public static class PipelineBuidler 
{ 
    private static readonly string AcquisitionIdentity = 
     AttributedModelServices.GetTypeIdentity(typeof(IAcquisition<>)); 
    private static readonly string TransformationIdentity = 
     AttributedModelServices.GetTypeIdentity(typeof(ITransformation<>)); 
    private static readonly string ExpressionIdentity = 
     AttributedModelServices.GetTypeIdentity(typeof(IExpression<>)); 

    public static Action BuildPipeline(ComposablePartCatalog catalog, 
     Func<IEnumerable<string>, int> acquisitionSelector, 
     Func<IEnumerable<string>, int> transformationSelector, 
     Func<IEnumerable<string>, int> expressionSelector) 
    { 
     var container = new CompositionContainer(catalog); 

Die Klasse hält MEF Typ Identitäten für Ihre drei Vertrags Schnittstellen. Wir werden diese später brauchen, um die korrekten Exporte zu identifizieren. Unsere BuildPipeline-Methode gibt eine Action zurück. Das wird die Pipeline sein, also können wir einfach pipeline() tun. Es dauert eine ComposablePartCatalog und drei Func s (um einen Export zu wählen). Auf diese Weise können wir alle schmutzige Arbeit in dieser Klasse behalten. Dann beginnen wir mit der Erstellung eines CompositionContainer.

Jetzt haben wir ImportDefinition s, zuerst für den Erwerb Komponente bauen:

 var aImportDef = new ImportDefinition(def => (def.ContractName == AcquisitionIdentity), null, ImportCardinality.ZeroOrMore, true, false); 

Diese ImportDefinition einfach filtert alle Ausfuhren der IAcquisition<> Schnittstelle aus. Jetzt können wir es in den Behälter geben:

 var aExports = container.GetExports(aImportDef).ToArray(); 

aExports jetzt alle IAcquisition<> Exporte im Katalog hält. Also lassen Sie uns die Wähler auf diesem Lauf:

 var selectedAExport = aExports[acquisitionSelector(aExports.Select(export => export.Metadata["Name"] as string))]; 

Und da wir unsere Akquisitions Komponente haben:

 var acquisition = selectedAExport.Value; 
     var acquisitionDataKind = (Type)selectedAExport.Metadata["DataKind"]; 

Jetzt werden wir das gleiche für die Transformation und die Expressions Komponenten tun, sondern mit einem Kleiner Unterschied: Die ImportDefinition wird sicherstellen, dass jede Komponente die Ausgabe der vorherigen Komponente verarbeiten kann.

 var tImportDef = new ImportDefinition(def => (def.ContractName == TransformationIdentity) && ((Type)def.Metadata["DataKind"]).IsAssignableFrom(acquisitionDataKind), 
      null, ImportCardinality.ZeroOrMore, true, false); 
     var tExports = container.GetExports(tImportDef).ToArray(); 
     var selectedTExport = tExports[transformationSelector(tExports.Select(export => export.Metadata["Name"] as string))]; 

     var transformation = selectedTExport.Value; 
     var transformationDataKind = (Type)selectedTExport.Metadata["DataKind"]; 

     var eImportDef = new ImportDefinition(def => (def.ContractName == ExpressionIdentity) && ((Type)def.Metadata["DataKind"]).IsAssignableFrom(transformationDataKind), 
      null, ImportCardinality.ZeroOrMore, true, false); 
     var eExports = container.GetExports(eImportDef).ToArray(); 
     var selectedEExport = eExports[expressionSelector(eExports.Select(export => export.Metadata["Name"] as string))]; 

     var expression = selectedEExport.Value; 
     var expressionDataKind = (Type)selectedEExport.Metadata["DataKind"]; 

Und jetzt können wir sie alle auf in einem Ausdrucksbaum verdrahten:

 var acquired = Expression.Call(Expression.Constant(acquisition), typeof(IAcquisition<>).MakeGenericType(acquisitionDataKind).GetMethod("Acquire")); 
     var transformed = Expression.Call(Expression.Constant(transformation), typeof(ITransformation<>).MakeGenericType(transformationDataKind).GetMethod("Transform"), acquired); 
     var expressed = Expression.Call(Expression.Constant(expression), typeof(IExpression<>).MakeGenericType(expressionDataKind).GetMethod("Express"), transformed); 
     return Expression.Lambda<Action>(expressed).Compile(); 
    } 
} 

Und das ist es! Eine einfache Beispielanwendung würde so aussehen:

[Export(typeof(IAcquisition<>))] 
[ExportMetadata("DataKind", typeof(BasicData))] 
[ExportMetadata("Name", "Basic acquisition")] 
public class Acquisition1 : IAcquisition<BasicData> 
{ 
    public BasicData Acquire() 
    { 
     return new BasicData { Basic = "Acquisition1" }; 
    } 
} 

[Export(typeof(IAcquisition<>))] 
[ExportMetadata("DataKind", typeof(AdvancedData))] 
[ExportMetadata("Name", "Advanced acquisition")] 
public class Acquisition2 : IAcquisition<AdvancedData> 
{ 
    public AdvancedData Acquire() 
    { 
     return new AdvancedData { Advanced = "Acquisition2A", Basic = "Acquisition2B" }; 
    } 
} 

[Export(typeof(ITransformation<>))] 
[ExportMetadata("DataKind", typeof(BasicData))] 
[ExportMetadata("Name", "Basic transformation")] 
public class Transformation1 : ITransformation<BasicData> 
{ 
    public BasicData Transform(BasicData data) 
    { 
     data.Basic += " - Transformed1"; 
     return data; 
    } 
} 

[Export(typeof(ITransformation<>))] 
[ExportMetadata("DataKind", typeof(AdvancedData))] 
[ExportMetadata("Name", "Advanced transformation")] 
public class Transformation2 : ITransformation<AdvancedData> 
{ 
    public AdvancedData Transform(AdvancedData data) 
    { 
     data.Basic += " - Transformed2"; 
     data.Advanced += " - Transformed2"; 
     return data; 
    } 
} 

[Export(typeof(IExpression<>))] 
[ExportMetadata("DataKind", typeof(BasicData))] 
[ExportMetadata("Name", "Basic expression")] 
public class Expression1 : IExpression<BasicData> 
{ 
    public void Express(BasicData data) 
    { 
     Console.WriteLine("Expression1: {0}", data.Basic); 
    } 
} 

[Export(typeof(IExpression<>))] 
[ExportMetadata("DataKind", typeof(AdvancedData))] 
[ExportMetadata("Name", "Advanced expression")] 
public class Expression2 : IExpression<AdvancedData> 
{ 
    public void Express(AdvancedData data) 
    { 
     Console.WriteLine("Expression2: ({0}) - ({1})", data.Basic, data.Advanced); 
    } 
} 


class Program 
{ 
    static void Main(string[] args) 
    { 
     var pipeline = PipelineBuidler.BuildPipeline(new AssemblyCatalog(typeof(Program).Assembly), StringSelector, StringSelector, StringSelector); 
     pipeline(); 
    } 

    static int StringSelector(IEnumerable<string> strings) 
    { 
     int i = 0; 
     foreach (var item in strings) 
      Console.WriteLine("[{0}] {1}", i++, item); 
     return int.Parse(Console.ReadLine()); 
    } 
} 
+0

Vielen Dank für eine solche vollständige Antwort! –

Verwandte Themen