Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

MEF Logger – Creating plug-ins using MEF

4.92/5 (12 votes)
5 Apr 2011CPOL6 min read 60.8K   1.9K  
In this article, I will show how to build a pluggable component using MEF.

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

C#
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
<?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:

C#
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:

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

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

C#
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:

C#
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)