Skip to content

Instantly share code, notes, and snippets.

@nthdeveloper
Last active June 16, 2019 08:58
Show Gist options
  • Save nthdeveloper/b07b300b94867ff12e5bdd4a9d84a8e3 to your computer and use it in GitHub Desktop.
Save nthdeveloper/b07b300b94867ff12e5bdd4a9d84a8e3 to your computer and use it in GitHub Desktop.
Managed Extensibility Framework (MEF) cheat sheet.

Documentation

  • Attributed Programming Model

Managed Extensibility Framework

System.ComponentModel.Composition

System.ComponentModel.Composition.Hosting

  • Attribute-less Programming Model

System.ComponentModel.Composition.Registration

Reference Libraries and Namespaces

  • Assemblies

System.ComponentModel.Composition.dll

  • For Attribute-less Programming

    • System.Reflection.Context.dll
    • System.ComponentModel.Composition.Registration.dll
  • Namespaces:

System.ComponentModel.Composition System.ComponentModel.Composition.Hosting

  • For Attribute-less Programming
    • System.ComponentModel.Composition.Registration

Simplest Usage

CompositionContainer container = new CompositionContainer(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
//Get an exported service and use it
var calc = container.GetExport<ICalculator>();
int result = calc.Value.Add(2, 3);
CompositionContainer container = new CompositionContainer(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
//Fill the current class instance with its dependencies
//Exports are added to the container
container.ComposeParts(this);

//Fill the dependencies but if container is updated (new parts added or removed) the composed instance won't be updated
//Exports are ignored
container.SatisfyImportsOnce(this);

Catalogs

A catalog discovers and provides composable parts. catalog.Parts Part definitions (ComposablePartDefinition) in the catalog.

AssemblyCatalog

Discovers attributed parts in a managed code assembly.

var catalog = new AssemblyCatalog(typeof(Program).Assembly);

DirectoryCatalog

Discovers attributed parts in the assemblies in a specified directory.

var catalog1 = new DirectoryCatalog(".");
var catalog2 = new DirectoryCatalog(extensionPath);
var catalog3 = new DirectoryCatalog(extensionPath, "*.dll");

//Getting an event whenever a file is loaded
dirCatalog.Changed += new EventHandler(catalog_Changed);

TypeCatalog

Discovers attributed parts from a collection of types. Used to disable assembly scanning and provide types manually.

//TypeCatalog(params Type[] types)
var catalog = new TypeCatalog(typeof(MyOtherComponent));

FilteredCatalog

A filtered part catalog created from another catalog with a filter function.

FilteredCatalog (ComposablePartCatalog catalog, Func<ComposablePartDefinition,bool> filter);

AggregateCatalog

A catalog of catalogs

var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));
catalog.Catalogs.Add(new DirectoryCatalog(extensionPath));
//...

Containers

_container = new CompositionContainer(catalog);
//Fill the dependencies
_container.ComposeParts(this);

//Get exports (lazy)
Lazy<IPlugin> exportItem = container.GetExport<IPlugin>();//If there is no or more than 1 matching export, exception is thrown
IEnumerable<Lazy<IPlugin>> exportItems = container.GetExports<IPlugin>();
IEnumerable<Lazy<IPlugin>> exportItems = container.GetExports<IPlugin>("ContractName");//IPlugins with contract name ContractName
IEnumerable<Lazy<object, object>> exportItems = container.GetExports(typeof(IPlugin), typeof(IPluginMetaData), "ContractName");
Lazy<IPlugin, IPluginMetaData> exportItems = container.GetExports<IPlugin, IPluginMetaData>("ContractName");
Lazy<ITranslator, ITranslatorMetadata> translator = container.GetExports<ITranslator, ITranslatorMetadata>()
                                .Where(t => t.Metadata.SourceLanguage == "hu-HU" && t.Metadata.TargetLanguage == "en-US").FirstOrDefault();

//Get exports (immediately)
IPlugin exportItem = container.GetExportedValue<IPlugin>();//If there is no or more than 1 matching export, exception is thrown
IPlugin exportItem = container.GetExportedValueOrDefault<IPlugin>();//Still throws exception if there is more than 1 matching export
IEnumerable<IPlugin> exportItems = container.GetExportedValues<IPlugin>();
//...

Parts

Implement IPartImportsSatisfiedNotification interface in a part to be notified when the composition process is completed for the part

class Plugin1 : IPartImportsSatisfiedNotification
{
    public void OnImportsSatisfied()
    {    
    }
}

Export Attributes

[Export(typeof(IDisplayAdapter))]
[Export("ContractName"))]//ContractName is any name, it will be matched with the name specified in [Import] attribute
[Export(“ContractName”, typeof(IDisplayAdapter))]//Use metadata attribute instead of name

//When defined for a base class or interface, all derived or implemnting classes exorts this component
//Implementing extension project should not anything about MEF
[InheritedExport]
[InheritedExport(typeof(IService))]//Same overloads with Export attribute
public class NumThree {}

public class NumFour : NumThree {}//NumFour also automatically exported
  • Exporting with Metadata
[Export(typeof(ITranslator))]
[ExportMetadata("SourceLanguage", "de-DE")]
[ExportMetadata("TargetLanguage", "en-US")]
public class GermanEnglishTranslator : ITranslator
{
 //...
}
  • Custom Export Attributes (better than metadata)
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class TranslatorExportAttribute : ExportAttribute, ITranslatorMetadata
{
   public TranslatorExportAttribute(string sourceLanguage, string targetLanguage)
      : base(typeof(ITranslator))
   {
      this.SourceLanguage = sourceLanguage;
      this.TargetLanguage = targetLanguage;
   }
   public string SourceLanguage { get; private set; }
   public string TargetLanguage { get; private set; }   
}

//Usage
[TranslatorExport("de-DE", "en-US")]
public class GermanTranslator : ITranslator
{
}
  • Export a method
 [Export(typeof(Func<int, string>))]
 public string DoSomething(int TheParam) {}

Import Attributes

[Import(typeof(ICalculator))]
public/private ICalculator calculator;
Lazy<T>, Lazy<T, TMetadata>//Get lazy 

[Import("ContractName", typeof(MyInterface))] //Get specific import with name
//Allow setting default value for the dependency if no appropriate export is found (Optional import)
[Import(AllowDefault = true)]
//Property or field will be recomposed when exports with a matching contract have changed in the container
[Import(AllowRecomposition=true)]

[ImportMany(typeof(IComePlugin))]
List<IComePlugin>/IEnumerable<T>/LazyList<T> Plugins {get; set;}

[ImportMany(AllowRecomposition=true)]//Same as Import

[ImportMany]
IEnumerable<Lazy<IOperation, IOperationData>> operations;
public interface IOperation
{
    int Operate(int left, int right);
}

//Properties of this interface are automatically filled with values from ExportMetadata attributes
public interface IOperationData
{
    Char Symbol { get; }
}

[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '+')]
class Add: IOperation
{
   public int Operate(int left, int right)
    {
        return left + right;
    }
}

// We need a translator from hungarian to english
Lazy<ITranslator, ITranslatorMetadata> translator = container.GetExports<ITranslator, ITranslatorMetadata>()
.Where(t => t.Metadata.SourceLanguage == "hu-HU" && t.Metadata.TargetLanguage == "en-US").FirstOrDefault();
  • Import a method
 [Import] //Contract name must match!
 public Func<int, string> DoSomething { get; set; }

Export Factory

  • ExportFactory improves granularity by allowing developers to create new instances of dependencies and control the lifetimes of the parts.
  • ExportFactory<T> can be exported which actually exports the type T, but it doesn’t create instance. Instance of the type will get created when it is accessed with method ExportCreator<T>.CreateExport().
  • ExportFactory<T> is created automatically like Lazy<T>
  • The return value of ExportCreator<T>.CreateExport() is a ExportLifetimeContext<T>. ExportLifetimeContext<T> has a property called Value that contains instance of the type T.
[ImportingConstructor]
public ExportFactoryExample(ExportFactory<Greetings> factory)
{
      _factory = factory;
}

public string Greeting(string name)
{
   using (var instance = _factory.CreateExport())
   {
         return instance.Value.Greet(name);
   }
}

Constructor Injection

  • Composition engine uses parameter-less constructor by default
  • Use a different constructor with ImportingConstructor attribute
[ImportingConstructor]
public MyClass([Import(typeof(IMySubAddin))]IMyAddin MyAddin)//Type parameter can be omitted
{
    _theAddin = MyAddin;
}

CompositonBatch

Colection of objects to be filled with parts

CompositionContainer container = new CompositionContainer(dirCat);
CompositionBatch batch = new CompositionBatch();
batch.AddPart(this);
container.Compose(batch);

Instance Mode

  • PartCreationPolicy
    • Any: Specifies that the CompositionContainer will use the most appropriate CreationPolicy for the part given the current context. This is the default CreationPolicy. By default, CompositionContainer will use Shared, unless the ComposablePart or importer requests NonShared.
    • NonShared: New object for each requests
    • Shared: Singleton
//Specifying creation policy while exporting
[Export, PartCreationPolicy(CreationPolicy.Any/Shared/NonShared)]

//Requesting a creation policy while importing
[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]

RequiredCreationPolicy of Import attribute overrides PartCreationPolicy specified for an export

Attribute-less Registration

RegistrationBuilder is introduced that handles part creation and export registration automatically, it has solved both problems arising because the export attribute is required for type registration.

RegistrationBuilder

Samples Blogpost

Assembly: System.ComponentModel.Composition.Registration.dll Also need: System.Reflection.Context.dll Namespace: System.ComponentModel.Composition.Registration

 var conventions = new RegistrationBuilder();
 conventions.ForTypesDerivedFrom<Controller>().Export().SetCreationPolicy(CreationPolicy.NonShared);
 //RegistrationBuilder rules replace the attribute syntax. So, to apply these rules to all types in an assembly, an AssemblyCatalog is used
 Assembly controllersAssembly = Assembly.GetExecutingAssembly();
 var catalog = new AssemblyCatalog(controllersAssembly, conventions);
 var container = new CompositionContainer(catalog);

* RegistrationBuilder methods
  • ForType<T>() — selects the single type T. For example ForType() would set up a rule for the concrete HomeController type.
  • ForTypesDerivedFrom<T>() — selects types assignable, but not equal to, a contract type T. This may be a base class or an interface.
  • ForTypesMatching(Predicate<Type> predicate) — selects types that match a Boolean selector.
   ForTypesMatching(t => t.Name.EndsWith("Repository")) //will select all types whose name ends with the word “Repository”.
  • Export samples
conventions.ForType<MainWindow>().Export();
conventions.ForType<MainWindow>().Export<IView>();
conventions.ForType<MainWindow>().Export<IView>().Export<ICloseable>();//Multiple exports
conventions.ForType<MainWindow>().ExportInterfaces(i => i.IsPublic);//Multiple interfaces can be exported using a filter
//Overloads exist for configuring the export with a contract name or with metadata
conventions.ForType<MainWindow>().Export<IView>(x => x.AddMetadata("Name", "Main"))
  • Ading Metadata
conventions.ForType<OutputWindow>().AddMetadata("Mode", "Debug");
//it can be calculated from the type
conventions.ForType<OutputWindow>().AddMetadata("Mode", t => GetMode(t));
  • Specifying importing constructor
conventions.ForType<MainWindow>().SelectConstructor(pb => new MainWindow(pb.Import<ILogger>()));
conventions.ForTypesDerivedFrom<IView>().SelectConstructor(ctors => ctors.Min(ctor => ctor.GetParameters().Length));

Diagnostics Improvements

Use CompositionOptions.DisableSilentRejection to get exceptions when any composition problem occurs.

container = new CompositionContainer(agrCatelog, CompositionOptions.DisableSilentRejection);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment