MEF Teil 5 – Composition und Recomposition

Die Kataloge und Container dienen dazu, die Instanzen der Composable Parts zu erzeugen und miteinander zu binden. In den vorherigen Blogs wurde dieses Thema nicht weiter behandelt. Das soll jetzt nachgeholt werden.

Composition

Composable Parts sind Objekte (Klassen, Schnittstellen, Methoden oder Eigenschaften), die mit dem Attribut Import, ImportMany oder Export dekoriert werden. Composable Parts, die als Export definiert wurden, werden vom Managed Extensibility Framework geladen und instanziiert. Die Composable Parts, die als Import deklariert wurden, sind Variablen, an welche die Exports gebunden werden. Im einfachsten Fall wird ein Import an ein Export gebunden. Das ist allerdings nur möglich, wenn beide Parts zueinander kompatibel sind.

Das Managed Extensibility Framework unterstützt die Instanziierung und das Binden mit den sogenannten Katalogen und Containern. Kataloge kontrollieren das Laden der Composable Parts, während Container die Instanzen der Exports erzeugen und diese mit den Imports binden.

Die Klasse ComposablePartCatalog

Von der Klasse ComposablePartCatalog gibt es verschiedene Ableitungen. Die verschiedenen Ableitungen sind notwendig, da es mehrere Möglichkeiten gibt, Composable Parts zu laden. Zu finden sind die Klassen in dem Namespace System.ComponentModel.Composition.Hosting. Composable Parts können sich in der gleichen Assembly befinden oder auf mehreren Assemblies verteilt sein. Des weiteren muss unterschieden werden, ob die Assemblies statisch oder dynamisch geladen werden.

Klasse Funktion
TypeCatalog Die Klasse TypeCatalog ermöglicht das Binden von exakt angegebenen Typen.
AssemblyCatalog Sollen Composable Parts aus einer angegebenen Assembly berücksichtigt werden, so wird die Klasse AssemblyCatalog benutzt.
DirectoryCatalog Es werden nur Assemblies aus einem bestimmten Verzeichnis geladen.
AggregateCatalog Mehrere Kataloge können mit Hilfe dieser Klasse kombiniert werden.
DeploymentCatalog Steht unter Silverlight zur Verfügung und dient dazu XAPs nachzuladen.
Die Klasse CompositionContainer

Über die Klasse CompositionContainer wird das Laden und Binden der Composable Parts gestartet. Hierzu wird dem Konstruktor der Klasse CompositionContainer als Parameter der Katalog übergeben. Die Methode ComposeParts() startet den Composing Prozess. Als Parameter muss der Methode ComposeParts() die Referenz auf das Objekt übergeben werden, das die Imports enthält. Alternativ kann aber auch die Methode Compose() aufgerufen werden, die eine Instanz der Klasse CompositionBatch erwartet.

Die Klasse CompositionBatch

Der Methode Compose() der Klasse CompositionContainer kann als Parameter eine Instanz der Klasse CompositionBatch übergeben werden. CompositionBatch verwaltet eine Liste von Composable Parts, die beim Composing Prozess berücksichtigt werden sollen.

Beispiel

Das folgende Beispiel zeigt die Benutzung aller genannten Klassen.

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using CarContract;

namespace CarHost
{
    class Program
    {
        [ImportMany(typeof(ICarContract), AllowRecomposition=true)]
        private IEnumerable<ICarContract> CarParts { get; set; }

        static void Main(string[] args)
        {
            new Program().Run();
        }
        void Run()
        {
            var catalog = new AggregateCatalog();

            // Parameterliste mit Klassen, die berücksichtigt werden sollen
            var typeCatalog = new TypeCatalog(typeof(CarMercedes.Mercedes),
                                                     typeof(CarAudi.Audi));
            catalog.Catalogs.Add(typeCatalog);

            // Assembly wird über den FullName dynamisch geladen und
            // dem Katalog hinzugefügt
            var assemblyCatalog = new AssemblyCatalog(
                                             Assembly.Load(
                                                      "Opel,
                                                       Version=1.0.0.0,
                                                       Culture=neutral,
                                                       PublicKeyToken=null"));
            catalog.Catalogs.Add(assemblyCatalog);

            // Assemblies aus dem Verzeichnis '.\AddIn' werden berücksichtigt
            var directoryCatalog = new DirectoryCatalog(".\AddIn");
            catalog.Catalogs.Add(directoryCatalog);

            // Container mit dem entsprechenden Katalog anlegen
            var container = new CompositionContainer(catalog);

            // Composition über die Klasse CompositionBatch starten
            var batch = new CompositionBatch();
            batch.AddPart(this);
            container.Compose(batch);

            // Composition ohne CompositionBatch starten
            //container.ComposeParts(this);
            // eine Parameterliste von Objekten kann auch angegeben werden
            //container.ComposeParts(this, obj1, obj2);

            // Anzeige aller Composable Parts
            foreach (ICarContract car in CarParts)
                Console.WriteLine(car.GetName());

            container.Dispose();
        }
    }
}

Die Ausgabe des Beispielprogramms sieht wie folgt aus:

CommandWindowsSample01

Beispiel 1 (Visual Studio 2010) auf GitHub

Recomposition

Mit Recomposition bezeichnet man die Möglichkeit, weitere Composable Parts zur Laufzeit nachzuladen. Vorstellbar wäre z.B. eine Anwendung, die von einer externen Quelle zusätzliche Komponenten nachlädt.

MEF ist so ausgelegt, dass ein Recomposition nicht möglich ist. Soll Recomposition unterstützt werden, so muss bei dem Attribut Import oder ImportMany die Eigenschaft AllowRecomposition auf TRUE gesetzt werden.

[ImportMany(typeof(ICarContract), AllowRecomposition=true)]

Recomposition kann nur über die Kataloge DirectoryCatalog und AggregateCatalog genutzt werden. Die Klasse DirectoryCatalog bietet hierzu speziell die Methode Refresh() an. Diese lädt alle Composable Parts erneut aus dem Verzeichnis des Katalogs.

Mit AggregateCatalog kann die Liste der Kataloge komplett neu aufgebaut werden. Somit lässt sich die Zusammenstellung der Composable Parts zur Laufzeit neu zusammensetzen. Hierzu ein kleines Beispiel.

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using CarContract;

namespace CarHost
{
    class Program
    {
        [ImportMany(typeof(ICarContract), AllowRecomposition=true)]
        private IEnumerable<ICarContract> CarParts { get; set; }

        static void Main(string[] args)
        {
            new Program().Run();
        }
        void Run()
        {
            var aggregateCatalog = new AggregateCatalog();

            aggregateCatalog.Catalogs.Add(new DirectoryCatalog(".\AddIn01"));
            var container = new CompositionContainer(aggregateCatalog);
            container.ComposeParts(this);

            foreach (ICarContract car in CarParts)
                Console.WriteLine(car.GetName());

            Console.WriteLine("\n...weitere Parts laden...\n");

            aggregateCatalog.Catalogs.Add(new DirectoryCatalog(".\AddIn02"));
            container.ComposeParts(this);

            foreach (ICarContract car in CarParts)
                Console.WriteLine(car.GetName());

            container.Dispose();
        }
    }
}

Am Anfang wird ein Objekt der Klasse AggregateCatalog angelegt. Dieser Katalog verwaltet eine Liste mit weiteren Katalogen. In diesem Beispiel enthält die Liste ein DirectoryCatalog mit dem Unterverzeichnis AddIn01. Die Zeilen 26 und 27 rufen von allen gefundenen Composable Parts die Methode GetName() auf. Ab Zeile 31 wird die Liste um einen weiteren Katalog ergänzt. Dieses zweite Objekt der Klasse DirectoryCatalog verweist auf das Verzeichnis AddIn02.

CommandWindowsSample01

Auf diese Weise lässt sich die Liste der zu berücksichtigen Kataloge beliebig anpassen. Neben dem Hinzufügen ist es auch möglich, Kataloge mit den Methoden Clear() und Remove() zu entfernen. In diesem Fall werden die Composable Parts von MEF zerstört.

Beispiel 2 (Visual Studio 2010) auf GitHub

Wird das Anpassen der Liste mit den Katalogen asynchron durchgeführt, so muss darauf geachtet werden, dass die Composable Parts thread-safe sind. Der Konstruktor der Composable Parts wird immer im Kontext des Threads ausgeführt, aus dem die Methode ComposeParts() der Klasse CompositionContainer aufgerufen wird.

Author: Stefan Henneken

I’m Stefan Henneken, a software developer based in Germany. This blog is just a collection of various articles I want to share, mostly related to Software Development.

One thought on “MEF Teil 5 – Composition und Recomposition”

Leave a comment