Played around with Microsoft Extensibility Framework - MEF

I played around with the June CTP of the Microsoft Extensibility Framework (MEF). I wanted to try both Dependency Injection (DI) and adding classes during runtime to extend an application with more logic without shutting down the main app. As an example I created a LogManager, I thought of the most simple possible thing to do. The LogManager will use different kind of loggers, to log exceptions. During runtime I wanted to add more loggers. For example lets pretend we are building an app where we log all exceptions into a database, suddenly we need to log it into a XML file also. If we don't use a provider based solution, we would probably open our application, add the new code to log into an XML file, recompile and deploy. But what with the MEF we can add the extra logger without recompiling the app, we don't even need to shut it down. (Well, this nothing new, this have bean possible for ages with framework like Spring).

I will start writing about my Loggers before I go into the LogManager and how to add "components" to the MEF frameworks containers. Here is a common interfaced used by the Loggers:

public interface ILogger
{
    void Log(string customMessage, Exception e);
}


The ILogger has one method Log, which takes a custom message and an Exception as an argument. To Implement a Logger I only need to implement this interface. When I use MEF I also need to implement a INotifyImport namespace, which has one method, ImportCompleted.  To add "components" during runtime and let MEF import them, the ImportCompleted method will be called on the imported "component". So if I want to do anything when MEF imports my "components" for example to get some default values, settings or what ever it can be, I have the possibility to do so. When working with MEF, I create two kinds of components, a provider and a consumer. The provider provides the consumer with some data. My Logger is a provider, it will provide the LogManager with functionality to log information. To define a provider I use the MEF's  ExportAttribute located in the System.ComponentModel.Composition namespace. I can give the provider a name by passing a string as an argument to the ExportAttribute, I pass "LogManager.Logger". I also decide to make the Logger a consumer only to use DI to inject Application information about the LogManager. To create consumer, I use the Import attribute. Here is the implementation of Logger which will show an exception in a MessageBox:

namespace Loggers
{
    [Export("LogManager.Logger")]
    public class MessageBoxLogger : ILogger, INotifyImport
    {
        public void Log(string customMessage, Exception e)
        {
            MessageBox.Show(this.LogManagerInfo.Name + " " + customMessage + " " + e.Message);
        }

        
        [Import]
        public AppInfo LogManagerInfo { get; set; }


        public void ImportCompleted()
        {
        }
    }
}

 

I created a "Logger" folder for my application where I will add my Loggers' assemblies. I want  to use MEF to automatically import a Logger during runtime. By using the DirectoryWatchingComponentCatalog, I can add a directory to watch for assemblies. Every consumer and providers which should be connected to each others needs to be added to a CompositionContainer. When using the DirectoryWatchingComponentCatalog, I add a Resolver to the CompositionContainer, this is done by using the Resolver property of the DirectoryWatchingComponentCatalog.

var d = new DirectoryWatchingComponentCatalog();
d.AddDirectory("Logger");

var c = new CompositionContainer(d.Resolver);


The LogManager has the responsibility to import my Loggers from the "Logger" directory, the TryGetImportInfo method of the CompositionContainer is called and the "LogManger.Logger" is passed as an argument. Only the Loggers with the Export attribute set to "LogManager.Logger" will be located. By using the GetBoundValue method of the result I get an instance of the Logger and the providers will export its data to the consumers. All Loggers in the "Logger" folder are added to a List of ILogger. Because I want to inject application information from the LogManager to the Loggers, I need to add my LogManager to the CompositionContainer. This is done by using the AddComponent method of the container.

public class LogManager
{
    private List<ILogger> LogProviders = new List<ILogger>();

    public LogManager()
    {
       var d = new DirectoryWatchingComponentCatalog();
       d.AddDirectory("Logger");

       var c = new CompositionContainer(d.Resolver);
       c.AddComponent<LogManager>(this);

       var result = c.TryGetImportInfos("LogManager.Logger");

       if (!result.Succeeded)
          MessageBox.Show("Can't find any logger", 
                          "MyLogger", 
                           MessageBoxButton.OK, 
                           MessageBoxImage.Error);

       foreach (IImportInfo importInfo in result.Value)
          this.LogProviders.Add((ILogger)importInfo.GetBoundValue());
}

 

To Expert the application information I decided to gather all info into one single class, AppInfo. To make sure the AppInfo is injected to the Logger when they are created, I mark the property ApplicationInfo with the Export attribute and the key for the export is the type of the AppInfo class.

[Export(typeof(AppInfo))]
public AppInfo ApplicationInfo
{
    get
    {
        return new AppInfo()
        {
            Name = "LogManager",
            Version = "v 1.0"
        };
    }
}

 

If you look at the code for the Logger, you can see that I use only an Import attribute on the LogManagerInfo property.

[Import]
public AppInfo LogManagerInfo { get; set; }


If a provider exports a type and the consumer has an import attribute specified for a property with the same type, they are connected.

The LogManager's Log method will iterate through all Loggers and invoke the Logger's Log method.

public void Log(string customerMessage, Exception e)
{
    if (this.LogProviders != null)
        this.LogProviders.ForEach( l => l.Log(customerMessage, e));
}

Now I can use my LogManger and during runtime I can add new loggers to the "Logger" folder and the Logger will be used when the Log method of the LogManger is executed.

2 Comments

  • How is this different from loading dlls on a pluggable architectured application. Isn't this just an extension?

  • Its nice to see people playing around with MEF. One question: why not have your LogManager actually import the ILogger's? Then in your log manager constructor after you call AddComponent call container.Bind() then you can look and see if you got any loggers or not and show your error message. That would typically be the preferred way as opposed to pulling directly on the container (i.e. calling TryGet*).

Comments have been disabled for this content.