Généralités
Catalog
Définit où les plugins doivent être recherchés.
|
// 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
|
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
|
[Import]
public IPlugin Plugins;
|
Import ZeroOrOne
Import optionel, pas d'exception si aucun export n'est trouvé.
|
[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.
|
[Import]
Lazy<MyClass> importedObject;
// MyClass est instancié lors de son utilisation
importedObject.Value.MyMethod();
|
Export
|
// 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 (IRuleMetadata → RuleName) 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
|
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
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
|
using System.ComponentModel.Composition;
|