adrift in the sea of experience

Wednesday, January 27, 2010

Using MEF for classes which take configuration values

We're using the Managed Extensibility Framework (part of the upcoming .NET 4.0) at work for a new project.

I have a few pre-MEF classes which take strings, integers other primitive data types in their constructor. For example, consider the following C# class which tracks recently used resources (e.g. the last files opened by the application) by saving them in a file:

[Export(typeof(IRecentlyUsedTracker))]
public class RecentlyUsedTracker : IRecentlyUsedTracker
{
   private readonly string file;
   private readonly int maxItems;

   // constructor
   public RecentlyUsedTracker(string recentlyUsedFile, int maxItems)
   {
      this.file = recentlyUsedFile;
      this.maxItems = maxItems;
   }

   // Marks the given resource as recently used.
   public void Touch(string resource)
   {
      ...
   }
}

The above export doesn't work, because MEF cannot instantiate this class. Obviously it cannot know which string and integer to use as arguments for the constructor.

You can still do it by adding [Import("somename")] attributes to the constructor declaration like this:

    // constructor
    public RecentlyUsedTracker(
       [Import("RecentlyUsedTracker.File")] string recentlyUsedFile,
       [Import("RecentlyUsedTracker.MaxItems")] int maxItems)
However, that makes it much more complex to set up the container. Each configuration value has to be explicitly added to the MEF container with ComposeExportedValue as shown below. Blergh! (correction: see update below!)
var catalog = ... some catalog ...
var container = new CompositionContainer(catalog);
container.ComposeExportedValue<string>("RecentlyUsedTracker.File", @"c:\recentlyused.txt");
container.ComposeExportedValue<int>("RecentlyUsedTracker.MaxItems", 5);

My next idea was then to do something like this for the constructor:

   public RecentlyUsedTracker([Import] IConfigurationProvider configurationProvider)
   {
      this.recentlyUsedFile = configurationFile.GetValue<string>("recentlyUsedTracker.File");
      this.maxItems = configurationProvider.GetValue<int>("recentlyUsedTracker.maxItems");
   }

I'm a bit worried about the fact that I'm importing the configurationProvider object only to use it briefly in the constructor. It's also annoying that I need to mock this service in my unit tests, instead of just passing a value. I've asked on the MEF forum if there is a better way.

Update: turns out there is a better way. My first attempt (adding attributes to constructor arguments) is just fine. It's just explicitly adding configuration values to the container that was the bad idea. As Glenn Block suggested, you can just export the configuration values via properties like this:

public class RecentlyUsedTrackerConfiguration
{
  public RecentlyUsedTrackerConfiguration()
  {
     //set values here
  }

  [Export("RecentlyUsedTracker.File")]
  public string File {get;set;}

  [Export("RecentlyUsedTracker.MaxItems")]
  public int MaxItems {get;set;}
}

4 comments:

Anonymous said...

This was an incredibly helpful post. Thank you SO much for sharing this!

Lorenzo said...

Hi, thank you so much for this post.

Is it possible to have the code sample? Can you explain in more detail the part used for dicovering modules? Thank you again. Regards.

Wim Coenen said...

@Lorenzo: it sounds like you are looking for documentation on MEF in general. I recommend reading through the MEF programming guide first: http://mef.codeplex.com/wikipage?title=Guide

Once you have read the MEF programming guiden and have played around with MEF a bit, my post should make more sense.

Lorenzo said...

Thank for your reply...

I've read the doc but I'm not able to configure it properly. Could you help me?

I've also posted in mef forum. Check this link:

"http://mef.codeplex.com/Thread/View.aspx?ThreadId=214968"

Thanks again. Best regards.