Introducción a Managed Extensibility Framework (MEF)

Qué es MEF y que te aporta

MEF es una tecnología que permite desarrollar aplicaciones extensibles. La gran ventaja que nos ofrece MEF es que no necesitamos diseñar la aplicación conociendo qué extensiones formaran parte de ella ni la implementación interna de las propias extensiones.

Por extensible nos referimos a que nuestra aplicación puede estar en producción y de forma dinámica añadir, reemplazar, eliminar las extensiones que tenemos sin necesidad ni de recompilar ni reiniciar la aplicación.

MEF viene con .NET Framework 4 en la librería: System.ComponentModel.Composition.

MEF vs IoC Container

Aunque pueda parecer que MEF e IoC ofrecen la misma funcionalidad la diferencia consiste en que tratan de resolver problemas distintos.

El objetivo básico de un framework de IoC es el de ofrecer desacoplamiento entre componentes y nos resuelve dependencias que conocemos. Esto nos permite que nuestra aplicación sea modular y testeable.

En cambio, en MEF, el principal objetivo es la extensibilidad: el desacoplamiento es una consecuencia. Sabemos que las extensiones que se vayan a implementar cumplirán un contrato pero no sabremos si sólo habrá una extensión, muchas o ninguna.

Ejemplo: Extensión IHelloWorld

Vamos a ver un ejemplo de cómo consumir nuestras extensiones con MEF. El ejemplo es muy sencillo y únicamente tenemos una aplicación de consola que consumirá extensiones que nos devuelven el mensaje Hola Mundo en el idioma definido en la extensión.

Primero declaramos en el ensamblado Serrate.MEFDemo.HelloWorld.Interface el contrato que deben cumplir:

1
2
3
4
5
6
7
8
9
10
namespace Serrate.MEFDemo.HelloWord.Interface
{
/// <summary>
/// Contrato para extensiones
/// </summary>
public interface IHelloWorld
{
string GetMessage();
}
}

Posteriormente en nuestra aplicación de consola Serrate.MEFDemo.HelloWorldApp tenemos la clase Consumer encargada de consumir, como no, las extensiones de terceros.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
namespace Serrate.MEFDemo.HelloWorldApp
{
/// <summary>
/// Clase encargada de consumir nuestras extensiones
/// </summary>
public class Consumer
{
/// <summary>
/// Esta lista importará las extensiones que cumplan
/// nuestros requisitos
/// </summary>
[ImportMany]
public List<IHelloWorld> HelloWorldPlugins { get; set; }
/// <summary>
/// Lectura de nuestras extensiones
/// </summary>
public void Read()
{
// Configuramos nuestro contenedor
this.Setup();
// Recorremos la lista de extensiones
foreach (var plugin in HelloWorldPlugins)
{
Console.WriteLine(plugin.GetMessage());
}
Console.ReadLine();
}
private void Setup()
{
// a partir del directorio de la aplicación buscamos
// en una ruta relativa nuestro directorio de extensiones
string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
DirectoryInfo extensionsDirectory =
new DirectoryInfo(appDirectory + "..\\..\\..\\extensions");
// creamos el catálogo a partir del directorio
var catalog = new DirectoryCatalog(extensionsDirectory.FullName);
// componemos las extensiones para poderlas usar
var compositionContainer = new CompositionContainer(catalog);
compositionContainer.ComposeParts(this);
}
}
}

Como podemos tener varias extensiones cumpliendo el mismo contrato, en nuestra aplicación declaramos una lista de este contrato, y la decoramos con el atributo [ImportMany]:

1
2
3
4
5
6
/// <summary>
/// Esta lista importará las extensiones que cumplan
/// nuestros requisitos
/// </summary>
[ImportMany]
public List<IHelloWorld> HelloWorldPlugins { get; set; }

Seguidamente podemos realizar la lectura de nuestras extensiones:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// <summary>
/// Lectura de nuestras extensiones
/// </summary>
public void Read()
{
// Configuramos nuestro contenedor
this.Setup();
// Recorremos la lista de extensiones
foreach (var plugin in HelloWorldPlugins)
{
Console.WriteLine(plugin.GetMessage());
}
Console.ReadLine();
}

En el método Setup indicamos el directorio de nuestras extensiones y realizamos la carga de éstas:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void Setup()
{
// a partir del directorio de la aplicación buscamos
// en una ruta relativa nuestro directorio de extensiones
string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
DirectoryInfo extensionsDirectory =
new DirectoryInfo(appDirectory + "..\\..\\..\\extensions");
// creamos el catálogo a partir del directorio
var catalog = new DirectoryCatalog(extensionsDirectory.FullName);
// componemos las extensiones para poderlas usar
var compositionContainer = new CompositionContainer(catalog);
compositionContainer.ComposeParts(this);
}

Finalmente, ya sólo nos hace falta crear nuestras extensiones que realizarán el trabajo duro. Indicaremos que las extensiones son exportables mediante el atributo [Export]

Crearemos el ensamblado Serrate.MEFDemo.Plugin.ES y allí tendremos nuestra clase que implementará el contrato:

1
2
3
4
5
6
7
8
9
10
11
namespace Serrate.MEFDemo.Plugin.ES
{
[Export(typeof(IHelloWorld))]
public class HolaMundo : IHelloWorld
{
public string GetMessage()
{
return "Hola Mundo";
}
}
}

En otro ensamblado Serrate.MEFDemo.Plugin.CA tendremos definida otra extensión:

1
2
3
4
5
6
7
8
9
10
11
namespace Serrate.MEFDemo.Plugin.CA
{
[Export(typeof(IHelloWorld))]
public class HolaMon : IHelloWorld
{
public string GetMessage()
{
return "Hola Món";
}
}
}

Si colocamos las librerías de nuestras extensiones en la carpeta que espera nuestra aplicación, al ejecutarla ya nos saldrá por pantalla el resultado esperado:

> Hola Mundo
> Hola Món

En esta introducción hemos visto como utilizar MEF para que nuestra aplicación pueda ser extendida por nosotros mismos o por terceros de forma realmente sencilla.