MEF

De Banane Atomic
Aller à la navigationAller à la recherche

Généralités

Catalog

Définit où les plugins doivent être recherchés.

Csharp.svg
// trouve tous les plugins présent dans le répertoire DirectoryPath
var catalog = new DirectoryCatalog("DirectoryPath", "Plugin.*.dll");
// le chemin peut être relatif
// les sous-répertoires ne sont pas scannés

// trouve tous les plugins présent dans l'assemblage spécifié
var catalog = new AssemblyCatalog(Assembly.LoadFrom("chemin vers l'assemblage"));

// trouve tous les plugins présent dans les types spécifiés
// permet d'avoir un contrôle plus fin et de ne charger que certain Type (classe)
var catalog = new TypeCatalog(typeof(type1), typeof(type2), ...);

// permet de composer plusieurs catalog
var catalog = new AggregateCatalog(new AssemblyCatalog(Assembly), new DirectoryCatalog("DirectoryPath"));
DirectoryCatalog ne fonctionne pas avec les chemins réseau.
Il faut utiliser un AggregateCatalog avec un AssemblyCatalog pour chaque fichier obtenu avec Directory.GetFiles

Charger tous les assemblages à l'exception de certains

Csharp.svg
var catalog = new AggregateCatalog();
foreach (var assembly in Directory.GetFiles("MonDossier", "*.dll").Where(a => !a.EndsWith("AssembleÀNePasImporter.dll")))
{
    catalog.Catalogs.Add(new AssemblyCatalog(Assembly.LoadFrom(assembly)));
}
var container = new CompositionContainer(catalog);

Proriétés

application
public class ImportDLL
{
    // correspondance avec le seul export string
    [Import]
    private string message;

    // correspondance avec le seul export int
    [Import]
    private int number;
    
    // correspondance avec l'export MyContract
    [Import("MyContract")]
    private int number2;
}
plugin
public class Plugin
{
    [Export]
    public string MyMessage
    {
        get { return "Hello !!!"; }
    }

    [Export]
    public int MyNumber
    {
        get { return 666; }
    }

    [Export("MyContract")]
    public int MyNumber2
    {
        get { return 111; }
    }
}

Méthodes

application
public class ImportDLL
{
    [Import("HelloContract", typeof(Func<string, string>))]
    public Func<string, string> HelloImport { get; set; }

    Console.WriteLine(HelloImport.Invoke("you"));
}
plugin
public class Plugin
{
    [Export("HelloContract", typeof(Func<string, string>))]
    public string Hello(string name)
    {
        return String.Format("Hello {0}", name);
    }
}

Import

Import ExactlyOne

Un export est attendu pour remplir cet import.

  • Si aucun export n'est trouvé une exception de type CompositionException est générée avec le message:
No exports were found that match the constraint
  • Si plusieurs exports sont trouvés une exception de type CompositionException est générée avec le message:
More than one export was found that matches the constraint
Csharp.svg
[Import]
public IPlugin Plugins;

Import ZeroOrOne

Import optionel, pas d'exception si aucun export n'est trouvé.

Csharp.svg
[Import(AllowDefault=true)]
public IPlugin Plugins;

Import ZeroOrMore

Permet d'importer aux sein d'une collection, des exports ayant le même contract.

application
[ImportMany]
IEnumerable<IPlugin> Plugins;
plugin
[Export]
public IPlugin plugin1 { get; set; }
[Export]
public IPlugin plugin2 { get; set; }

InheritedExport

application
[InheritedExport]
public interface IRule { }

public class ImportDLL
{
    [ImportMany]
    IEnumerable<IRule> rules;
}
plugin
// pas besoin de déclarer d'export car il hérite de IRule qui l'a déjà définit
public class Rule1 : IRule { }
public class Rule2 : IRule { }
// bloque le système d'InheritedExport. Rule3 ne sera pas importée.
[PartNotDiscoverable]
public class Rule3 : IRule { }
Ici le plugin ne référence pas MEF.

Lazy

Normalement MEF instancie les objets importés lors de l'appel de ComposeParts ou SatisfyImportsOnce.
Lazy permet de retarder l'instanciation des objets importés au moment où on les utilise.

Csharp.svg
[Import]
Lazy<MyClass> importedObject;

// MyClass est instancié lors de son utilisation
importedObject.Value.MyMethod();

Export

Csharp.svg
// Par défaut le contrat est du type de la classe
// Les trois exports suivants sont équivalents
[Export]
class MaClasse : IInterface

[Export(typeof(MaClasse))]
class MaClasse : IInterface

[Export(typeof("Namespace.MaClasse"))]
class MaClasse : IInterface

// Il est possible de forcer l'export suivant le contrat d'interface
// Utile si le contrat d'import est du type de l'interface
[Export(typeof(IInterface))]
class MaClasse : IInterface

ExportMetadata

application
public interface IRule { }

public class ImportDLL
{
    [ImportMany(typeof(IRule))]
    IEnumerable<Lazy<IRule, IDictionary<string, object>>> rules;

    var rule1 = rules.Where(rule => (string)rule.Metadata["RuleName"] == "Rule1").FirstOrDefault();
    if (rule1 != null) { ... }
}
plugin
[Export(typeof(IRule))]
[ExportMetadata("RuleName", "Rule1")]
public class Rule1 : IRule { }

[Export(typeof(IRule))]
[ExportMetadata("RuleName", "Rule2")]
public class Rule2 : IRule { }
Ne fonctionne pas avec InheritedExport

Custom ExportMetadata

Permet de remplacer le IDictionary<string, object> par une interface.

application
public interface IRule { }

public interface IRuleMetadata
{
    string RuleName { get; }
}

public class ImportDLL
{
    [ImportMany(typeof(IRule))]
    IEnumerable<Lazy<IRule, IRuleMetadata>> rules;

    var rule1 = rules.Where(rule => (string)rule.Metadata.RuleName == "Rule1").FirstOrDefault();
    if (rule1 != null) { ... }
}
Le nom de la propriété de l'interface (IRuleMetadataRuleName) doit correspondre à la clé de l'ExportMetadata (RuleName)

Exemple

application
public interface ITalk
{
    string Bye(string name);
}

class ImportDLL
{
    [Import]
    private string message;

    [Import("HelloContract", typeof(Func<string, string>))]
    private Func<string, string> helloImport;

    [Import]
    private ITalk talk;

    public void Run()
    {
        // on spécifie ou charger les assemblages de plugin
        var catalog = new DirectoryCatalog(@"Chemin\vers\le\dossier\du\plugin");
        var container = new CompositionContainer(catalog);

        // recherche des exports correspondant aux imports
        container.SatisfyImportsOnce(this);
        // échec si un import ne trouve pas d'export correspondant
        // ou si plusieurs exports correspondent à un import

        Console.WriteLine(message);

        Console.WriteLine(helloImport.Invoke("you"));

        Console.WriteLine(talk.Invoke("you"));
    }
}
plugin
public class Plugin
{
    [Export]
    public string MyMessage
    {
        get { return "Hello !!!"; }
    }

    [Export("HelloContract", typeof(Func<string, string>))]
    public string Hello(string name)
    {
        return String.Format("Hello {0}", name);
    }

    // il faut spécifier que le contrat d'export est ITalk, car il est Talk par défaut
    // et le contrat d'import attendu est ITalk
    [Export(typeof(ITalk))]
    public class Talk : ITalk
    {
        public string Bye(string name)
        {
            return String.Format("Bye {0}", name);
        }
    }
}

Filtering Catalogs

Catch exceptions

Csharp.svg
try
{
    var catalog = new DirectoryCatalog("Chemin");
}
catch (DirectoryNotFoundException dnfex)
{
    // si le chemin vers le dossier n'existe pas.
}

try
{
    ...
    container.SatisfyImportsOnce(this);
}
// Certains imports ExactlyOne n'ont pas trouvés leurs exports
// Ou plusieurs [Export] trouvé pour un import ExactlyOne
// Ou dépendance du plugin chargé non trouvée
catch (CompositionException ex)
{
    var message = ex.Message;
}
// Différence de contrat (interface) entre Import et Export
catch (ReflectionTypeLoadException rtlex)
{
    var sb = new StringBuilder();
    foreach (var innerEx in rtlex.LoaderExceptions)
    {
        sb.AppendLine(innerEx.Message);
    }
    log.Error(sb.ToString());
}

Rejection trace messages

Lorsqu'une part est rejetée, MEF trace la part rejetée, la raison, et l'import qui a causé le rejet.
La trace est visible dans la fenetre de sortie de Visual Studio.
Il est aussi possible d'écrire la trace dans un fichier:

App.config
<configuration>
    <system.diagnostics>
        <sources>
            <source name="System.ComponentModel.Composition" switchValue="All">
                <listeners>
                    <add name="fileListener"
                         type="System.Diagnostics.TextWriterTraceListener"
                         initializeData="composition.log" />
                </listeners>
            </source>
        </sources>
        <trace autoflush="true" indentsize="4" />
    </system.diagnostics>

Plugins sur un partage réseau

Charger différentes versions de la même dll

Dans le context load-from, la 2ème dll ne sera pas chargée et la première sera utilisée.
Pour éviter ça:

  • Utiliser des strong names (signer les dll)
  • Utiliser le .NET Framework Add-In Model
  • Utiliser le GAC
  • Utiliser les Application Domains

Erreurs

cannot convert from ... to ComposablePart

Csharp.svg
using System.ComponentModel.Composition;