MEF Recomposition

2009/11/05

tags: ,
one comment

thinking about MEF as another IoC container led us into misconception.
MEF design goal was being a generic extensibility infrastructure,
its just happens that it overlap with the IoC world (and yes it has most of the IoC feature).

this post address the challenge of building extensions over none CLR types (like image files)

from the technical perspective it will discuss the following techniques:

the source code for this post can be found here.

 

Prerequisite

this is advance post which assume basic understanding of the MEF technology (for MEF introduction read this post).

 

Our Test Case

in order to demonstrate the above techniques I choose common scenario of building
very simple WPF application that display pictures which came from heterogeneous data source providers.

image

The Solution Modeling

image

ComponentModel.Initialization.dll

this components design by the MEF team as centric handle for the MEF container functionality

(i did very minor changes to the CompositionHost class, basically exposing the underline container as Export Provider)

ImageExportProviders.dll

this is the heart of this post,.

Export providers (not the catalog) is the underline MEF unit that responsible for the instantiation of the exported parts.

the different providers implementations (local, Picasa, Fickr) has the knowledge of
how to instantiate the discovered photo files into CLR type that implement IPhotoInfo.

MEFPhotoDataSourceProvider

this class is responsible for abstracting the WPF application from it actual photos sources.

WpfImageViewerUI.exe

is the UI implementation

 

Application flow

from this point i will start to explain the different techniques used in the application,
starting with the UI.

 

UI (WpfImageViewerUI  project)

    App.xaml

At the code behind level, it initial the Container with custom MEF Export Providers

and pass the container to the Composition Host (the composition host used as

the default container, it is reachable from anywhere in the code)

Code Snippet
  1. protected override void OnStartup(StartupEventArgs e)
  2. {
  3.     base.OnStartup(e);
  4.  
  5.     var providers = new ExportProvider[] {
  6.         new LocalImageExportProvider("Images", "*.jpg"),
  7.         new PicasaImageExportProvider(URL_PICASA_GENERAL),
  8.         new FlickrImageExportProvider(URL_FLICKR_PEOPLE)
  9.     };
  10.     var container = new CompositionContainer(providers);
  11.  
  12.     CompositionHost.InitializeContainer(container);
  13. }

At the Xaml level we embed the Style.xaml

Code Snippet
  1. <Application.Resources>
  2.     <ResourceDictionary>
  3.         <ResourceDictionary.MergedDictionaries>
  4.             <ResourceDictionary Source="Style.xaml" />
  5.         </ResourceDictionary.MergedDictionaries>
  6.     </ResourceDictionary>
  7. </Application.Resources>

     Style.xaml

For better separation between the graphical designer and the developer, I’m using the style.xaml
as the designer own xaml (only the designer should touch this file, he can change styles and templates).

     PictureShell.xaml

this is the developer own xaml, which the designer shouldn’t touch.

it should contain the minimal xaml needed, from the functionality perspective

(it should not have any beauty factor actually it should be really ugly without the style.xaml)

Code Snippet
  1. <Window x:Class="Bnaya.Samples.PictureShell"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.         xmlns:local="clr-namespace:Bnaya.Samples.Data…"
  5.     Title="PictureShell" Height="300" Width="600">
  6.     <Window.DataContext>
  7.         <local:MEFPhotoDataSourceProvider/>
  8.     </Window.DataContext>
  9.  
  10.     <ListBox x:Name="_lst" ItemsSource="{Binding IsAsync=True}" />
  11. </Window>

  
as you can see it kept short, clean and easy to understand.

from the debugging perspective you can disembed the style.xaml and check whether the
bug came from the functionality or the design.

this xaml contain:

    ListBox declaration
    MEFPhotoDataSourceProvider (which is the binding source of the list’s ItemSource)

 

UI.Components (define the MEFPhotoDataSourceProvider class)

as we mention earlier we using data source provider to abstract the actual origin of the data.

the xaml won’t have to be changed whenever adding or removing data sources.

another benefit is the ability of a-sync loading (data source that expose hundreds of photos cannot expose synchronously)

Code Snippet
  1. public class MEFPhotoDataSourceProvider : DataSourceProvider
  2. {
  3.     private ObservableCollection<IPhotoInfo> _photos =
  4.         new ObservableCollection<IPhotoInfo>();
  5.     public MEFPhotoDataSourceProvider()
  6.     {   // register for MEF ewcomposable discovery
  7.         CompositionHost.Host.ExportsChanged += OnPhotosChangedHandler;
  8.     }
  9.  
  10.     protected override void BeginQuery()
  11.     {   // a-sync initialization
  12.         ThreadPool.QueueUserWorkItem(Init);
  13.     }
  14.  
  15.     [ImportMany(typeof(IPhotoInfo), AllowRecomposition = true)]
  16.     private IEnumerable<Lazy<IPhotoInfo>> PhotosInternal { get; set; }
  17.  
  18.     private void Init(object state)
  19.     {
  20.         PartInitializer.SatisfyImports(this); // load the picture through MEF
  21.         PhotosInternal.ForEach(lazy => _photos.Add(lazy.Value)); // add
  22.         base.OnQueryFinished(_photos);
  23.     }
  24.  
  25.     private void OnPhotosChangedHandler(object sender,ExportsChangeEventArgs e)
  26.     {
  27.         foreach (var expDef in e.AddedExports) {
  28.             if (expDef.Metadata.ContainsKey("Export")) {
  29.                 Invoke(() =>
  30.                 {
  31.                     Export exp = expDef.Metadata["Export"] as Export;
  32.                     _photos.Add(exp.Value as IPhotoInfo);
  33.                 });
  34.             }
  35.         }
  36.     }
  37. }

    at line 15, you can see that we using the ImportMany decoration with AllowRecomposition = true 
