Auf Englisch lesen

Freigeben über


Managed Extensibility Framework (MEF)

Dieses Thema bietet eine Übersicht über das in .NET Framework 4 eingeführte Managed Extensibility Framework.

Was ist 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.

Das Problem der Erweiterbarkeit

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.

Dies wird von MEF bereitgestellt

Mit MEF ist die explizite Registrierung 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 von der MEF-Kompositions-Engine 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.

Verfügbarkeit von MEF

Das MEF ist ein wesentlicher Bestandteil vom .NET Framework 4 und ist überall dort verfügbar, wo das .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.

MEF und MAF

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.

SimpleCalculator: Eine Beispielanwendung

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 (Visual Basic) heruntergeladen werden.

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. Ausführlichere Beispiele finden Sie unter Managed Extensibility Framework auf GitHub.

  • Erstellen Sie zum Einstieg in Visual Studio ein neues Konsolenanwendungsprojekt, und nennen Sie es SimpleCalculator.

  • Fügen Sie der Assembly System.ComponentModel.Composition einen Verweis hinzu, wo sich MEF befindet.

  • Öffnen Sie Module1.vb oder Program.cs, und fügen Sie die Anweisung Imports oder using 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.

  • Wenn Sie Visual Basic verwenden, fügen Sie das Public-Schlüsselwort der Zeile hinzu, die das Module1-Modul deklariert.

Kompositionscontainer und Kataloge

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

Wenn Sie Visual Basic verwenden, fügen Sie in der Datei Module1.vb die öffentliche Klasse Program hinzu.

Fügen Sie der Program-Klasse in Module1.vb bzw. Program.cs die folgende Zeile hinzu:

private CompositionContainer _container;

Zur Ermittlung der verfügbaren Teile verwenden Kompositionscontainern einen Katalog. 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()
{
    try
    {
        // 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);
        _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.

Importe und Exporte mit Attributen

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 von der Kompositions-Engine ausgefüllt.

Jeder Import weist einen Vertrag auf, der bestimmt, 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 können Sie typeof(ICalculator) weglassen. MEF geht automatisch davon aus, dass der Vertrag auf dem Typ des Imports basiert, sofern Sie ihn nicht explizit angeben.)

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)
{
    // Composition is performed in the constructor.
    var p = new Program();
    Console.WriteLine("Enter Command:");
    while (true)
    {
        string 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.

Importe und ImportMany-Attribute

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 von der Kompositions-Engine 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, die das exportierte Objekt beschreiben. 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. Add wird zwar von der IOperation-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 MySimpleCalculator bei der Erstellung von Program 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.)

Rechnerlogik

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;
    // Finds the operator.
    int fn = FindFirstNonDigit(input);
    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 Public das Schlüsselwort Module1 hinzugefügt haben. Geben Sie im Konsolenfenster eine Addition ein, z. B. "5+3", und der Rechner gibt das entsprechende Ergebnis aus. Bei jedem anderen Operator erhalten Sie die Meldung „Vorgang nicht gefunden!“.

Erweitern von SimpleCalculator mithilfe einer neuen Klasse

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.

Erweitern von SimpleCalculator mithilfe einer neuen Assembly

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 Kompilieren oder Erstellen. Ändern Sie den Build-Ausgabepfad oder den Ausgabepfad so, dass er auf das Erweiterungsverzeichnis im SimpleCalculator-Projektverzeichnis (..\SimpleCalculator\Extensions\) zeigt.

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 Debugging-Zwecke gedacht. In einer Produktionsanwendung würden Sie einen relativen Pfad verwenden.) DirectoryCatalog fügt nun alle Teile, die in den Assemblys im Erweiterungsverzeichnis gefunden werden, dem Kompositionscontainer hinzu.

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.

Schlussbemerkung

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.

Nächste Schritte

Der vollständige Code für dieses Beispiel kann unter SimpleCalculator-Beispiel (Visual Basic) 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.