MEF 2.0 – mini series: part 6 (Composition scoping and lifetime management)

2013/01/16

MEF 2.0 – mini series: part 6

(Composition scoping and lifetime management)

this is the 6th post in the MEF 2.0 mini series.
you can see the following TOC for other posts in this series.

in this post I will cover a new concept of scoping and part lifetime management, which is a great improvement over MEF 1.

DEV, MEF, SELA, Export, Import, CompositionContainer, RegistrationBuilder, Catalog

MEF 1 was coming with a fairly naïve lifetime management.
part’s lifetime could be either shared or non-shared (you could also apply ‘any’ but eventually ‘any’ will be created as shared or non-shared).
shared is a singleton instantiation, while non-shared will create a new instance each time.

MEF 1′s instantiation model doesn’t support a complex scenario where some dependency’s lifetime should be dictate by the lifetime of other unites.
you can conceder a UI window that is having plug-ins that should be dispose while the UI window is closing.

for example consider you have the following components (parts) dependency flow:

DEV, MEF, SELA, Export, Import, CompositionContainer, RegistrationBuilder, Catalog

the application can have multiple processes.
each process is having some plugins, but each view should have different instantiation of the plugins.
until now you can argue that a non-shared instantiation will do the job.
the next dependency level (DAL) should be shared under the boundary of a process but shouldn’t be shared across processes
this one can neither handle by the share nor by non-shared instantiation.
single instance of DAL should be created under each view scope.

MEF 2 added a scoped lifetime management.
I will use the attribute based model for this sample, but it will work in the same way in the fluent model.

Code Snippet
  1. [Export]
  2. public class Application
  3. {
  4.     [Import]
  5.     private ExportFactory<ProcessA> ProcAFactory { get; set;}
  6.     [Import]
  7.     private ExportFactory<ProcessB> ProcBFactory { get; set;}
  8. }
  9.  
  10. [Export]
  11. public class ProcessA
  12. {
  13.     [Import]
  14.     public PluginA PlugA { get; private set; }
  15.     [Import]
  16.     public PluginB PlugB { get; private set; }
  17. }
  18.  
  19. [Export]
  20. public class ProcessB
  21. {
  22.     [Import]
  23.     public PluginA PlugA { get; private set; }
  24.     [Import]
  25.     public PluginB PlugB { get; private set; }
  26. }
  27.  
  28. [Export]
  29. public class PluginA
  30. {
  31.     [Import]
  32.     public DAL Dal { get; private set; }
  33. }
  34.  
  35. [Export]
  36. public class PluginB
  37. {
  38.     [Import]
  39.     public DAL Dal { get; private set; }
  40. }
  41.  
  42. [Export]
  43. public class DAL
  44. {
  45. }

at line 4-7 you can see a new type of importing target called ExportFactory<T> this type will initialize the scope.
It is having a CreateExport method that return a ExportLifetimeContext<T> can control the part life time.

the full implementation of the Application class is:

Code Snippet
  1.   [Export]
  2.   public class Application
  3.   {
  4.       [Import]
  5.       private ExportFactory<ProcessA> ProcAFactory { get; set;}
  6.       [Import]
  7.       private ExportFactory<ProcessB> ProcBFactory { get; set;}
  8.  
  9.       public void WriteLayoutA()
  10.       {
  11.           using (ExportLifetimeContext<ProcessA> lifeOfA = ProcAFactory.CreateExport())
  12.           {
  13.               ProcessA a = lifeOfA.Value;
  14.               Console.WriteLine("Proc A");
  15.               Console.WriteLine("\tPlug A: {0}", a.PlugA.GetHashCode());
  16.               Console.WriteLine("\t\tDal: {0}", a.PlugA.Dal.GetHashCode());
  17.               Console.WriteLine("\tPlug B: {0}", a.PlugB.GetHashCode());
  18.               Console.WriteLine("\t\tDal: {0}", a.PlugB.Dal.GetHashCode());
  19.           }
  20.       }
  21.  
  22.       public void WriteLayoutB()
  23.       {
  24.           using (ExportLifetimeContext<ProcessB> lifeOfB = ProcBFactory.CreateExport())
  25.           {
  26.                 ProcessBb = lifeOfB.Value;
  27.               Console.WriteLine("Proc B");
  28.               Console.WriteLine("\tPlug A: {0}", b.PlugA.GetHashCode());
  29.               Console.WriteLine("\t\tDal: {0}", b.PlugA.Dal.GetHashCode());
  30.               Console.WriteLine("\tPlug B: {0}", b.PlugB.GetHashCode());
  31.               Console.WriteLine("\t\tDal: {0}", b.PlugB.Dal.GetHashCode());
  32.           }
  33.       }
  34.   }

you can see the usage of ExportFactory<T> at lines 11 and 24.

having a lifetime handled part is the first step for the scoping.
now we can define which part will be managed by the scope.
for each scope we except to find a single instantiation of each scoped part.
as you can see in the diagram below, the Plugins and the DAL should be instantiate once for each scope.

Presentation1

in order to define what’s goes within the scope we should use the CompositionScopeDefinition as shown in the following code snippet:

Code Snippet
  1. static void Main(string[] args)
  2. {
  3.     var scopeDependentCatalog = new TypeCatalog(
  4.         typeof(ProcessA),
  5.         typeof(ProcessB),
  6.         typeof(PluginA),
  7.         typeof(PluginB),
  8.         typeof(DAL));
  9.     var scopeDefDependent = new CompositionScopeDefinition(scopeDependentCatalog, null);
  10.  
  11.     var appCatalog = new TypeCatalog(typeof(Application));
  12.     var scopeDefRoot = new CompositionScopeDefinition(appCatalog, new[] { scopeDefDependent });
  13.  
  14.  
  15.     var container = new CompositionContainer(scopeDefRoot);
  16.  
  17.     var app = container.GetExportedValue<Application>();
  18.  
  19.     app.WriteLayoutA();
  20.     Console.WriteLine("————————————");
  21.     app.WriteLayoutB();
  22. }

you can see the scope dependent definition at line 9
and the hierarchic between the scope and the application at line 12.

as you may notice it is possible to have deeper hierarchic, but this will be shown in future posts.

the output is:

scopeoutput

remember that we was printing the part’s hash code.
you can easily see that each process is having different plugin and DAL’s instantiations, but even those the DAL was consumed by both plugins it has a single instantiation per scope.

Summary

scoping is a very powerful instantiation model which can solve some of the real-life scenario which wasn’t fall into the shared or non-shared models.

in future post I will present a deeper hierarchic and the catalog filtering capability.

Shout it

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>