[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.
¿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…