(0) exportieren Drucken
Alle erweitern
Dieser Artikel wurde maschinell übersetzt. Bewegen Sie den Mauszeiger über die Sätze im Artikel, um den Originaltext anzuzeigen. Weitere Informationen
Übersetzung
Original

Managed Extensibility Framework (MEF)

Das Managed Extensibility Framework oder MEF ist eine Bibliothek zum Erstellen von einfachen und erweiterbaren Anwendungen. Es ermöglicht Anwendungsentwicklern, Erweiterungen ohne Konfiguration zu ermitteln und zu verwenden. Das Framework ermöglicht Erweiterungsentwicklern, Code leicht zu kapseln und zerbrechliche harte Abhängigkeiten zu vermeiden. MEF ermöglicht nicht nur die Wiederverwendung von Erweiterungen innerhalb von Anwendungen, sondern auch anwendungsübergreifend.

Angenommen, Sie entwickeln eine komplexe erweiterungsfähige Anwendung. Die Anwendung umfasst eine große Anzahl kleinerer Komponenten und ist gleichzeitig für das Erstellen und Ausführen der Komponenten zuständig.

Der einfachste Lösungsansatz ist, die Komponenten als Quellcode in die Anwendung zu integrieren und sie direkt vom Code aufzurufen. Hierbei ergeben sich allerdings mehrere offensichtliche Nachteile. Zunächst einmal können keine neuen Komponenten hinzugefügt werden, ohne dabei den Quellcode zu ändern. Dies wäre zwar für eine Webanwendung, jedoch nicht für eine Clientanwendung akzeptabel. Außerdem kann möglicherweise nicht auf den Quellcode der Komponenten zugegriffen werden, da diese eventuell von Drittanbietern entwickelt wurden. Aus dem gleichen Grund darf den Komponenten auch nicht der Zugriff auf Ihren Quellcode gestattet werden.

Eine elegantere Möglichkeit wäre die Bereitstellung eines Erweiterungspunkts oder einer Schnittstelle, um die Komponenten von der Anwendung zu entkoppeln. Bei diesem Modell kann eine Schnittstelle zur Implementierung einer Komponente und eine API zur Interaktion mit der Anwendung bereitgestellt werden. So ist die Gewährung von Quellcodezugriff nicht mehr erforderlich, allerdings müssen hier andere Schwierigkeiten beachtet werden.

Da von der Anwendung Komponenten nicht selbständig ermittelt werden können, müssen die verfügbaren und zu ladenden Komponenten exakt angegeben werden. Hierfür werden in der Regel die verfügbaren Komponenten in einer Konfigurationsdatei genau registriert. Das Gewährleisten der Verwendung der richtigen Komponenten kann also zu einem Wartungsproblem werden, insbesondere, wenn die Aktualisierung vom Endbenutzer und nicht vom Entwickler durchgeführt werden muss.

Außerdem können Komponenten nicht untereinander kommunizieren, außer durch die fest definierten Channels der Anwendung selbst. Wurde vom Anwendungsentwickler eine bestimmte Kommunikation nicht eingeplant, kann sie normalerweise nicht stattfinden.

Schließlich müssen sich Komponentenentwickler für harte Abhängigkeiten zwischen Assemblys und den implementierten Schnittstellen entscheiden. Dies kann bei der Verwendung einer Komponente in mehr als einer Anwendung und der Erstellung eines Testframeworks für Komponenten Schwierigkeiten bereiten.

Mit dem MEF ist die genaue Registrierung der verfügbarer Komponenten nicht mehr erforderlich, da sie mithilfe von Komposition implizit ermittelt werden können. Durch eine MEF-Komponente (Teil) werden sowohl die Abhängigkeiten (Importe) als auch die verfügbaren Funktionen (Exporte) deklarativ angegeben. Bei der Erstellung eines Teils werden vom MEF-Kompositionsmodul die von anderen Teilen verfügbaren Komponenten für die Importe bereitgestellt.

Auf diese Weise können die im vorherigen Abschnitt erläuterten Probleme vermieden werden. Da die Funktionen von den MEF-Teilen deklarativ angegeben werden, sind sie zur Laufzeit auffindbar. Eine Anwendung kann Teile also ohne hartcodierte Verweise oder instabile Konfigurationsdateien nutzen. Mit dem MEF können Anwendungen Teile anhand von Metadaten ermitteln und untersuchen. Dabei müssen die Teile nicht instanziiert oder ihre Assemblys geladen werden. Daher muss nicht genau angegeben werden, wann und auf welche Weise Erweiterungen geladen werden sollen.

Zusätzlich zu den bereitgestellten Exporten können von einem Teil seine Importe angegeben werden, die von anderen Teilen ausgefüllt werden. Dies ermöglicht die einfache Kommunikation zwischen Teilen und die optimale Verarbeitung von Code. Gemeinsame Dienste von Komponenten können z. B. separat aufgeteilt und leicht geändert oder ersetzt werden.

Da im MEF-Modell keine harten Abhängigkeiten für Anwendungsassemblys erforderlich sind, können Erweiterungen in mehreren Anwendungen wiederverwendet werden. So können anwendungsunabhängig auch mühelos Testumgebungen für Erweiterungskomponenten entwickelt werden.

Eine erweiterbare, mithilfe des MEF erstellte Anwendung deklariert einen Import, der von Erweiterungskomponenten ausgefüllt werden kann und kann ebenfalls Exporte deklarieren, um Anwendungsdienste für Erweiterungen verfügbar zu machen. Von jeder Erweiterungskomponente wird ein Export deklariert. Importe können ebenfalls deklariert werden. Auf diese Weise sind Erweiterungskomponenten selbst automatisch erweiterbar.

MEF ist ein wesentlicher Bestandteil des .NET Framework 4 und ist überall dort verfügbar, wo .NET Framework verwendet wird. Sie können MEF in Clientanwendungen verwenden, egal ob bei diesen Windows Forms, WPF oder eine andere Technologie zum Einsatz kommt, oder in Serveranwendungen, die ASP.NET verwenden.

In früheren Versionen von .NET Framework wurde das Managed Add-in Framework (MAF) eingeführt, das für die Isolation und Verwaltung von Erweiterungen innerhalb von Anwendungen entworfen wurde. Bei MAF befindet sich der Fokus auf einer etwas höheren Ebene als bei MEF und liegt auf der Erweiterungsisolation und dem Laden und Entladen von Assemblys, während bei MEF der Fokus auf Erkennbarkeit, Erweiterbarkeit und Portabilität gerichtet ist. Die zwei Frameworks arbeiten optimal zusammen, und beide können von Einzelanwendungen verwendet werden.

Die einfachste Möglichkeit zur Entdeckung der Möglichkeiten von MEF ist das Erstellen einer einfachen MEF-Anwendung. In diesem Beispiel erstellen Sie einen einfachen Rechner namens SimpleCalculator. Das Ziel von SimpleCalculator ist die Erstellung einer Konsolenanwendung, die grundlegende arithmetische Befehle wie "5+3" oder "6-2" verarbeiten kann und korrekte Ergebnisse liefert. Mit MEF können mühelos neue Operatoren hinzugefügt werden, ohne dabei den Anwendungscode ändern zu müssen.

Der vollständige Code für dieses Beispiel kann unter SimpleCalculator-Beispiel heruntergeladen werden.

Hinweis Hinweis

Mit SimpleCalculator sollen die Konzepte und die Syntax des MEF veranschaulicht werden. Auf ein realistisches Verwendungsszenario wird in diesem Fall kein Wert gelegt. Viele der Anwendungen, die am meisten von der Leistungsfähigkeit von MEF profitieren würden, sind komplexer als SimpleCalculator. Umfangreichere Beispiele finden Sie auf Codeplex unter Managed Extensibility Framework.

Erstellen Sie in Visual Studio 2010 zunächst ein neues Konsolenanwendungsprojekt mit dem Namen SimpleCalculator. Fügen Sie einen Verweis auf die System.ComponentModel.Compositions-Assembly hinzu, in der sich MEF befindet. Öffnen Sie "Module1.vb" oder "Program.cs", und fügen Sie Imports- oder using-Anweisungen für System.ComponentModel.Composition und System.ComponentModel.Composition.Hosting hinzu. Diese zwei Namespaces enthalten MEF-Typen, die zur Entwicklung einer erweiterbaren Anwendung erforderlich sind. Fügen Sie in Visual Basic das Public-Schlüsselwort der Zeile hinzu, die das Module1-Modul deklariert.

