Friday, February 6, 2009

MEF – The new extensibility standard

Preface
One of the most impressive technologies knocked out by Microsoft was Windows Communication Foundation. WCF unified various communication capabilities that exists in .NET 2.0 into a single, common, general framework. I think that it is the great achievement!
Recently I found Visual Studio 2010 training kit which includes the set of the hand-on-labs. One of the labs is called "Introduction To Managed Extensibility Framework". I read the instructions and it made me excited. Managed Extensibility Framework unifies the application extensibility capabilities! Actually .NET doesn't provide any "standard" API's for the applications extensibility. There is the plug-in model which should be implemented from scratch by every application that wants to support it. WPF provides the ways for look-and-feel extensibility by using styles, templates, skins and themes. But until now there was no the technology that unifies the extensibility like WCF unifies the communication. Managed Extensibility Framework is unifier guy for the extensibility. In this post I will try to describe my experience with MEF and provide the real-world code sample.

MEF architecture
The MEF architecture is shown here. It is very simple and the extensibility implementation can be described in the following steps:
  • The application provides the set of extensibility points using MEF API.
  • The application hosts the extensibility parts container using MEF API.
  • The plug-in provides the set of extensibility parts using MEF API.
  • The application imports the extensibility parts using MEF API.
The API is pretty straightforward and it's very easy to implement the extensibility support in your application using MEF.

The real-world sample.
Recently I was thinking about providing one more extensibility facilities for Data Dynamics Reports. Briefly - it should provide the capability to use the custom UI editors of some entities. For the real-world sample I selected the similar task:
WPF application provides the editor of the color of something. For example the application for facial composite construction uses the editor for eyes color. The application should provide the built-in color editor and end-user should be able to specify the custom color editor.
For example : the application provides built-in color editor that allows editing of the Red, Green and Blue color components. End-user can select the custom color editor that allows editing the Hue, Saturation and Brightness color components. The application should provide the visual feedback whenever the selected color is changed.


Implementation of the built-in color editor.
So, the built-in color editor allows editing of the Red, Green and Blue color components. WPF doesn't provide the standard color editor and we need to implement it. The implementation of the RGB color editor is straightforward task. We should:
  • Define the dependency properties for the Red, Green and Blue components.
  • Define the dependency property for the currently selected color.
  • Provide the way which can be used by the hosting application to intercept the event when the selected color is changed.
The last thingie is a bit of pain. There is the beautiful article that describes this pain and offers the various solutions for intercepting the event of changing the dependency property value. I will use the simple event(i.e. not routed event) to allow intercepting the selected color changing. Since all that application should know about the color editor is how to intercept the color changing event, I will define the new interface that provides this event and implement this interface in the built-in color editor. There are several alternate ways to design this system, but for it doesn't matter for the topic I discuss. The basic code that defines the color editor control, the dependency property for Red component, the dependency property for the currently selected color and another needed stuff is shown below.

interface IColorChanged
{
   event ColorChangedEventHandler ColorChanged;
}

delegate void ColorChangedEventHandler(object sender, ColorChangedEventArgs args);

class ColorChangedEventArgs : EventArgs
{
   public Color OldValue{ get; set;}
   public Color NewValue{ get; set;}
}

class RGBColorEditor : Control, IColorChanged
{
   private Color _color = Colors.Black;
   public event ColorChangedEventHandler ColorChanged;

   public ColorEditor()
   {
      SetValue(RProperty, _color.R);
      SetValue(SelectedColorProperty, _color);
   }

   public static readonly DependencyProperty RProperty =
   DependencyProperty.Register("R", typeof (byte), typeof (RGBColorEditor),
   new PropertyMetadata((byte)255, OnRChanged));

   public static readonly DependencyProperty SelectedColorProperty =
   DependencyProperty.Register("SelectedColor", typeof(Color), typeof(RGBColorEditor), new    PropertyMetadata(Colors.Black, OnSelectedColorChanged));

   public byte R
   {
      set { SetValue(RProperty, value); }
      get { return (byte)GetValue(RProperty); }
   }

   private void OnRChanged(byte newValue)
   {
      _color.R = newValue;
      SetValue(SelectedColorProperty, _color);
   }

   private static void OnRChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
   {
      (d as ColorEditor).OnRChanged((byte)e.NewValue);
   }

   private void OnSelectedColorChanged(DependencyPropertyChangedEventArgs e)
   {
      if (ColorChanged != null)
         ColorChanged(this, new ColorChangedEventArgs { NewValue = (Color)e.NewValue, OldValue =       (Color)e.OldValue });
   }

   private static void OnSelectedColorChanged(DependencyObject d,       DependencyPropertyChangedEventArgs e)
   {
      (d as ColorEditor).OnSelectedColorChanged(e);
   }
}

The rest of the code defines the dependency properties for the green and blue components of the color and also provides the default control template. RGBColorEditor is contained in the main application assembly.


Custom color editor sample.
So, our custom editor control will allow editing of the Hue, Saturation and Brightness color components. The implementation would be very similar to RGBColorEditor. We implement IColorChanged interface, define the dependency properties for Hue, Saturation, Brightness and SelectedColor properties. Here is the snippet of the code:

class HSBColorEditor : Control, IColorChanged
{
   private static Color ConvertHsbToRgb(double h, double s, double b)
   {
      //return new color created from h,s,b values;
   }
   public static readonly DependencyProperty RProperty =
   DependencyProperty.Register("H", typeof(double), typeof(HSBColorEditor),
   new PropertyMetadata(0, OnHChanged));

   void OnHChanged(double newValue)
   {
      Color newColor = ConvertHsbToRgb(newValue, S, B);
      SetValue(SelectedColorProperty, newColor);
   }

   private static void OnHChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
   {
      (d as HSBColorEditor).OnHChanged((double)e.NewValue);
   }
}

HSBColorEditor class is contained in the separate assembly. Let's say its name is FacialCompositeCustomControls.


Extensibility without MEF.
I think that the most suitable way to provide the extensibility facility without MEF is using Provider Model Design Pattern. The application configuration file would have the list of the available color editors, the application would provide end-user with UI that shows this list and allows selecting the appropriate editor. Then the application would create the selected control instance and add it to the corresponding place.

MEF world.
For our application purpose we will use the color editor selector dialog which is responsible for collecting the custom color editor implementations and allowing end-user to select the appropriate editor. The color editor selector will try collecting the color editors from the assemblies located in the Plugin folder of the the directory of the executing application. Also, the built-in color editor should be added in the collection. So, the first thing that we should do if defining the extensibility points in the color editor selector. MEF allows to do that by using different ways, but the key is System.ComponentModel.Composition.ImportAttribute. Look at the code:

class ColorEditorSelector : Window
{
   [Import(typeof(IColorChanged))]
   private ExportCollection<Control> EditorCollection { get; set; }
   public Control SelectedEditor { get; set; }
}

We defined the extensibility point using ImportAttribute and set the contract for the plug-in - it should implement IColorChanged interface. The color editor collection will be stored in the property of type System.ComponentModel.Composition.ExportCollection<Control>. SelectedEditor property is used by the application to get the color editor selected by user. Then we need to add the code which looks for the available color editors in the executing assembly and Plugin folder. The code is shown below. In the MEF terms we will add the composition container which loads the compositions parts from the executing assembly and from Plugins folder.

private void Compose()
{
   var catalog = new AggregateCatalog();
   var catalog1 = new AssemblyCatalog(typeof(ColorEditorSelector).Assembly);
   catalog.Catalogs.Add(catalog1);
   var catalog2 = new DirectoryCatalog("Plugins", true);
   catalog.Catalogs.Add(catalog2);
   var container = new CompositionContainer(catalog);
   var batch = new CompositionBatch();
   batch.AddPart(this);
   container.Compose(batch);
}

This code is like design pattern for the composition. It defines several places from which the extensible parts can be loaded, adds them to the container and performs the composition. The composition batch is used for the dynamic re-composition. I.e. if one copies the new assembly contains the custom color editor in Plugins folder, then it is handled dynamically without re-run the application. Compose method is called from ColorEditorSelector constructor. Here is the code that runs the composition and fills the listbox by the type names of the available color editors:

public ColorEditorSelector()
{
   Compose();
   var list = new ListBox();
   foreach(var colorEditor in EditorCollection)
   {
      var control = colorEditor.GetExportedObject();
      list.Items.Add(control.GetType().Name);
   }
}

The last thing that we should do is add the extensibility points definition in the color editor implementations. It is done by using System.ComponentModel.Composition.ExportAttribute:

[Export(typeof(IColorChanged))]
internal sealed class RGBColorEditor : Control, IColorChanged

[Export(typeof(IColorChanged))]
internal sealed class HSBColorEditor : Control, IColorChanged

And that's all about the extensibility. The rest of ColorEditorSelector dialog composes the UI which allows to select the appropriate color editor and sets SelectedEditor accordingly. The application uses the SelectedEditor to add it to UI. Looks very straightforward and great, isn't it?


In conclusion.
MEF is included in .NET Framework 4.0 CTP and I think it is really new standard for the application extensibility. Microsoft did the great job in design and implementation of MEF library and I will definitely will use it!

1 comment:

  1. Great post Sergey! I'm a fan of the IServiceProvider interface myself. Looks like MEF is the new kid in town. I'll have to give this a try. Your post breaks it down very nicely. Well done indeed sir. :) Thanks!

    ReplyDelete