Click here to Skip to main content
Click here to Skip to main content

MEF Logger – Creating plug-ins using MEF

By , 5 Apr 2011
 

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:

  1. Define extension points
  2. 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.

  3. Define an MEF Contract for each extension point
  4. 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.

  5. Define a class to manage the extension point
  6. To inform MEF how to manage our plug-ins, we use the Import attribute. In our example, the PluginHandler class will manage the extensions.

  7. Create the plug-in
  8. 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.

  9. Define a Catalog
  10. 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.

Figura1.JPG

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.

Figura2.JPG

Figure 2

Figura3.JPG

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..

Figura4.JPG

Figure 4

Figura5.JPG

Figure 5

By renaming the class, we will receive the following message:

Figura6.JPG

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.

Figura7.JPG

Figure 7

Figura8.JPG

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.

Figura9.JPG

Figure 9

Figura10.JPG

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..

Figura11.JPG

Figure 11

Add a reference to the MefLogger.Interface project..

Figura12.JPG

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:

  1. Add a New Application Configuration File and name it MefLogger.dll.config.
  2. Change the BuildAction property to Content.
  3. Change the Copy to Output property to Copy Always.

Figura13.JPG

Figure 13

Modify the MefLogger.dll.config file as below:

<?xml version="1.0" encoding="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;
// Referências ao MEF
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
    {
        // O atributo ImportMany permite que importemos várias classes que 
        // implementam a mesma interface
        [ImportMany(typeof(IMefLogger))]
        // AddinList armazenará todos os plugins carregados
        public List<IMefLogger> AddinList
        { get; set; }

        // AggregateCatalog armazena os catálogos do MEF 
        AggregateCatalog catalog = new AggregateCatalog();

        public void InitializePlugins()
        {
            // cria a instância da nossa lista de plugins
            AddinList = new List<IMefLogger>();
            // cria um DirectoryCatalog e adiciona ao nosso AggregateCatalog
            catalog.Catalogs.Add(new DirectoryCatalog(GetConFiguretionPath(), "*.dll"));
            // cria o CompositionContainer
            CompositionContainer cc = new CompositionContainer(catalog);
            // Efetua a mágica.... É neste momento que o MEF 
            // carrega os plugins e os adiciona ao nosso AddinList
            cc.ComposeParts(this);
        }

        public void WriteLog(string message)
        {
            // para cada plugin carregado em AddinList
            foreach (IMefLogger l in AddinList)
            {
                // chama o método Log de cada plugin
                l.Log(message);
            }
        }

        private string GetConFiguretionPath()
        {
            /* Utilizar arquivos de conFigureção em DLL não é trivial. 
             * Para conseguir isso devemos seguire os seguintes passos:
             * - Adicionar o arquivo App.Config
             * - Mudar a propriedade BuildAction para Content
             * - Mudar a propriedade Copy to Output para Copy Always
             * - Alterar o nome de App.Config para MefLogger.dll.config
             */
            // Abre o arquivo de conFigureção a partir da pasta onde a DLL está
            ConFiguretion PluginConfig = 
            ConFiguretionManager.OpenExeConFiguretion(
                                    this.GetType().Assembly.Location);
            // Recupera a seção appSettings
            AppSettingsSection PluginConfigAppSettings = 
            (AppSettingsSection)PluginConfig.GetSection("appSettings");
            // retorna o valor da chave PluginPath
            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
    {
        // Vamos utilizar o padrão Singleton, 
        // para evitar carregar os plugins várias vezes.
        static Logger singletonLogger;
        
        PluginHandler h;
        private Logger()
        {
            h = new PluginHandler();
            h.InitializePlugins();

        }

        // Implementação do padrão Singleton
        public static Logger GetLogger()
        {
            // se a instância da classe Logger não foi criada, então cria.
            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.

Figura14.JPG

Figura15.JPG

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)
        {
            // o arquivo será criado na pasta onde a aplicação SampleApp estará rodando.
            StreamWriter sw = File.AppendText("addinlog.txt");
            sw.WriteLine(message);
            sw.Close();
        }
    }
}

Figura16.JPG

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
            {
                // Ocorrerá erro caso não esteja rodando o programa com 
                // permissões de administrador
            }
        }
    }
}

We now need to add a project to test our DLL. Let's add a new Console Application project named SampleApp.

Figura17.JPG

Figure 17

To test our application, we'll just add a reference to the MefLogger project.

Figura18.JPG