Der Kern des MEF-Kompositionsmodells ist der Kompositionscontainer, der alle verfügbaren Teile enthält und von dem die Komposition ausgeführt wird. (Mit Komposition ist das Zuweisen von Importen zu Exporten gemeint.) Der gängigste Kompositionscontainertyp ist CompositionContainer, den wir auch für SimpleCalculator verwenden werden.

Fügen Sie in Visual Basic in der Datei Module1.vb die öffentliche Klasse Program hinzu. Fügen Sie anschließend der Program-Klasse in "Module1.vb" bzw. "Program.cs" die folgende Zeile hinzu:

private CompositionContainer _container;

Zur Ermittlung der verfügbaren Teile wird von den Kompositionscontainern ein Katalog verwendet. Ein Katalog ist ein Objekt, durch das ermittelte, aus einer beliebigen Quelle stammende Teile verfügbar gemacht werden. MEF stellt zur Ermittlung von Teilen in bereitgestellten Typen, Assemblys oder Verzeichnissen Kataloge bereit. Anwendungsentwickler können leicht neue Kataloge zur Ermittlung von Teilen aus anderen Quellen erstellen, z. B. aus einem Webdienst.

Fügen Sie der Program-Klasse den folgenden Konstruktor hinzu:

private Program()
{
    //An aggregate catalog that combines multiple catalogs
    var catalog = new AggregateCatalog();
    //Adds all the parts found in the same assembly as the Program class
    catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));

    //Create the CompositionContainer with the parts in the catalog
    _container = new CompositionContainer(catalog);

    //Fill the imports of this object
    try
    {
        this._container.ComposeParts(this);
    }
    catch (CompositionException compositionException)
    {
        Console.WriteLine(compositionException.ToString());
   }
}

Durch den Aufruf von ComposeParts wird der Kompositionscontainer angewiesen, einen bestimmten Satz von Teilen zu verfassen (in diesem Fall die aktuelle Instanz von Program). Zu diesem Zeitpunkt wird jedoch keine Aktion ausgeführt, da Program über keine Importe zum Füllen verfügt.

Importieren Sie zunächst mithilfe von Program einen Rechner. Dies ermöglicht die Trennung von Benutzeroberflächenkomponenten, z. B. der Konsolenein- und -ausgabe, die an Program geleitet werden, von der Logik des Rechners.

Fügen Sie der Program-Klasse folgenden Code hinzu:

[Import(typeof(ICalculator))]
public ICalculator calculator;

Beachten Sie, dass die Deklaration des calculator-Objekts nicht ungewöhnlich ist, aber durch das ImportAttribute-Attribut ergänzt wird. Durch das Attribut wird ein Import deklariert, d. h., bei der Komposition des Objekts wird es vom Kompositionsmodul ausgefüllt.

