Managed Extensibility Framework (MEF)

.NET Framework (current version)
 

En este tema se proporciona información general sobre la biblioteca Managed Extensibility Framework que se incluye en .NET Framework 4.

Managed Extensibility Framework o MEF es una biblioteca para crear aplicaciones ligeras y extensibles. Permite a los desarrolladores de aplicaciones detectar y utilizar extensiones sin requisitos de configuración. También permite a los desarrolladores de extensiones encapsular código con facilidad y evitar dependencias lógicas frágiles. MEF no solo permite reutilizar extensiones dentro de las aplicaciones sino también entre aplicaciones.

Imagine que es el arquitecto de una aplicación grande que debe admitir la extensibilidad. Su aplicación tiene que incluir un número potencialmente grande de componentes menores, y es responsable de crearlos y ejecutarlos.

El enfoque más sencillo es incluir los componentes como código fuente en su aplicación y llamarlos directamente desde el código. Esto tiene varias desventajas obvias. Mucho más importante es que no puede agregar nuevos componentes sin modificar el código fuente, una restricción que podría ser aceptable en una aplicación web, pero que no es viable en una aplicación cliente. Igualmente problemático es que podría no tener acceso al código fuente de los componentes, porque tal vez haya sido desarrollado por terceros y, por la misma razón, no puede permitirles acceso al suyo.

Un enfoque ligeramente más sofisticado sería proporcionar un punto de extensión o interfaz para permitir la desasociación entre la aplicación y sus componentes. Con este modelo, podría proporcionar una interfaz que un componente pueda implementar, y una API para que pueda interactuar con su aplicación. Así se resuelve el problema del acceso al código fuente, pero todavía presenta dificultades.

Dado que a la aplicación le falta la capacidad para detectar los componentes por sí misma, se debe indicar explícitamente qué componentes están disponibles y deben cargarse. Por lo general, esto se logra registrando explícitamente los componentes disponibles en un archivo de configuración. Esto significa que asegurar que los componentes sean correctos se convierte en una cuestión de mantenimiento, especialmente si usted es el usuario final y no el desarrollador que se espera que haga la actualización.

Además, los componentes no pueden comunicarse entre sí, excepto a través de los canales estrictamente definidos en el código de la propia aplicación. Si el arquitecto de la aplicación no ha previsto la necesidad de una comunicación determinada, normalmente es imposible.

Por último, los desarrolladores de componentes deben aceptar una dependencia fuerte en el ensamblado que contiene la interfaz que implementan. Esto dificulta que un componente se use en más de una aplicación y también puede crear problemas cuando se crea un marco de pruebas para los componentes.

En lugar de este registro explícito de componentes disponibles, MEF proporciona una manera de detectarlos implícitamente, a través de composición. Un componente MEF, denominado un parte, mediante declaración especifica tanto sus dependencias (conocido como importa) como qué capacidades (conocido como exporta) hace que estén disponible. Cuando se crea un elemento, el motor de composición de MEF cubre sus importaciones con lo que está disponible en otros elementos

Este enfoque resuelve los problemas comentados en la sección anterior. Dado que los elementos MEF especifican sus capacidades mediante declaración, son reconocibles en tiempo de ejecución, lo que significa que una aplicación puede utilizar elementos sin referencias incluidas en el código o archivos de configuración frágiles. MEF permite a las aplicaciones detectar y examinar elementos por sus metadatos, sin crear instancias ni cargar sus ensamblados. Por consiguiente, no hay necesidad de especificar meticulosamente qué extensiones se deben cargar y cuándo.

Además de las exportaciones proporcionadas, un elemento puede especificar las importaciones, que serán completadas por otros elementos. Esto hace que la comunicación entre los elementos sea posible además de sencilla y permite una buena factorización del código. Por ejemplo, los servicios comunes a muchos componentes se pueden tener en cuenta en un elemento independiente, para modificarlos o reemplazarlos con facilidad.