this is the key of the a-sync capabilities (lazy loading).
you can download the source code from here and run the application with and without AllowRecomposition 
in order to see its effect.

    at line 20, PartInitializer.SatisfyImports(this) is actually asking the default MEF container (which we define at the app.xaml)
for the discovered export parts that match the import.

    at line 22, we introduce the base class with our data source collection.

    at the constructor level line 7, CompositionHost.Host.ExportsChanged += OnPhotosChangedHandler we register to the
default  MEF container changed event (again the a-sync lazy loading)

    at the OnPhotosChangedHandler, line 25, we adding any new composed instances into the bind-able collection.

 

Finally we got into the heart of extensibility model, the Export Providers

we use custom Export Provider for discovering none CLR entities (in our case photo files) and introduce it to the CLR world
via the MEF infrastructure as instances that implement IPhotoInfo.

the ImageExportProviders project contain 4 files (one base class and 3 providers)

    ImageExportProviderBase is the provider base class

Code Snippet
  1. public abstract class ImageExportProviderBase : ExportProvider
  2. {
  3.     protected static readonly string CONTRACT_NAME = typeof(IPhotoInfo).FullName;
  4.     protected readonly string _path;
  5.     public ImageExportProviderBase(string path) {
  6.         _path = path;
  7.     }
  8.     protected override IEnumerable<Export> GetExportsCore(
  9.         ImportDefinition definition, AtomicComposition atomicComposition)
  10.     {
  11.         IEnumerable<Export> exports = null;
  12.         if (definition.IsRecomposable) // Async
  13.             ThreadPool.QueueUserWorkItem(LoadPhotos, definition); // we will use recomposition
  14.         else
  15.             exports = OnLoadPhotos(definition);
  16.  
  17.         return exports;
  18.     }
  19.     protected abstract IEnumerable<Export> OnLoadPhotos(ImportDefinition definition);
  20.     private void LoadPhotos(object state) {
  21.         OnLoadPhotos(state as ImportDefinition);
  22.     }
  23.     protected void IntroduceNewExport(Export e) {
  24.         var metadata = new Dictionary<string, object>();
  25.         metadata.Add("Export", e);
  26.         var expDef = new ExportDefinition(CONTRACT_NAME, metadata);
  27.         using (var atomic = new AtomicComposition())
  28.         {
  29.             var argsChanging = new ExportsChangeEventArgs(
  30.                 new ExportDefinition[] { expDef }, new ExportDefinition[0], atomic);
  31.             atomic.AddCompleteAction(() => { });
  32.             base.OnExportsChanging(argsChanging);
  33.             atomic.Complete();
  34.         }
  35.         var argsChanged = new ExportsChangeEventArgs(
  36.             new ExportDefinition[] { expDef }, new ExportDefinition[0], null);
  37.         base.OnExportsChanged(argsChanged);
  38.     }
  39. }

The GetExportsCore is what we have to override when creating custom Export Provider.
you can see at line 12, that we check whether the import definition is recomposable and act accordingly.

in case of recomposable the IntroduceNewExport method is where we notify about the new available exports.

the 3 derived class is just a parser for specific source type.

    LocalImageExportProvider discover and parse file on the local file system.

    FlickrImageExportProvider discover and parse file from the Flickr web site.

Flickr API does not support thumbnails, which results that pictures discovered on Flickr will appear with delay.

    PicasaImageExportProvider discover and parse file from the Picasa web site.

Conclusion

the MEF ability to compose beyond the CLR world is one of its underline extensibility design that
differentiate MEF from typical IoC containers.

and we can use MEF to abstract our data sources achieving a-sync and loosely coupled advantage.

 

the source code for this post can be found here.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

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=""> <s> <strike> <strong>

*

one comment

  1. Yvonne2013/12/23 ב 01:14

    Hi! I know this is kinda off topic however , I’d figured I’d ask.
    Would you be interested in trading links or maybe guest writing a blog article or vice-versa?

    My site discusses a lot of the same topics as yours and I feel we could greatly benefit from
    each other. If you’re interested feel free to shoot me an e-mail.
    I look forward to hearing from you! Great blog by the way!

    my blog: webpage – Yvonne

    Reply