Jeder Import weist einen Vertrag auf, durch den bestimmt wird, welche Exporte dem Import zugewiesen werden können. Der Vertrag kann eine explizit angegebene Zeichenfolge sein oder von MEF aus einem angegebenen Typ, in diesem Fall die ICalculator-Schnittstelle, automatisch generiert werden. Jeder mit einem entsprechenden Vertrag deklarierte Export kann diesem Import zugewiesen werden. Der Typ des calculator-Objekts ist zwar tatsächlich ICalculator, allerdings ist dies nicht erforderlich. Der Vertrag ist unabhängig vom Typ des Importobjekts. (In diesem Fall kann typeof(ICalculator) ausgelassen werden. Sofern nicht anders angeben, wird von MEF automatisch angenommen, dass es sich um einen importtypbasierten Vertrag handelt.

Fügen Sie dem Modul oder dem SimpleCalculator-Namespace diese sehr einfache Schnittstelle hinzu:

public interface ICalculator
{
    String Calculate(String input);
}

Nach der Definition von ICalculator benötigen wir eine Klasse für die Implementierung. Fügen Sie dem Modul oder SimpleCalculator-Namespace die folgende Klasse hinzu:

[Export(typeof(ICalculator))]
class MySimpleCalculator : ICalculator
{

}

Dieser Export entspricht dem Import in Program. Damit eine Übereinstimmung zwischen Export und Import vorliegt, muss der Export den gleichen Vertragstyp aufweisen. Beim Export unter einem Vertrag auf Grundlage von typeof(MySimpleCalculator) wäre ein Konflikt die Folge, und der Import würde nicht ausgefüllt werden. Der Vertrag muss genau übereinstimmen.

Da der Kompositionscontainer alle in dieser Assembly verfügbaren Teilen enthält, ist der MySimpleCalculator-Teil verfügbar. Wenn der Konstruktor für Program eine Komposition für das Program-Objekt ausführt, wird der entsprechende Import mit einem zu diesem Zweck erstellten MySimpleCalculator-Objekt ausgefüllt.

Für die Benutzeroberflächenebene (Program) müssen keine weiteren Informationen bereitgestellt werden. Sie können daher den Rest der Benutzeroberflächenlogik in der Main-Methode ausfüllen.

Fügen Sie der Main-Methode folgenden Code hinzu:

static void Main(string[] args)
{
    Program p = new Program(); //Composition is performed in the constructor
    String s;
    Console.WriteLine("Enter Command:");
    while (true)
    {
        s = Console.ReadLine();
        Console.WriteLine(p.calculator.Calculate(s));
    }
}

Durch diesen Code wird lediglich eine Eingabezeile gelesen und die Calculate-Funktion von ICalculator für das Ergebnis aufgerufen, das an die Konsole zurückgegeben wird. Mehr Code ist in Program nicht erforderlich. Die restlichen Aufgaben werden in den Teilen ausgeführt.

Zur Gewährleistung der Erweiterbarkeit von SimpleCalculator muss eine Reihe von Vorgängen importiert werden. Ein gewöhnliches ImportAttribute-Attribut wird von ausschließlich einem ExportAttribute ausgefüllt. Sind mehrere Exporte verfügbar, wird vom Kompositionsmodul ein Fehler ausgegeben. Zur Erstellung eines Imports, der mit einer beliebigen Anzahl von Exporten ausgefüllt werden kann, verwenden Sie das ImportManyAttribute-Attribut.

Fügen Sie der MySimpleCalculator-Klasse die folgende Eigenschaft für Vorgänge hinzu:

[ImportMany]
IEnumerable<Lazy<IOperation, IOperationData>> operations;

Lazy<T, TMetadata> ist ein von MEF bereitgestellter Typ für indirekte Verweise auf Exporte. Zusätzlich zum exportierten Objekt selbst können hier auch Exportmetadaten oder Informationen abgerufen werden, durch die das exportierte Objekt beschrieben wird. Jeder Lazy<T, TMetadata> enthält ein IOperation-Objekt, das eine tatsächliche Operation darstellt, und ein IOperationData-Objekt, das die Metadaten darstellt.

Fügen Sie dem Modul oder SimpleCalculator-Namespace die folgenden einfachen Schnittstellen hinzu:

public interface IOperation
{
     int Operate(int left, int right);
}

public interface IOperationData
{
    Char Symbol { get; }
}

In diesem Fall handelt es sich bei den Metadaten für die einzelnen Vorgänge um das Symbol, das diese Operation darstellt, z. B. +, -, * usw. Fügen Sie dem Modul oder SimpleCalculator-Namespace die folgende Klasse hinzu, um die Additionsoperation verfügbar zu machen:

[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '+')]
class Add: IOperation
{
    public int Operate(int left, int right)
    {
        return left + right;
    }
}

An der Funktionsweise des ExportAttribute-Attributs hat sich nichts geändert. Das ExportMetadataAttribute-Attribut fügt dem Export Metadaten in Form eines Name-Wert-Paars hinzu. IOperation wird zwar von der Add-Klasse implementiert; eine Klasse, die IOperationData implementiert, wurde jedoch nicht explizit definiert. Stattdessen wird eine Klasse, deren Eigenschaften auf den Namen der bereitgestellten Metadaten basieren, implizit von MEF erstellt. (Dies ist eine von mehreren Möglichkeiten für den Zugriff auf Metadaten in MEF.)

Die Komposition in MEF ist rekursiv. Sie haben das Program-Objekt, durch das ein ICalculator vom Typ MySimpleCalculator importiert wurde, explizit zusammengesetzt. Von MySimpleCalculator wird wiederum eine Auflistung von IOperation-Objekten importiert. Dieser Import wird zur gleichen Zeit wie die Importe von Program bei der Erstellung von MySimpleCalculator ausgefüllt. Würde von der Add-Klasse ein weiterer Import deklariert werden, würde dieser ebenfalls ausgefüllt werden müssen usw. Jeder nicht ausgefüllte Import führt zu einem Fehler bei der Komposition. (Es ist jedoch möglich, Importe als optional zu deklarieren oder ihnen Standardwerte zuzuweisen.)

Sind diese Teile bereitgestellt, bleibt nun noch die Rechnerlogik selbst. Fügen Sie der MySimpleCalculator-Klasse den folgenden Code hinzu, um die Calculate-Methode zu implementieren:

public String Calculate(String input)
{
    int left;
    int right;
    Char operation;
    int fn = FindFirstNonDigit(input); //finds the operator
    if (fn < 0) return "Could not parse command.";

    try
    {
        //separate out the operands
        left = int.Parse(input.Substring(0, fn));
        right = int.Parse(input.Substring(fn + 1));
    }
    catch 
    {
        return "Could not parse command.";
    }

    operation = input[fn];

    foreach (Lazy<IOperation, IOperationData> i in operations)
    {
        if (i.Metadata.Symbol.Equals(operation)) return i.Value.Operate(left, right).ToString();
    }
    return "Operation Not Found!";
}

Durch die anfänglichen Schritte wird die Eingabezeichenfolge analysiert und in linke und rechte Operanden sowie in ein Operatorzeichen eingeteilt. In der foreach-Schleife wird jeder Member der operations-Auflistung untersucht. Diese Objekte sind vom Typ Lazy<T, TMetadata>, und der Zugriff auf die Metadatenwerte und das exportierte Objekt ist mit der Metadata- bzw. Value-Eigenschaft möglich. Wird in diesem Fall festgestellt, dass es sich bei der Symbol-Eigenschaft des IOperationData-Objekts um eine Übereinstimmung handelt, ruft der Rechner die Operate-Methode des IOperation-Objekts auf und gibt das Ergebnis zurück.

Zur Fertigstellung des Rechners benötigen Sie auch eine Hilfsmethode, die die Position des ersten Zeichens einer Zeichenfolge zurückgibt, bei dem es sich nicht um eine Ziffer handelt. Fügen Sie der MySimpleCalculator-Klasse die folgende Hilfsmethode hinzu:

private int FindFirstNonDigit(String s)
{
    for (int i = 0; i < s.Length; i++)
    {
        if (!(Char.IsDigit(s[i]))) return i;
    }
    return -1;
}

Sie sollten das Datenbankprojekt jetzt kompilieren und ausführen können. In Visual Basic müssen Sie sicherstellen, dass Sie Module1 das Schlüsselwort Public hinzugefügt haben. Geben Sie im Konsolenfenster eine Addition ein, z. B. "5+3", und der Rechner gibt das entsprechende Ergebnis aus. Ein beliebiger anderer Operator führt zu einer Meldung, die anzeigt, dass der Vorgang nicht gefunden werden konnte.

Da der Rechner nun funktioniert, kann ganz leicht eine neue Rechenoperation hinzugefügt werden. Fügen Sie dem Modul oder SimpleCalculator-Namespace die folgende Klasse hinzu:

[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '-')]
class Subtract : IOperation
{
    public int Operate(int left, int right)
    {
        return left - right;
    }
}

Kompilieren Sie das Projekt und führen Sie es aus. Geben Sie eine Subtraktion ein, z. B. "5-3". Der Rechner unterstützt jetzt Subtraktionen ebenso sowie Additionen.

Das Hinzufügen von Klassen zum Quellcode ist einfach, aber mit MEF kann auch außerhalb des Quellcodes einer Anwendung nach Teilen gesucht werden. Zur Veranschaulichung muss SimpleCalculator für die Suche nach Teilen in Verzeichnissen und in seinen eigenen Assemblys durch das Hinzufügen eines DirectoryCatalog angepasst werden.

Fügen Sie dem SimpleCalculator-Projekt ein neues Verzeichnis mit dem Namen Extensions hinzu. Das Verzeichnis muss auf der Projektebene und nicht auf der Projektmappenebene hinzugefügt werden. Fügen Sie der Projektmappe anschließend ein neues ClassLibrary-Projekt mit dem Namen ExtendedOperations hinzu. Das neue Projekt wird in eine separate Assembly kompiliert.

Öffnen Sie den Projekteigenschaften-Designer für das ExtendedOperations-Projekt, und klicken Sie auf die Registerkarte Erstellen oder auf die Registerkarte Kompilieren. Ändern Sie den Buildausgabepfad oder den Ausgabepfad, sodass dieser auf das Erweiterungsverzeichnis im SimpleCalculator-Projektverzeichnis zeigt (..\SimpleCalculator\Extensions\).

Fügen Sie anschließend dem Program-Konstruktor in der Datei Module1.vb oder in der Datei "Program.cs" die folgende Zeile hinzu:

catalog.Catalogs.Add(new DirectoryCatalog("C:\\SimpleCalculator\\SimpleCalculator\\Extensions"));

Ersetzen Sie den Beispielpfad durch den Pfad zum Verzeichnis "Erweiterungen". (Dieser absolute Pfad ist nur für Debugzwecke bestimmt. In einer Produktionsanwendung würde ein relativer Pfad verwendet werden.) Vom DirectoryCatalog werden jetzt alle in den Assemblys im Verzeichnis "Erweiterungen" enthaltenen Teile dem Kompositionscontainer hinzugefügt.

Fügen Sie im ExtendedOperations-Projekt Verweise auf SimpleCalculator und System.ComponentModel.Composition hinzu. Fügen Sie in der ExtendedOperations-Klassendatei eine Imports- oder eine using-Anweisung für System.ComponentModel.Composition hinzu. Fügen Sie in Visual Basic die Imports-Anweisung für SimpleCalculator hinzu. Fügen Sie der ExtendedOperations-Klassendatei anschließend den folgenden Code hinzu:

[Export(typeof(SimpleCalculator.IOperation))]
[ExportMetadata("Symbol", '%')]
public class Mod : SimpleCalculator.IOperation
{
    public int Operate(int left, int right)
    {
        return left % right;
    }
}

Das ExportAttribute-Attribut muss vom gleichen Typ sein wie ImportAttribute, damit der Vertrag als Übereinstimmung betrachtet wird.

Kompilieren Sie das Projekt und führen Sie es aus. Testen Sie den neuen MOD (%)-Operator.

In diesem Thema wurden die grundlegenden Konzepte des MEF behandelt.

  • Teile, Kataloge und der Kompositionscontainer

    Die Teile und der Kompositionscontainer sind die Grundbausteine einer MEF-Anwendung. Ein Teil ist jedes Objekt, durch das ein Wert importiert oder exportiert wird, einschließlich des Objekts selbst. Ein Katalog stellt eine Auflistung der Teile einer bestimmten Quelle bereit. Der Kompositionscontainer verwendet die von einem Katalog bereitgestellten Teile, um eine Komposition (die Bindung von Importen an Exporte) durchzuführen.

  • Importe und Exporte

    Durch Importe und Exporte kommunizieren Komponenten miteinander. Durch einen Import fordert eine Komponente einen bestimmten Wert oder ein Objekt an. Mit einem Export wird die Verfügbarkeit eines Werts angezeigt. Jeder Import wird anhand seines Vertrags mit einer Reihe von Exporten verglichen.

Der vollständige Code für dieses Beispiel kann unter SimpleCalculator-Beispiel heruntergeladen werden.

Weitere Informationen und Codebeispiele finden Sie unter Managed Extensibility Framework. Eine Liste der MEF-Typen finden Sie unter dem System.ComponentModel.Composition-Namespace.

Community-Beiträge

HINZUFÜGEN
Anzeigen:
© 2014 Microsoft