Figure 18

Make our SampeApp project the "StartUp Project"..

Figura19.JPG

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:

Figura20.JPG

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

fmsalmeida
Other Zurich
Brazil Brazil
Member
I'm a system analyst (senior) and I live in Belo Horizonte, Minas Gerais, Brazil. I've been working with software development since 1996.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionStraightforwardmemberMatthew Cuba15 Nov '11 - 6:51 
Folks can disagree over what makes an article, but I followed along step by step quite easily enough. I agree with the one comment that this is a bit light on commentary between the code snippets and screen snaps but but otherwise, I think it is useful and provides a starting point through a very simple example that doesn't bog down the user with things that aren't germane to MEF.
 
Good job.
“You can't teach people to be lazy - either they have it, or they don't.”
-Dagwood Bumstead

GeneralI like it (doesn't matter if it's an article or not)membermeraydin23 Sep '11 - 7:16 
For those who want to "read" about MEF, there are lots of "articles" at MSDN. Keep going this way, I liked the way you present it.
 
However, it would be better if the comments were in English Wink | ;)
GeneralNicememberlindycat6 Aug '11 - 1:41 
I am just beginning with MEF, I liked the article and the code also, both are helpful. One thing I find disturbing are comments written in spanish(I suppose), I'd rather have them in English or not at all.
GeneralRe: Nicememberfmsalmeida8 Aug '11 - 3:35 
thanks, i'm glad you like it
 
The comments are written in protuguese. Sorry.
I'm writing another MEF article and I will write the comments in english. I promisse.
GeneralMy vote of 5memberIAbstract10 Jul '11 - 3:45 
Excellent piece of work. There is a lot to cover in MEF and I think you did a great job!!
GeneralRe: My vote of 5memberfmsalmeida18 Jul '11 - 4:24 
thanks, i'm glad you like it
QuestionMEF Loggermemberolinzer25 May '11 - 10:45 
Hi! Your article was very helpful, you have my vote of 5!
 
However, I've got a question and maybe you can helpme. The code of the example works, but what if I need to reload the plug in. When I do that, I receive a message that the file (dll) is locked.
 
After research, I found that a way to solve this is with shadow copy, and load the Assembly in a new AppDomain. But, I have no idea how to mix shadow copy and mix it with MEF.
 
I'd be very thankful if you can post a little example of this or give me some advice, to my user name at gmail.
 
Best regards!
AnswerRe: MEF Loggermemberfmsalmeida26 May '11 - 2:57 
I'm glad you enjoyed.
 
please see the links below. They will explain how to do what you want using Recomposition.
 
http://blogs.microsoft.co.il/blogs/bnaya/archive/2010/01/15/mef-for-beginner-recomposition-policy-part-7.aspx[^]
 
http://mef.codeplex.com/wikipage?title=Recomposition[^]
 
http://blogs.infosupport.com/blogs/alexb/archive/2010/01/21/configuring-mef-with-linq-to-xml-part-2-making-mef-recomposition-on-the-fly-when-the-file-is-edited.aspx[^]
 
http://stackoverflow.com/questions/3785801/how-do-i-get-mef-to-recompose-when-i-change-a-part[^]
QuestionWhy logging as an example?memberKinStephen11 Apr '11 - 13:11 
Why did you provide an example of logging through the MEF framework? log4net is already a well established logging framework. I'd really like to see one that better shows off MEF.
 
I've briefly read up on MEF... sounds like an IoC... I'd have to read more to confirm. If it is an IoC, then the same question comes up again. Why MEF over Windsor Castle (a well established IoC framework).
AnswerRe: Why logging as an example?memberfmsalmeida12 Apr '11 - 1:29 
1) The goal of this project isn't build another logger solution, but I think that a logger is a good example to show MEF the simplest way, instead of "Hello Words" or other kinds of examples. I wanted to write just a few lines of code to keep it simple and to show the power of MEF.
 
2) MEF isn't a IoC container, but he uses an IoC container.
 
Please read the articles below. They will explain these question:
 
http://msdn.microsoft.com/en-us/magazine/gg650670.aspx[^]
 
http://msmvps.com/blogs/peterritchie/archive/2010/02/24/mef-is-not-an-ioc-container-but-mef-uses-ioc.aspx[^]
 
http://s3.amazonaws.com/hanselminutes/hanselminutes_0148.pdf[^]

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 5 Apr 2011
Article Copyright 2011 by fmsalmeida
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid