MEF - Importing from XAML

Qualche settimana fa ho il letto il post di Bnaya Importing from Xaml e siccome la cosa mi ha incuriosito ho deciso di “espandere” la sua idea in maniera tale da poter importare da xaml i miei plugin. Però volevo anche non dipendere da un’interfaccia/contratto specifico così giocando un pò è uscita questa MarkupExtension:

[MarkupExtensionReturnType(typeof(object))]
public class MEFMarkupExtension : MarkupExtension
{
    private Type contractType;
 
    public MEFMarkupExtension(Type type)
    {
        this.contractType = type;
        this.Cardinality = ImportCardinality.ExactlyOne;
    }
 
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        object result = null;
 
        switch (Cardinality)
        {
            case ImportCardinality.ExactlyOne:
                // [Import(AllowDefault = false)]
                result = InvokeContainerMethod("GetExportedValue");
                break;
            case ImportCardinality.ZeroOrMore:
                // [ImportMany]
                result = InvokeContainerMethod("GetExportedValues");
                break;
            case ImportCardinality.ZeroOrOne:
                // [Import(AllowDefault = true)]
                result = InvokeContainerMethod("GetExportedValueOrDefault");
                break;
        }
 
        return result;
    }
 
    public ImportCardinality Cardinality { get; set; }
 
    private object InvokeContainerMethod(string methodName)
    {
        return CompositionHost.Container
            .GetType()
            .GetMethod(methodName, new Type[] { })
            .MakeGenericMethod(contractType)
            .Invoke(CompositionHost.Container, null);
    }
}

namespace System.ComponentModel.Composition.Primitives
{
    public enum ImportCardinality
    {
        ZeroOrOne = 0,
        ExactlyOne = 1,
        ZeroOrMore = 2,
    }
}
Il codice è molto semplice: in base alla cardinalità specificata dall’utente verrà richiamato uno specifico metodo del container.
La markup extension può essere utilizzata in questo modo se specifichiamo ZeroOrMore come cardinalità
<ListBox DataContext="{local:MEFMarkupExtension {x:Type local:IPlugin}, Cardinality=ZeroOrMore}"
             ItemsSource="{Binding}"
             ItemTemplate="{StaticResource PluginTemplate}"/>

in questo se specifichiamo ExactlyOne oppure ZeroOrOne

<ContentControl DataContext="{local:MEFMarkupExtension {x:Type local:IPlugin2}, Cardinality=ExactlyOne}"
                 Content="{Binding}"
                 ContentTemplate="{StaticResource PluginTemplate}"/>

E CompositionHost?
Molto probabilmente chi ha usato MEF in qualche applicazione Silverlight ha già visto questa classe ed anche PartInitializer. Glenn Block ha rilasciato un piccolo progetto contente queste due classi su SkyDrive in quanto non sono presenti nè sulla Preview presente su Codeplex e neanche nella prossima versione del .NET (parlo sempre dell’assembly per Desktop Applications).

CompositionHost può essere utilizzata per inizializzare un container tramite InitializeContainer che poi verrà utilizzato dal metodo PartInitializer.SatisfyImports(object instance).

Siccome dovevo poter accedere al Container una volta inizializzato ho aggiunto questa proprietà a CompositionHost

public static CompositionContainer Container
{
    get
    {
        return _container;
    }
}

ed infine all’interno di App.xaml.cs “compongo” il tutto

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
 
    CompositionHost.InitializeContainer(new CompositionContainer(new AssemblyCatalog(Assembly.GetExecutingAssembly())));
    PartInitializer.SatisfyImports(this);
}
Spero quanto prima di riuscire a postare qualcosa di più avanzato (ad esempio MEF & MAF, oppure qualche internal) e naturalmente se avete qualche curiosità particolare chiedete!
Federico
PS: Non so quanto possa essere utile/bello/fico questo post però una volta qualcuno mi disse di postare per me per cui… :)