Datum: 1 oktober 2015
Auteur: Marcel Bancken, Software Architect

Het Managed Extensibility Framework (MEF) is een framework van Microsoft om uitbreidbare applicaties te bouwen. Het fundamentele doel van MEF is om plug-in componenten aan een bestaande applicatie toe te kunnen voegen. Het is een integraal onderdeel van het .Net Framework sinds versie 4.0 (System.ComponentModel.Composition). In .Net 4.5 zijn er een aantal verbeteringen en uitbreidingen aan MEF doorgevoerd dat dan ook wel MEF2 of MEFII wordt genoemd.

Alvorens verder in te gaan op details over MEF worden hier eerst een aantal fundamentele termen uitgelegd die helpen bij het beter begrijpen ervan:

  • Part: een object (bijvoorbeeld een class, method of property) dat geïmporteerd of geëxporteerd kan worden naar de applicatie.
  • Catalog: een object dat helpt bij het vinden van de beschikbare parts uit een assembly of directory.
  • Contract: legt vast waaraan een part moet voldoen (bijvoorbeeld een interface of een data type).
  • Compose: het instantiëren van de geëxporteerde parts tot geïmporteerde objecten.

MEF werkt volgens het principe van vraag en aanbod van objecten (parts). Met MEF kunnen beschikbare parts die voldoen aan een contract m.b.v. een catalog dynamisch gevonden en daarna geïnstantieerd worden.

MEF1

MEF1 (.Net 4.0) heeft enkele nadelen:

  1. Objecten kunnen alleen geëxporteerd worden door er export attributen aan te geven. Hierdoor is het niet mogelijk om types te exporteren waar je geen source code van hebt.
  2. Beperkte controle over de levensduur van instanties. MEF 1 kent slechts twee instantie scopes: per instance en shared global lifetime.
  3. Problemen die kunnen optreden bij het instantiëren van geëxporteerde parts zijn lastig te diagnosticeren.

MEF2

In MEF2 zijn de problemen die er waren met MEF1 opgelost:

  1. Attribuut-loze registratie: class RegistrationBuilder is geïntroduceerd om export registratie en part creatie af te handelen. RegistrationBuilder heeft drie methodes om de te exporteren parts te identificeren en selecteren:
    • ForType<T>(): selecteert een enkel type. De volgende regels code laten zien hoe het type AnniversaryGreetings geëxporteerd en geïnstantieerd wordt.// Export type
      regisBuilder.ForType<AnniversaryGreetings>().Export<AnniversaryGreetings>();
      // Import type
      var v = container.GetExportedValue<AnniversaryGreetings>();
    • ForTypesDerivedFrom<T>(): selecteert types die afgeleid zijn van een base class of een interface. De volgende regels code laten zien hoe types afgeleid van IGreetings geëxporteerd en geinstantieerd worden.// Export all types derived from IGreetings
      regisBuilder.ForTypesDerivedFrom<IGreetings>()
      .SelectConstructor(cInfo => cInfo[0]) // this selects first constructor
      .Export<IGreetings>();
      // Import types derived from IGreetings
      var v = container.GetExportedValues<IGreetings>();
    • ForTypesMatching(Predicate p): selecteert types die overeenkomen met een booleaanse expressie. Dit is de meest flexibele manier om types te exporteren. De volgende regels code laten zien hoe type(s) met de naam HomeGreetings geëxporteerd en geïnstantieerd worden.// Export types matching name
      regisBuilder.ForTypesMatching(x => x.Name.Equals(“HomeGreetings”)).Export();
      // Import types
      var v = container.GetExportedValues<HomeGreetings>();
  2. Betere controle over de levensduur van instanties: class ExportFactory<T> is geïntroduceerd om op elk gewenst moment een geëxporteerd object te kunnen instantiëren en op te ruimen. De volgende regels code laten zien hoe je gebruik kunt maken van ExportFactory om een instantie van Greetings te maken.// Export factory class
    public class ExportFactoryExample
    {
    ExportFactory<Greetings> _factory;
    [ImportingConstructor]
    public ExportFactoryExample(ExportFactory<Greetings> factory)
    {
    _factory = factory;
    }
    public string Greeting(string name)
    {
    using (var instance = _factory.CreateExport())
    {
    return instance.Value.Greet(name);
    }
    }
    } // Export the ExportFactoryExample type
    regisBuilder.ForType<ExportFactoryExample>().Export<ExportFactoryExample>();
    // Import the ExportFactoryExample type
    var exportType = container.GetExportedValue<ExportFactoryExample>();
  3. Fouten afhandeling: in MEF2 is behalve dat de foutmeldingen zelf zijn verbeterd ook de mogelijkheid toegevoegd om aan de CompositionContainer constructor mee te geven dat excepties moeten worden gegeven wanneer er bij het instantiëren iets fout gaat.

Voorbeeld WPF applicatie

Om het gebruik en de werking van MEF2 te demonstreren heb ik een simpele WPF applicatie geschreven dat parts die interface IGreetings implementeren importeert en toont middels het aanmaken van knoppen op de UI. Het volgende diagram geeft de structuur van de applicatie weer.

WpfApp_struct
Als de applicatie wordt gestart krijg je het volgende scherm:

WpfApp_started

Door het drukken op de Start knop worden alle parts objecten uit de bin directory die IGreetings implementeren shared geïnstantieerd. Hier zit OtherGreetings niet bij want die bevind zich in een andere assembly en directory.

WpfApp_Shared_Import

Bij het drukken op start wordt vervolgens de volgende code uitgevoerd:

WpfApp_StartClicked
Als we de parts nu NonShared instantiëren krijgen we na drukken op start het volgende:

WpfApp_NonShared_Import

Als we vervolgens de assembly ClassLibrary2.dll naar de bin directory van de WPF applicatie kopiëren en na het herstarten van de applicatie weer op start drukken zien we dat OtherGreetings erbij is gekomen:

WpfApp_NonShared_Plugin_Import

Conclusie

MEF2 is aanzienlijk verbeterd t.o.v. MEF1 waardoor het vrij eenvoudig is geworden om uitbreidbare applicaties te ontwikkelen. MEF2 biedt goede ondersteuning om plug-in componenten aan een bestaande applicatie toe te voegen.

Meer weten?

Heb je vragen of wil je meer informatie? Neem dan contact met ons op via info.nl@axians.com of door te bellen naar +31 88 988 96 00.

Download_WPF_applicatie