Introduction
The goal is to learn how to use MEF in a very simple way. Let's build a component for recording logs, which is so important in applications, and this component should record logs in different ways. For example, log into a database, Windows event log, file server, etc. The big advantage of a pluggable component is that you can add other loggers without having to modify the application, and it's quite simple thanks to the MEF team..
Background
MEF allows us to work with the concept of plug-ins. It provides a framework that allows us to specify the points where the application may be extended, exposing modules that can be plugged by external components. Rather than explicitly reference the components in the application, MEF allows our application to find components at runtime, through the composition of parts, managing what it takes to keep these extensions. Thus, our application does not depend on an implementation, but an abstraction, and can add new features to a program without the need to recompile, or even, without interrupting its execution.
MEF architecture (quite simple...)
To build a pluggable application using MEF, we must follow these steps:
- Define extension points
The extension points are the "parts" of our application where we want to allow extensions. In our example, the extension point is the log writing.
- Define an MEF Contract for each extension point
For each extension point set, we need to define an MEF contract that can be a delegate or an interface. In our example, we will create a DLL containing the interface that defines our MEF Contract.
- Define a class to manage the extension point
To inform MEF how to manage our plug-ins, we use the Import
attribute. In our example, the PluginHandler
class will manage the extensions.
- Create the plug-in
To create an extension, we should first implement the MEF Contract to the desired extension point. This extension is known as an MEF Composable Part. To define a class as a Composable Part, we use the attribute Export
.
- Define a Catalog
The catalog holds, at runtime, a list of all imported Composable Parts. The catalogs can be of type AssemblyCatalog
, DirectoryCatalog
, TypeCatalog
, and DeploymentCatalog
. In our example, we use the DirectoryCatalog
, which discovers plug-ins in a certain directory.
Using the code
Our solution will consist of five projects.
Project name
| Project type
| description
| References
|
MefLogger
| Class Library
| Main DLL, to be used by your application
| MefLogger.Interface
|
MefLogger.Interface
| Class Library
| DLLl to be used by plug-ins
|
|
MefFileLoggerPlugin
| Class Library
| Plug-in to register log in text files
| MefLogger.Interface
|
MefEventLoggerPlugin
| Class Library
| Plug-in to register log in the Windows Event Log
| MefLogger.Interface
|
ApplicationTest
| Console
| Test application
| MefLogger
|
The solution is based on the following concept:
In order to create a pluggable system, we must export an interface that must be implemented by plug-ins. For this, I created a separate project to define the interface MefLogger.Interface
.
The MefLogger project is our component that will use MEF to export and import the plug-in interface, and manage and fire commands to the plug-ins. The interesting thing is that the MefLogger project does not know how to register logs; it's the plugins who need to know how to do this. This project is a DLL that will find and manage plug-ins; this way, we don't need to bother implementing extension points in every application we want to implement logging. Just reference the MefLogger DLL in our application.
The MefFileLoggerPlugin and MefEventLoggerPlugin projects are our plug-ins. The SampleApp project is a very simple console application that demonstrates the use of the MefLogger DLL. Well, let's move to the example.
Create a new blank solution and give it the name MefLoggerSolution, as in Figure 1.
Figure 1
Add a new Class Library project by clicking the right mouse button (for righties) on the solution. Name this project MefLogger.Interface. This project will contain the contracts of our plug-in.
Figure 2
Figure 3
Rename Class1
to IMefLogger
. It is a good practice to use the letter "I" at the beginning of the name to identify the class as an interface..
Figure 4
Figure 5
By renaming the class, we will receive the following message:
Figure 6
Clicking the Yes button, Visual Studio will replace all references to Class1
with IMefLogger
.
Modify the content of the class for it to be an interface. The code should like below::
using System;
namespace MefLogger.Interface
{
public interface IMefLogger
{
void Log(string message);
}
}
Add a new Class Library project to our solution. Name this project MefLogger. This project will be our main DLL.
Figure 7
Figure 8
Change the Class 1 name for the Logger.
Add a new class to the MefLogger project called PluginHandler
. This class will be responsible for loading and firing commands to the plug-ins.
Figure 9
Figure 10
Now let's add the necessary references. As I haven't registered MEF in the GAC, I will add references to the MEF DLLs located in a folder..
Figure 11
Add a reference to the MefLogger.Interface project..
Figure 12
Also add an Application Configuration File and name it MefLogger.dll.config.
Using the configuration files in the DLL is not trivial. To achieve this, we must follow these steps:
- Add a New Application Configuration File and name it MefLogger.dll.config.
- Change the BuildAction property to Content.
- Change the Copy to Output property to Copy Always.
Figure 13
Modify the MefLogger.dll.config file as below:
="1.0"="utf-8"
<conFiguretion>
<appSettings>
<add key ="PluginPath" value="D:\Artigos\MefLoggerSolution\Plugins"/>
</appSettings>
</conFiguretion>
Now we can write the code needed to use MEF and manage the plug-ins.
Modify the PluginHandler
class to the code below:
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.ComponentModel.Composition.ReflectionModel;
using System.ComponentModel.Composition.AttributedModel;
using MefLogger.Interface;
using System.Reflection;
using System.ConFiguretion;
namespace MefLogger
{
internal class PluginHandler : IDisposable
{
[ImportMany(typeof(IMefLogger))]
public List<IMefLogger> AddinList
{ get; set; }
AggregateCatalog catalog = new AggregateCatalog();
public void InitializePlugins()
{
AddinList = new List<IMefLogger>();
catalog.Catalogs.Add(new DirectoryCatalog(GetConFiguretionPath(), "*.dll"));
CompositionContainer cc = new CompositionContainer(catalog);
cc.ComposeParts(this);
}
public void WriteLog(string message)
{
foreach (IMefLogger l in AddinList)
{
l.Log(message);
}
}
private string GetConFiguretionPath()
{
ConFiguretion PluginConfig =
ConFiguretionManager.OpenExeConFiguretion(
this.GetType().Assembly.Location);
AppSettingsSection PluginConfigAppSettings =
(AppSettingsSection)PluginConfig.GetSection("appSettings");
return PluginConfigAppSettings.Settings["PluginPath"].Value;
}
public void Dispose()
{
catalog.Dispose();
catalog = null;
AddinList.Clear();
AddinList = null;
}
}
}
Now, modify the Logger
class to the code below:
singusing System;
namespace MefLogger
{
public class Logger : IDisposable
{
static Logger singletonLogger;
PluginHandler h;
private Logger()
{
h = new PluginHandler();
h.InitializePlugins();
}
public static Logger GetLogger()
{
if (singletonLogger == null)
singletonLogger = new Logger();
return singletonLogger;
}
public void Log(string message)
{
h.WriteLog(message);
}
public void Dispose()
{
h = null;
singletonLogger = null;
}
}
}
Just to organize our solution, add a solution folder called Plugins. This folder is only logical, so if you look in Windows Explorer, you will not see this folder. We will use this folder to create the plug-in projects.
Figure 14 and Figure 15
Add into the Plugins folder a new Class Library project named TextLoggerPlugin. In this project, we'll create a plug-in to write the log into a text file. Add references to the MefLogger.Interface project and the System.ComponentModel.Composition.CodePlex.Dll MEF DLL. For plug-ins, this is the only required MEF DLL we need to reference.
usingusing System;
using System.ComponentModel.Composition;
using MefLogger.Interface;
using System.IO;
namespace TextLoggerPlugin
{
[Export(typeof(IMefLogger))]
public class TextLogger : IMefLogger
{
public void Log(string message)
{
StreamWriter sw = File.AppendText("addinlog.txt");
sw.WriteLine(message);
sw.Close();
}
}
}
Figure 16
Let's now create a new project in another plug-in, EventLoggerPlugin, which will record our log in the Windows Event Log.
usingusing System;
using System.ComponentModel.Composition;
using MefLogger.Interface;
using System.Diagnostics;
namespace EventLoggerPlugin
{
[Export(typeof(IMefLogger))]
public class EventLogger : IMefLogger
{
string sSource = "";
string sLog = "";
string sEvent = "";
public void Log(string message)
{
sSource = "EventLoggerAddin";
sLog = "Application";
sEvent = message;
try
{
if (!EventLog.SourceExists(sSource))
EventLog.CreateEventSource(sSource, sLog);
EventLog.WriteEntry(sSource, sEvent);
EventLog.WriteEntry(sSource, sEvent, EventLogEntryType.Information, 234);
}
catch
{
}
}
}
}
We now need to add a project to test our DLL. Let's add a new Console Application project named SampleApp.
Figure 17
To test our application, we'll just add a reference to the MefLogger project.
Figure 18
Make our SampeApp project the "StartUp Project"..
Figure 19
Modify the Program
class to:
usingusing System;
using MefLogger;
namespace SampleApp
{
class Program
{
static void Main(string[] args)
{
Logger _logger = Logger.GetLogger();
_logger.Log("Log Teste 1");
_logger.Log("Log Teste 2");
_logger.Log("Log Teste 3");
_logger.Dispose();
}
}
}
Finally, our solution will look like this:
Figure 20
To run the application, we must create the folder defined in the MefLogger.dll.config file and copy the DLLs created in the plug-in projects there..
You are done..
You can modify this simple example and tailor it to meet your needs..
Conclusion
We have seen that MEF is very simple to use and greatly simplifies the development of pluggable systems..
Points of Interest
Here is the MEF site on CodePlex: http://mef.codeplex.com/documentation.