Dado que el modelo MEF no requiere dependencias lógicas en un ensamblado de aplicación determinado, permite reutilizar las extensiones entre una aplicación y otra. Esto también facilita el desarrollo de un agente de prueba, independiente de la aplicación, para probar los componentes de extensión.

Una aplicación extensible escrita utilizando MEF declara una importación que puede ser completada por componentes de extensión y también puede declarar exportaciones para exponer servicios de aplicación a extensiones. Cada componente de extensión declara una exportación y también puede declarar importaciones. De esta manera, los componentes de extensión son automáticamente extensibles.

MEF es una parte integral de .NET Framework 4 y está disponible dondequiera que se use .NET Framework. Puede utilizar MEF en aplicaciones cliente, que usen Windows Forms, WPF o cualquier otra tecnología, o en aplicaciones servidor que usen ASP.NET.

En versiones anteriores de .NET Framework se presentó Managed Add-in Framework (MAF), diseñado para permitir a las aplicaciones aislar y administrar extensiones. MAF se centra en un nivel ligeramente más alto que MEF y se concentra en el aislamiento de la extensión y la carga y descarga del ensamblado, mientras que MEF se centra en la detectabilidad, extensibilidad y portabilidad. Los dos marcos interoperan fácilmente y una aplicación única puede aprovecharse de ambos.

La manera más sencilla de ver qué puede hacer MEF es compilar una aplicación sencilla con él. En este ejemplo, compilará una calculadora muy sencilla denominada SimpleCalculator. El objetivo de SimpleCalculator es crear una aplicación de consola que acepte comandos aritméticos básicos, con el formato "5+3" ó "6-2", y devuelva respuestas correctas. Con MEF, podrá agregar nuevos operadores sin cambiar el código de la aplicación.

Para descargar el código completo de este ejemplo, consulte el ejemplo SimpleCalculator.

System_CAPS_ICON_note.jpg Nota

La finalidad de SimpleCalculator es mostrar los conceptos y la sintaxis de MEF, más que proporcionar un escenario realista para su uso. Muchas de las aplicaciones que más se beneficiarían de todo lo que MEF puede ofrecer son más complejas que SimpleCalculator. Para obtener ejemplos más exhaustivos, vea la Managed Extensibility Framework en Codeplex.

Para empezar, en Visual Studio 2010, cree un nuevo proyecto de aplicación de consola denominado SimpleCalculator. Agregue una referencia al ensamblado System.ComponentModel.Composition, donde reside MEF. Abra Module1.vb o Program.cs y agregue instrucciones Imports o using para System.ComponentModel.Composition y System.ComponentModel.Composition.Hosting. Estos dos espacios de nombres contienen tipos MEF que necesitará para desarrollar una aplicación extensible. En Visual Basic, agregue la palabra clave Public a la línea que declara el módulo Module1.

El núcleo del modelo de composición de MEF es el contenedor de composición, que contiene todos los elementos disponibles y realiza la composición. (Es decir, la concordancia de importaciones y exportaciones). Es el tipo más común de contenedor de composición CompositionContainer, y lo utilizará en SimpleCalculator.

En Visual Basic, en Module1.vb, agregue una clase pública denominada Program. A continuación, agregue la línea siguiente a la clase Program en Module1.vb o Program.cs:

private CompositionContainer _container;  

Para detectar los elementos disponibles, el contenedor de composición hace uso de un catálogo. Un catálogo es un objeto que hace que los elementos disponibles se detecten en algún origen. MEF proporciona catálogos para detectar las partes de un tipo, un ensamblado o un directorio suministrado. Los desarrolladores de aplicaciones pueden crear fácilmente nuevos catálogos para detectar partes de otros orígenes, como un servicio Web.

Agregue el siguiente constructor a la clase Program:

private Program()  
{  
    //An aggregate catalog that combines multiple catalogs  
    var catalog = new AggregateCatalog();  
    //Adds all the parts found in the same assembly as the Program class  
    catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));  
  
    //Create the CompositionContainer with the parts in the catalog  
    _container = new CompositionContainer(catalog);  
  
    //Fill the imports of this object  
    try  
    {  
        this._container.ComposeParts(this);  
    }  
    catch (CompositionException compositionException)  
    {  
        Console.WriteLine(compositionException.ToString());  
   }  
}  

La llamada a ComposeParts indica al contenedor de composición que cree un conjunto específico de elementos, en este caso la instancia actual de Program. En este punto, sin embargo, no sucederá nada, dado que Program no tiene importaciones que completar.

Primeramente, haga que Program importe una calculadora. Esto permite la separación de los problemas de la interfaz de usuario, como la entrada y salida de consola que irá a Program, de la lógica de la calculadora.

Agregue el código siguiente a la clase Program :

[Import(typeof(ICalculator))]  
public ICalculator calculator;  

Observe que la declaración de la calculator objeto no es rara, pero se decora con el ImportAttribute atributo. Este atributo declara que algo es una importación, es decir, el motor de la composición la completará cuando se cree el objeto.

Cada importación tiene un contrato, que determina qué exportaciones coincidirá con. El contrato puede ser una cadena especificada explícitamente o MEF puede generarla automáticamente a partir de un tipo determinado, en este caso la interfaz ICalculator. Cualquier exportación declarada con un contrato coincidente completará esta importación. Tenga en cuenta que mientras el tipo del objeto calculator sea de hecho ICalculator, esto no es necesario. El contrato es independiente del tipo del objeto de importación. (En este caso, podría omitir typeof(ICalculator). MEF adoptará automáticamente el contrato que se va a basar en el tipo de importación, a menos que lo especifique explícitamente).

Agregue esta sencilla interfaz al módulo o al espacio de nombres SimpleCalculator:

public interface ICalculator  
{  
    String Calculate(String input);  
}  

Ahora que ha definido ICalculator, necesita una clase que lo implemente. Agregue la siguiente clase al módulo o al espacio de nombres SimpleCalculator:

[Export(typeof(ICalculator))]  
class MySimpleCalculator : ICalculator  
{  
  
}  

Aquí está la exportación que coincidirá con la importación en Program. Para que la exportación coincida con la importación, la exportación debe tener el mismo contrato. La exportación bajo un contrato basado en typeof(MySimpleCalculator) generará una desigualdad y no se completará la importación; el contrato debe coincidir exactamente.

Puesto que el contenedor de composición se rellenará con todos los elementos disponibles de este ensamblado, el elemento MySimpleCalculator estará disponible. Cuando el constructor Program realiza la composición en el objeto Program, su importación se completará con un objeto MySimpleCalculator, que se creará con ese fin.

La capa de interfaz de usuario (Program) no necesita más información. Por tanto, puede rellenar el resto de la lógica de la interfaz de usuario en el método Main.

Agregue el código siguiente al método Main:

static void Main(string[] args)  
{  
    Program p = new Program(); //Composition is performed in the constructor  
    String s;  
    Console.WriteLine("Enter Command:");  
    while (true)  
    {  
        s = Console.ReadLine();  
        Console.WriteLine(p.calculator.Calculate(s));  
    }  
}  

Este código simplemente lee una línea de entrada y llama a la función Calculate de ICalculator en el resultado, que lo vuelve a escribir en la consola. Ese es todo el código que necesita en Program. El resto del trabajo se hará en los elementos.

Para que SimpleCalculator sea extensible, necesita importar una lista de operaciones. Un normal ImportAttribute atributo se rellena mediante una y solamente una ExportAttribute. Si hay más de uno disponible, el motor de la composición genera un error. Para crear una importación que pueda ser completada por cualquier número de exportaciones, puede utilizar el ImportManyAttribute atributo.

Agregue la siguiente propiedad de operaciones a la MySimpleCalculator clase:

[ImportMany]  
IEnumerable<Lazy<IOperation, IOperationData>> operations;  

Lazy<T, TMetadata> es un tipo proporcionado por MEF que contiene referencias indirectas a las exportaciones.</T, TMetadata> Aquí, además del propio objeto exportado, también obtendrá exportar metadatos, o la información que describe el objeto exportado. Cada Lazy<T, TMetadata> contiene un IOperation objeto que representa una operación real y un IOperationData objeto, que representa sus metadatos.</T, TMetadata>

Agregue estas sencillas interfaces al módulo o al espacio de nombres SimpleCalculator:

public interface IOperation  
{  
     int Operate(int left, int right);  
}  
  
public interface IOperationData  
{  
    Char Symbol { get; }  
}  

En este caso, los metadatos de cada operación son el símbolo que representa esa operación, como +, -, *, etc. Para que la operación de suma esté disponible, agregue la siguiente clase al módulo o al espacio de nombres SimpleCalculator:

[Export(typeof(IOperation))]  
[ExportMetadata("Symbol", '+')]  
class Add: IOperation  
{  
    public int Operate(int left, int right)  
    {  
        return left + right;  
    }  
}  

El ExportAttribute atributo funciones como antes. El ExportMetadataAttribute atributo adjunta metadatos, en forma de un par de nombre y valor a esa exportación. Mientras que la clase Add implementa IOperation, no se define explícitamente una clase que implementa IOperationData. En su lugar, MEF crea implícitamente una clase con propiedades basadas en los nombres de los metadatos proporcionados. (Esta es una de las maneras de tener acceso a los metadatos en MEF).

Composición en MEF es recursiva. Compuso el objeto Program explícitamente, que importó un ICalculator que resultó de ser del tipo MySimpleCalculator. A su vez, MySimpleCalculator importa una colección de objetos IOperation y se completará cuando se cree MySimpleCalculator, al mismo tiempo que las importaciones de Program. Si la clase Add declarara una importación más extensa, también tendría que completarse, y así sucesivamente. Toda importación que quede incompleta dará error en la composición. (Es posible, sin embargo, declarar las importaciones para que sean opcionales o asignarles valores predeterminados).

Con estos componentes bien establecidos, todo lo que queda es la propia lógica de la calculadora. Agregue el código siguiente a la clase MySimpleCalculator para implementar el método Calculate:

public String Calculate(String input)  
{  
    int left;  
    int right;  
    Char operation;  
    int fn = FindFirstNonDigit(input); //finds the operator  
    if (fn < 0) return "Could not parse command.";  
  
    try  
    {  
        //separate out the operands  
        left = int.Parse(input.Substring(0, fn));  
        right = int.Parse(input.Substring(fn + 1));  
    }  
    catch   
    {  
        return "Could not parse command.";  
    }  
  
    operation = input[fn];  
  
    foreach (Lazy<IOperation, IOperationData> i in operations)  
    {  
        if (i.Metadata.Symbol.Equals(operation)) return i.Value.Operate(left, right).ToString();  
    }  
    return "Operation Not Found!";  
}  

Los pasos iniciales analizan la cadena de entrada en los operandos izquierdo y derecho, y un carácter de operador. En el bucle de foreach, se examina cada miembro de la colección operations. Estos objetos son de tipo Lazy<T, TMetadata>, y puede tener acceso a sus valores de metadatos y el objeto exportado con el metadatos propiedad y el valorpropiedad respectivamente.</T, TMetadata> En este caso, si la propiedad Symbol del objeto IOperationData se detecta como una coincidencia, la calculadora llama al método Operate del objeto IOperation y devuelve el resultado.

Para completar la calculadora, también necesita un método auxiliar que devuelve la posición del primer carácter de una cadena que no sea un dígito. Agregue el método auxiliar siguiente a la clase MySimpleCalculator:

private int FindFirstNonDigit(String s)  
{  
    for (int i = 0; i < s.Length; i++)  
    {  
        if (!(Char.IsDigit(s[i]))) return i;  
    }  
    return -1;  
}  

A partir de este momento, podrá compilar e implementar el proyecto. En Visual Basic, asegúrese de que ha agregado la palabra clave Public a Module1. En la ventana de consola, escriba una operación de suma, como "5+3" y la calculadora devolverá los resultados. Cualquier otro operador dará lugar al mensaje "Operación no encontrada" .

Ahora que la calculadora funciona, agregar una nueva operación es fácil. Agregue la siguiente clase al módulo o al espacio de nombres SimpleCalculator:

[Export(typeof(IOperation))]  
[ExportMetadata("Symbol", '-')]  
class Subtract : IOperation  
{  
    public int Operate(int left, int right)  
    {  
        return left - right;  
    }  
}  

Compile y ejecute el proyecto. Escriba una operación de resta, como "5-3". La calculadora admite ahora la resta además de la suma.

Agregar clases al código fuente es bastante sencillo, pero MEF permite buscar elementos fuera del propio origen de una aplicación. Para demostrar esto, necesitará modificar SimpleCalculator para que busque un directorio, así como su propio ensamblado, para las partes, agregando un DirectoryCatalog.

Agregar un nuevo directorio denominado Extensions al proyecto SimpleCalculator. Asegúrese de agregarlo en el nivel del proyecto y no en el nivel de la solución. A continuación, agregue un nuevo proyecto de biblioteca de clases a la solución, denominada ExtendedOperations. El nuevo proyecto se compilará en un ensamblado independiente.

Abra el Diseñador de proyectos para el proyecto ExtendedOperations y haga clic en el compilar o crear ficha. Cambiar el ruta de acceso de salida de compilación o ruta de acceso de salida para que apunte al directorio Extensions en el directorio del proyecto SimpleCalculator (.. \SimpleCalculator\Extensions\).

En Module1.vb o Program.cs, agregue la línea siguiente al constructor Program:

catalog.Catalogs.Add(new DirectoryCatalog("C:\\SimpleCalculator\\SimpleCalculator\\Extensions"));  

Reemplace la ruta de acceso del ejemplo por la ruta de acceso del directorio Extensions. (Esta ruta de acceso absoluta es para fines de depuración solamente. En una aplicación de producción, usaría una ruta de acceso relativa). El DirectoryCatalog agregará los elementos encontrados en cualquier ensamblado del directorio Extensions al contenedor de composición.

En el proyecto ExtendedOperations, agregue referencias a SimpleCalculator y System.ComponentModel.Composition. En el archivo de clase ExtendedOperations, agregue una instrucción Imports o using para System.ComponentModel.Composition. En Visual Basic, agregue también una instrucción Imports para SimpleCalculator. Después, agregue la clase siguiente al archivo de clase ExtendedOperations:

[Export(typeof(SimpleCalculator.IOperation))]  
[ExportMetadata("Symbol", '%')]  
public class Mod : SimpleCalculator.IOperation  
{  
    public int Operate(int left, int right)  
    {  
        return left % right;  
    }  
}  

Tenga en cuenta que para el contrato coincida, el ExportAttribute atributo debe tener el mismo tipo que el ImportAttribute.

Compile y ejecute el proyecto. Pruebe el nuevo operador Mod (%).

En este tema se han tratado los conceptos básicos de MEF.

  • Elementos, catálogos y el contenedor de composición

    Los elementos y el contenedor de composición son los pilares fundamentales de una aplicación MEF. Un elemento es cualquier objeto que importa o exporta un valor, hasta sí mismo incluido. Un catálogo proporciona una colección de elementos de un origen determinado. El contenedor de composición utiliza los elementos proporcionados por un catálogo para realizar la composición, el enlace de las importaciones a las exportaciones.

  • Importaciones y exportaciones

    Las importaciones y las exportaciones son la manera en la que los componentes se comunican. Con una importación, el componente especifica la necesidad de un valor u objeto determinado, y con una exportación se especifica la disponibilidad de un valor. Cada importación coincide con una lista de exportaciones por medio de su contrato.

Para descargar el código completo de este ejemplo, consulte el ejemplo SimpleCalculator.

Para obtener más información y ejemplos de código, consulte Managed Extensibility Framework. Para obtener una lista de los tipos MEF, vea el System.ComponentModel.Composition espacio de nombres.

Mostrar: