Hazte Extensible con MEF

[UPDATE] Os podéis descargar el código de ejemplo

En la nueva versión del Framework (4.0) tendremos una librería que nos permitirá crear aplicaciones extensibles con cierta facilidad. Managed Extensibility Framework (MEF) permite que nuestras aplicaciones sean capaces de ofrecer nuevas funcionalidades sin necesidad de recompilar el código, tan solo añadiendo la DLL extensible como si de un Plug-in se tratase.

mef-blocks

¿Cómo funciona MEF?

MEF se basa en un Catálogo y en un CompositeContainer. El catálogo es el encargado de realizar el descubrimiento de las extensiones y el contenedor se encarga de descubrir y cargar las extensiones y satisfacer las dependencias.

Las partes extensibles deben de cumplir un contrato o interfaz y son cargadas en nuestra aplicación a través del catálogo. Veamos un ejemplo:

Definamos un contrato para el envío de una lista de ficheros a diferentes destinos (ftp, http, etc.)

   1: /// <summary>

   2: /// Contrato para el envío de una lista de ficheros.

   3: /// </summary>

   4: public interface ISend

   5: {

   6:     /// <summary>

   7:     /// Nombre del modo de envío

   8:     /// </summary>

   9:     string Name { get; }

  10:  

  11:     /// <summary>

  12:     /// Método que realiza el envío de la lista de ficheros

  13:     /// </summary>

  14:     /// <param name="files">Lista de ficheros a enviar</param>

  15:     /// <returns>Devuelve True si se ha realizado el envío correctamente.</returns>

  16:     bool AllFiles(List<string> files);

  17: }

Este contrato es el que tendrán que cumplir las extensiones que podemos añadir a nuestra aplicación.

El siguiente paso es realizar la carga de las extensiones para tenerlas disponibles para su uso.

   1: [Import]

   2: public ISend Send { get; set; }

   3:  

   4: private void Compose()

   5: {

   6:     var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

   7:     var container = new CompositionContainer(catalog);

   8:     container.ComposeParts(this);

   9: }

Instanciamos el Catálogo de nuestro ensamblado y realizamos la carga de las extensiones con el CompositionContainer. Cuanto se carga la extensión, quedará disponible para el uso en la propiedad definida por el atributo Import.

Otra posibilidad, a parte de la de crear nuestros propios catálogos, es utilizar el System.ComponentModel.Composition.Hosting.DirectoryCatalog. Esta catálogo nos permite realizar la carga de las extensiones desde un directorio de la siguiente forma:

   1: [ImportMany("send", typeof(ISend))]

   2: public IEnumerable<ISend> Sends { get; set; }

   3:  

   4: private void Compose()

   5: {

   6:     string path = Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty).Substring(0, path.LastIndexOf(@"/"));

   7:  

   8:     var catalog = new DirectoryCatalog(path + @"/Extensions/");

   9:     var contanier = new CompositionContainer(catalog);

  10:     contanier.ComposeParts(this);

  11: }

Como vemos en el código, hemos cambiado el Import por ImportMany para que sea capaz de cargar todos los extensores que se encuentren en el directorio que le vamos a especificar. Luego, en vez de utilizar el AssemblyCatalog, cargamos los extensores del directorio Extensions con el DirectoryCatalog. Esto realizará la carga del Enumerable de ISend y podremos seleccionar uno de ellos o todos para utilizar sus métodos.

Para ejecutar los métodos de un extensor sólo tendremos que obtenerlo de la propiedad definida con el Import (o del enumerado del ImportMany) y realizar la llamada al método que se ha definido en la interfaz.

   1: Send.AllFiles(ficheros);

   1: foreach (ISend item in Sends)

   2: {

   3:     item.AllFiles(ficheros);

   4: }

El contenedor es el encargado de instancias la extensión y permitir la ejecución del método requerido.

¿Cómo creamos extensiones?

Las extensiones no son más que ensamblados que implementan la interfaz del contrato, cumpliendo así con este y aplicando su propia lógica de programación.

   1: [Export("send", typeof(ISend))]

   2: public class Ftp : ISend

   3: {

   4:     string ftpAddress = "ftp.test.es";

   5:  

   6:     public string Name

   7:     {

   8:         get { return "FTP"; }

   9:     }

  10:  

  11:     public bool AllFiles(List<string> files)

  12:     {

  13:         FtpWebRequest ftpRequest = FtpWebRequest.Create(ftpAddress) as FtpWebRequest;

  14:         ftpRequest.Method = WebRequestMethods.Ftp.UploadFile;

  15:         ftpRequest.Credentials = System.Net.CredentialCache.DefaultCredentials;

  16:  

  17:         foreach (string file in files)

  18:         {

  19:             FileStream stream = File.OpenRead(file); 

  20:             byte[] buffer = new byte[stream.Length];

  21:             stream.Read(buffer, 0, buffer.Length); 

  22:             stream.Close();

  23:  

  24:             Stream reqStream = ftpRequest.GetRequestStream(); 

  25:             reqStream.Write(buffer, 0, buffer.Length); 

  26:             reqStream.Close();

  27:         }

  28:  

  29:         return false;

  30:     }

  31: }

Básicamente declaramos nuestra clase con el atributo Export y, opcionalmente, le especificamos de que tipo es. Implementamos los métodos que sean requeridos por la interfaz y listo. Compilamos este ensamblado, lo ponemos en el directorio que hemos indicado para la carga de los extensores y listo. Ya tenemos accesible esta extensión para el envío de ficheros por ftp. El resto de extensiones se desarrollan de la misma manera y se colocan en ensamblados por separado en la carpeta de las extensiones.

Fíjense que nuestra interfaz tiene un método llamado Name que podemos utilizar para realizar consultas LINQ y obtener el extensor que queremos, consultando por nombre.

   1: ISend extensor = Sends.Where(s => s.Name == "FTP").FirstOrDefault();

   2: extensor.AllFiles(ficheros);

Con MEF tenemos un Framework sencillo que nos permite añadirle mucha potencia a nuestras aplicaciones. Con pocas líneas de código hemos sido capaces de añadir diversas funcionalidades a nuestra aplicación sin tener que recompilarla y instalarla en los clientes. Por supuesto, esto es un pequeño ejemplo de la potencia de MEF, ahora nos quedaría poder personalizar el catálogo para que un usuario pueda elegir que extensores quiere, poder configurar estos extensores, etc.

 

Saludos a todos…

9 comentarios sobre “Hazte Extensible con MEF”

  1. No entiendo este post.
    Si el MEF sirve para añadir nueva funcionalidad a una aplicación sin recompilarla en base a un interfaz.
    Se me ocurren muchas formas y mejores de hacerlo y sin reinventar la rueda. La Inversión de control (o Inyección de Dependencia) está para esto. Es una solución usada por mucha gente y muy bien documentada. Incluso microsoft tiene su propia implementación con Unity.

  2. Hola, ¿qué es lo que no entiendes?

    MEF no es un IoC. MEF utiliza algunos principios similares a IoC para permitir la composición y la extensión de las aplicaciones, se centra en el descubrimiento de los extensores y en permitir a la aplicación extenderse por si sola (cómo lo hace Visual Studio 2010).
    IoC es más que un framework de composición. IoC se encarga de ciclos de vida, proxies, orientación a aspectos, agregación de eventos y otras muchas características que MEF no tiene.

    MEF existe para dar soporte a un modelo de Plugin.

  3. MAF (System.Addin) se centra en un problema muy específico, aislamiento y control de versiones. Se oculta de los dominios de aplicación para el usuario, administra cuando bloquearse y cuando poder descargar los addin incluso cuando no está en uso. MAF no proporciona ningún control de las dependencias como lo hace MEF.
    El objetivo de MEF es simplificar el proceso de extensión hasta el punto que sea tan sencillo como soltar un ensamblado en una carpeta.
    Aunque no son excluyentes, es posible utilizar la sencillez de MEF con el aislamiento de MAF.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *