Tipps und Tricks rund um .NET und Visual Studio

API-basierte Konfiguration in MEF 2
Kommentare

Dr. Holger Schwichtenberg (MVP) und FH-Prof. Manfred Steyer teilen in der Kolumne „.NETversum“ ihr Expertenwissen rund um .NET-Tools und WPF mit.

In der ersten Version von MEF mussten Imports und Exports über Attribute gekennzeichnet werden. Ab Version 2, die in .NET 4.5 enthalten ist, kann der Entwickler diese Aufgabe auch über ein Fluent-API, das das Definieren von Konventionen erlaubt, bewerkstelligen.

API-basierte Konfiguration

Im Zuge dessen kann er zum Beispiel festlegen, dass MEF sämtliche Subtypen eines bestimmten Basistyps exportieren soll. Die nachfolgenden Listings demonstrieren die Verwendung dieses API. Dazu beinhalten sie ein Interface ILogger sowie eine Implementierung davon. Letztere soll exportiert werden. Daneben beinhaltet es die Klasse Calculator, deren Konstruktoren ein zu importierendes IEnumerable erwarten. Die Listings 1 und 2 zeigen den Einsatz des API, mit dem sämtliche Implementierungen von ILogger als Exporte sowie einer der beiden Konstruktoren von Calculator als Importing Constructor definiert werden. Dreh- und Angelpunkt ist hierbei die Instanz der Klasse RegistrationBuilder, bei der die einzelnen Konventionen registriert werden. Die Methode ForTypesDerivedFrom nimmt Bezug auf sämtliche Implementierungen von ILogger; die Methode Export veranlasst – wie der Name vermuten lässt – den Export und SetCreationPolicy bestimmt, ob MEF den Export als Singleton, der von sämtlichen Imports zu teilen ist (CreationPolicy.Shared), einrichten soll oder ob jeder Import eine eigene Instanz des Exports bekommt. Mit dem Lambda-Ausdruck, der an Export übergeben werden kann, legt der Aufrufer Details zum Export fest. Das betrachtete Beispiel legt auf diese Weise fest, dass als Vertrag der Typ ILogger zu verwenden ist. Danach referenziert das Beispiel den Calculator mit ForType und exportiert ihn via Export. Daneben legt es mittels SelectConstructor den für den Import zu verwendenden Konstruktor fest. Verzichtet der Entwickler auf diesen Aufruf, wird per Definition der Konstruktor mit den meisten Parametern herangezogen. Anschließend übergibt das Beispiel den RegistrationBuilder an den zu verwendenden Catalog und erzeugt damit auf die gewohnte Art und Weise einen CompositionContainer, über den es mittels GetExport eine Instanz von Calculator bezieht.

public abstract class ILogger
{
    public abstract void Log(string msg);
}

public class ConsoleLogger: ILogger
{
    public override  void Log(string msg)
    {
        Console.WriteLine(DateTime.Now.ToString() + ": " + msg);
    }
}

public class Calculator
{
    private IEnumerable Logger { get; set; }

    public Calculator(IEnumerable logger, string somethingElse)
    {
        this.Logger = logger;
    }

    public Calculator(IEnumerable logger)
    {
        this.Logger = logger;
    }

    public int Add(int a, int b)
    {
        var c = a + b;
        Logger.ToList().ForEach(logger => 
            logger.Log(string.Format("ADD: {0} + {1} = {2}", a, b, c)));

        return c;
    }
}
var rb = new RegistrationBuilder();

rb.ForTypesDerivedFrom()
    .Export(ec => ec.AsContractType())
    .SetCreationPolicy(CreationPolicy.Shared);

rb.ForType().Export();
rb.ForType().SelectConstructor(
            pib => new Calculator(pib.Import<List>()));

var catalog = new AssemblyCatalog(typeof(Program).Assembly, rb);
var container = new CompositionContainer(catalog, 
                           CompositionOptions.DisableSilentRejection);

var calculator = container.GetExport().Value;

calculator.Add(2, 3);

Ein weiteres Beispiel für die Definition von Konventionen findet sich in Listing 3 und 4. Es legt mit ForTypesMatching fest, dass sämtliche Typen, deren Namen auf Helper enden, zu exportieren sind. Dazu nimmt ForTypesMatching einen Lambda-Ausdruck entgegen, der eine Instanz von Type auf einen booleschen Ausdruck abbildet. Dieser Ausdruck gibt an, ob der jeweilige Typ zu exportieren ist. Die Methode ImportProperty legt hingegen fest, dass MEF eine bestimmte Eigenschaft eines Typs als Ziel für Imports verwenden soll. Die Eigenschaft nimmt sie dabei als Lambda-Ausdruck entgegen. Weitere Details zum Import legt der Entwickler über einen optionalen zweiten Parameter fest. Auch hierbei handelt es sich um einen Lambda-Ausdruck. Dieser bildet eine Instanz von ImportBuilder (im Listing als ib bezeichnet) auf die gewünschten Einstellungen ab. Mit ib.AsMany legt das betrachtete Beispiel fest, dass die jeweilige Eigenschaft mehrere Imports in Form einer Auflistung aufnehmen soll. Darüber hinaus exportiert es mittels AddMetadata Metadaten für die Typen ConsoleLogger und FileLogger.

var rb = new RegistrationBuilder();

rb.ForTypesMatching(t => t.Name.EndsWith("Helper")).Export();

rb.ForType().Export();
rb.ForType().ImportProperty(oc => oc.Logger, 
                                                         ib => ib.AsMany(true));
rb.ForType().ImportProperty(oc => oc.FormatHelper);

rb.ForType().Export(eb => 
            eb.AddMetadata("minLevel", "debug").AsContractType() );
rb.ForType().Export(eb => 
            eb.AddMetadata("minLevel", "info").AsContractType());

var catalog = new AssemblyCatalog(typeof(Program).Assembly, rb);
var container = new CompositionContainer(catalog, 
                              CompositionOptions.DisableSilentRejection);

var calculator = container.GetExport().Value;
            
calculator.Add(2, 3);

public class OtherCalculator
{
    public IEnumerable<Lazy<ILogger, Dictionary<string, object>>> 
                                                      Logger { get; set; }

    public FormatHelper FormatHelper { get; set; }

    [...]
}
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -