Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este artículo se proporciona información general sobre managed Extensibility Framework que se introdujo en .NET Framework 4.
¿Qué es MEF?
Managed Extensibility Framework (MEF) es una biblioteca para crear aplicaciones ligeras y extensibles. Permite a los desarrolladores de aplicaciones detectar y usar extensiones sin requisitos de configuración. También permite a los desarrolladores de extensiones encapsular código fácilmente y evitar dependencias difíciles frágiles. MEF no solo permite reutilizar las extensiones dentro de las aplicaciones, sino también en todas las aplicaciones.
El problema de extensibilidad
Imagina que eres el arquitecto de una aplicación grande que debe proporcionar soporte para la extensibilidad. La aplicación tiene que incluir un gran número de componentes más pequeños y es responsable de crearlos y ejecutarlos.
El enfoque más sencillo para el problema es incluir los componentes como código fuente en la aplicación y llamarlos directamente desde el código. Esto tiene una serie de desventajas obvias. Lo más importante es que no se pueden agregar nuevos componentes sin modificar el código fuente, una restricción que podría ser aceptable en, por ejemplo, una aplicación web, pero no se puede trabajar en una aplicación cliente. Igualmente problemático, es posible que no tenga acceso al código fuente de los componentes, ya que pueden ser desarrollados por terceros y, por el mismo motivo, no puede permitir que accedan a los suyos.
Un enfoque ligeramente más sofisticado sería proporcionar un punto de extensión o una interfaz, para permitir la desacoplamiento entre la aplicación y sus componentes. En este modelo, puede proporcionar una interfaz que un componente pueda implementar y una API para permitir que interactúe con la aplicación. Esto resuelve el problema de requerir acceso al código fuente, pero todavía tiene sus propias dificultades.
Dado que la aplicación carece de capacidad para detectar componentes por sí solos, debe indicarse explícitamente qué componentes están disponibles y deben cargarse. Normalmente, esto se logra registrando explícitamente los componentes disponibles en un archivo de configuración. Esto significa que asegurarse de que los componentes son correctos se convierte en un problema de mantenimiento, especialmente si es el usuario final y no el desarrollador que se espera que realice la actualización.
Además, los componentes no pueden comunicarse entre sí, excepto a través de los canales definidos rígidamente de la propia aplicación. Si el arquitecto de aplicaciones no ha previsto la necesidad de una comunicación determinada, suele ser imposible.
Por último, los desarrolladores de componentes deben aceptar una dependencia difícil en qué ensamblado contiene la interfaz que implementan. Esto dificulta el uso de un componente en más de una aplicación y también puede crear problemas al crear un marco de pruebas para componentes.
Qué proporciona MEF
En lugar de este registro explícito de los componentes disponibles, MEF proporciona una manera de detectarlos implícitamente a través de la composición. Un componente MEF, denominado parte, especifica declarativamente sus dependencias (conocidas como importaciones) y qué funcionalidades (conocidas como exportaciones) pone a disposición. 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 descritos en la sección anterior. Dado que las partes MEF especifican mediante declaración sus funcionalidades, se pueden detectar en tiempo de ejecución, lo que significa que una aplicación puede usar elementos sin referencias codificadas de forma rígida o archivos de configuración frágiles. MEF permite a las aplicaciones detectar y examinar elementos por sus metadatos, sin crear instancias de ellos ni incluso cargar sus ensamblados. Como resultado, no es necesario especificar cuidadosamente cuándo y cómo se deben cargar las extensiones.
Además de sus exportaciones proporcionadas, una parte puede especificar sus importaciones, que serán rellenadas por otras partes. Esto hace que la comunicación entre partes no solo sea posible, sino fácil, y permite una buena factorización del código. Por ejemplo, los servicios comunes a muchos componentes se pueden factorizar en una parte independiente y modificar o reemplazar fácilmente.
Dado que el modelo MEF no requiere dependencias difíciles en un ensamblado de aplicación determinado, permite reutilizar las extensiones de la aplicación a la aplicación. Esto también facilita el desarrollo de un arnés de prueba, independientemente de la aplicación, para probar los componentes de extensión.
Una aplicación extensible escrita mediante MEF declara una importación que se puede rellenar mediante 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 este modo, los propios componentes de extensión son automáticamente extensibles.
Dónde está disponible MEF
MEF está disponible en .NET Framework 4 y versiones posteriores, y en .NET 5 y versiones posteriores. Puede usar MEF en las aplicaciones cliente, tanto si usan Windows Forms, WPF o cualquier otra tecnología, o en aplicaciones de servidor que usan ASP.NET.
MEF y MAF
Las versiones anteriores de .NET Framework introdujeron managed Add-in Framework (MAF), diseñadas para permitir que las aplicaciones aíslen y administren extensiones. El enfoque de MAF es de nivel ligeramente superior a MEF, centrándose en el aislamiento de extensiones y la carga y descarga de ensamblados, mientras que el enfoque de MEF se centra en la detectabilidad, la extensibilidad y la portabilidad. Los dos marcos interoperan sin problemas y una sola aplicación puede aprovechar ambas ventajas.
SimpleCalculator: una aplicación de ejemplo
La manera más sencilla de ver lo que MEF puede hacer es crear una aplicación MEF sencilla. En este ejemplo, creará 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" o "6-2" y devuelva las 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 (Visual Basic).
Nota:
El propósito de SimpleCalculator es demostrar los conceptos y la sintaxis de MEF, en lugar de proporcionar necesariamente un escenario realista para su uso. Muchas de las aplicaciones que se beneficiarían más de la potencia de MEF son más complejas que SimpleCalculator. Para obtener ejemplos más amplios, consulte Managed Extensibility Framework en GitHub.
Para empezar, en Visual Studio, cree un nuevo proyecto aplicación de consola y asígnelo el nombre
SimpleCalculator.Agregue una referencia al ensamblado , donde se encuentra MEF.
Abra Module1.vb o Program.cs y agregue directivas o para y . Estos dos espacios de nombres contienen tipos MEF que necesitará para desarrollar una aplicación extensible.
Si usa Visual Basic, agregue la palabra clave
a la línea que declara el módulo />
Contenedor de composición y catálogos
El núcleo del modelo de composición MEF es el contenedor de composición, que contiene todas las partes disponibles y realiza la composición. La composición es la correspondencia entre las importaciones y las exportaciones. El tipo de contenedor de composición más común es y lo usará para SimpleCalculator.
Si usa Visual Basic, agregue una clase pública denominada Program en Module1.vb.
Agregue la siguiente línea a la clase en Module1.vb o Program.cs:
Dim _container As CompositionContainer
private CompositionContainer _container;
Con el fin de descubrir las partes disponibles, los contenedores de composición hacen uso de un catálogo. Un catálogo es un objeto que pone a disposición los elementos encontrados desde algún origen. MEF proporciona catálogos para detectar elementos de un tipo proporcionado, un ensamblado o un directorio. Los desarrolladores de aplicaciones pueden crear fácilmente catálogos para detectar elementos de otros orígenes, como un servicio web.
Agregue el siguiente constructor a la clase :
Public Sub New()
' An aggregate catalog that combines multiple catalogs.
Dim catalog = New AggregateCatalog()
' Adds all the parts found in the same assembly as the Program class.
catalog.Catalogs.Add(New AssemblyCatalog(GetType(Program).Assembly))
' Create the CompositionContainer with the parts in the catalog.
_container = New CompositionContainer(catalog)
' Fill the imports of this object.
Try
_container.ComposeParts(Me)
Catch ex As CompositionException
Console.WriteLine(ex.ToString)
End Try
End Sub
private Program()
{
try
{
// 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);
_container.ComposeParts(this);
}
catch (CompositionException compositionException)
{
Console.WriteLine(compositionException.ToString());
}
}
La llamada a le indica al contenedor de composición que componga un conjunto específico de partes, en este caso, la instancia actual de . Sin embargo, en este momento no ocurrirá nada, ya que no tiene elementos que importar.
Importaciones y exportaciones con atributos
En primer lugar, debes importar una calculadora. Esto permite separar los problemas de la interfaz de usuario, como la entrada y salida de la consola que entrarán en , desde la lógica de la calculadora.
Agregue el siguiente código a la clase :
<Import(GetType(ICalculator))>
Public Property calculator As ICalculator
[Import(typeof(ICalculator))]
public ICalculator calculator;
Observe que la declaración del objeto no es inusual, pero que está decorada con el atributo . Este atributo declara algo que va a ser una importación; es decir, será rellenado por el motor de composición cuando se redacte el objeto.
Cada importación tiene un contrato, que determina con qué exportaciones coincidirá. El contrato puede ser una cadena especificada explícitamente, o puede ser generado automáticamente por MEF a partir de un tipo determinado, en este caso la interfaz . Cualquier exportación declarada con un contrato coincidente cumplirá esta importación. Tenga en cuenta que, aunque el tipo del objeto es de hecho , esto no es necesario. El contrato es independiente del tipo del objeto de importación. (En este caso, podría dejar fuera el . MEF asume automáticamente que el contrato se basará en el tipo de importación a menos que lo especifique explícitamente).
Agregue esta interfaz muy sencilla al módulo o al espacio de nombres .
Public Interface ICalculator
Function Calculate(input As String) As String
End Interface
public interface ICalculator
{
string Calculate(string input);
}
Ahora que ha definido , necesita una clase que la implemente. Agregue la siguiente clase al módulo o al espacio de nombres :
<Export(GetType(ICalculator))>
Public Class MySimpleCalculator
Implements ICalculator
End Class
[Export(typeof(ICalculator))]
class MySimpleCalculator : ICalculator
{
}
Esta es la exportación que coincidirá con la importación en . Para que la exportación coincida con la importación, la exportación debe tener el mismo contrato. La exportación según un contrato basado en produciría una discordancia y la importación no se completaría; el contrato debe coincidir exactamente.
Puesto que el contenedor de composición se rellenará con todas las partes disponibles en este ensamblado, la parte estará disponible. Cuando el constructor para realiza la composición en el objeto , su importación se llenará con un objeto que se creará para ese propósito.
La capa de interfaz de usuario () no necesita saber nada más. Por lo tanto, puede rellenar el resto de la lógica de la interfaz de usuario en el método .
Agregue el código siguiente al método :
Sub Main()
' Composition is performed in the constructor.
Dim p As New Program()
Dim s As String
Console.WriteLine("Enter Command:")
While (True)
s = Console.ReadLine()
Console.WriteLine(p.calculator.Calculate(s))
End While
End Sub
static void Main(string[] args)
{
// Composition is performed in the constructor.
var p = new Program();
Console.WriteLine("Enter Command:");
while (true)
{
string 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 de en el resultado, que lo vuelve a escribir en la consola. Eso es todo el código que necesita en . El resto del trabajo se hará en los elementos.
Atributos Imports e ImportMany
Para que SimpleCalculator sea extensible, debe importar una lista de operaciones. Un atributo ordinario lo completa un atributo y solo uno. Si hay más de uno disponible, el motor de composición genera un error. Para crear una importación que se puede rellenar con cualquier número de exportaciones, puede usar el atributo .
Agregue la siguiente propiedad de operaciones a la clase :
<ImportMany()>
Public Property operations As IEnumerable(Of Lazy(Of IOperation, IOperationData))
[ImportMany]
IEnumerable<Lazy<IOperation, IOperationData>> operations;
es un tipo proporcionado por MEF para contener referencias indirectas a las exportaciones. Aquí, además del propio objeto exportado, también se obtienen metadatos de exportación o información que describe el objeto exportado. Cada contiene un objeto , que representa una operación real y un objeto , que representa sus metadatos.
Agregue las siguientes interfaces simples al módulo o al espacio de nombres :
Public Interface IOperation
Function Operate(left As Integer, right As Integer) As Integer
End Interface
Public Interface IOperationData
ReadOnly Property Symbol As Char
End Interface
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 :
<Export(GetType(IOperation))>
<ExportMetadata("Symbol", "+"c)>
Public Class Add
Implements IOperation
Public Function Operate(left As Integer, right As Integer) As Integer Implements IOperation.Operate
Return left + right
End Function
End Class
[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '+')]
class Add: IOperation
{
public int Operate(int left, int right)
{
return left + right;
}
}
El atributo funciona como lo hizo antes. El atributo adjunta metadatos, en forma de par nombre-valor, a esa exportación. Aunque la clase implementa , una clase que implementa no se define explícitamente. En su lugar, MEF crea implícitamente una clase con propiedades basadas en los nombres de los metadatos proporcionados. (Esta es una de las varias maneras de acceder a los metadatos en MEF).
La composición en MEF es recursiva. Compuso el objeto explícitamente, que importó un que resultó ser del tipo . , a su vez, importa una colección de objetos y esa importación se rellenará cuando se cree, al mismo tiempo que las importaciones de . Si la clase declaraba una importación adicional, también tendría que rellenarse, etc. Cualquier importación que no se haya rellenado genera un error de composición. (Sin embargo, es posible declarar las importaciones como opcionales o asignarles valores predeterminados).
Lógica de calculadora
Con estas partes en su lugar, todo lo que queda es la propia lógica de la calculadora. Agregue el código siguiente en la clase para implementar el método :
Public Function Calculate(input As String) As String Implements ICalculator.Calculate
Dim left, right As Integer
Dim operation As Char
' Finds the operator.
Dim fn = FindFirstNonDigit(input)
If fn < 0 Then
Return "Could not parse command."
End If
operation = input(fn)
Try
' Separate out the operands.
left = Integer.Parse(input.Substring(0, fn))
right = Integer.Parse(input.Substring(fn + 1))
Catch ex As Exception
Return "Could not parse command."
End Try
For Each i As Lazy(Of IOperation, IOperationData) In operations
If i.Metadata.symbol = operation Then
Return i.Value.Operate(left, right).ToString()
End If
Next
Return "Operation not found!"
End Function
public String Calculate(string input)
{
int left;
int right;
char operation;
// Finds the operator.
int fn = FindFirstNonDigit(input);
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 separan la cadena de entrada en operandos izquierdo y derecho y un carácter de operador. En el bucle, se examinan todos los miembros de la colección. Estos objetos son de tipo y se puede tener acceso a sus valores de metadatos y al objeto exportado con la propiedad y la propiedad respectivamente. En este caso, si se detecta que la propiedad del objeto es una coincidencia, la calculadora llama al método del objeto y devuelve el resultado.
Para completar la calculadora, también necesita un método auxiliar que devuelva la posición del primer carácter que no es de dígito en una cadena. Agregue el siguiente método auxiliar a la clase :
Private Function FindFirstNonDigit(s As String) As Integer
For i = 0 To s.Length - 1
If Not Char.IsDigit(s(i)) Then Return i
Next
Return -1
End Function
private int FindFirstNonDigit(string s)
{
for (int i = 0; i < s.Length; i++)
{
if (!char.IsDigit(s[i])) return i;
}
return -1;
}
Ahora debería poder compilar y ejecutar el proyecto. En Visual Basic, asegúrese de agregar la palabra clave Public a Module1. En la ventana de la consola, escriba una operación de adición, como "5+3" y la calculadora devuelve los resultados. Cualquier otro operador da como resultado el mensaje "Operation Not Found!" (Operación no encontrada).
Extender SimpleCalculator mediante una nueva clase
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 :
<Export(GetType(IOperation))>
<ExportMetadata("Symbol", "-"c)>
Public Class Subtract
Implements IOperation
Public Function Operate(left As Integer, right As Integer) As Integer Implements IOperation.Operate
Return left - right
End Function
End Class
[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 ahora admite la resta, así como la adición.
Extender SimpleCalculator mediante un nuevo ensamblado
Agregar clases al código fuente de la aplicación es lo suficientemente simple, pero MEF proporciona la capacidad de buscar componentes fuera del propio origen de una aplicación. Para demostrar esto, deberá modificar SimpleCalculator para buscar partes en un directorio, así como en su propio ensamblado, agregando un .
Agregue un nuevo directorio denominado al proyecto SimpleCalculator. Asegúrese de agregarlo en el nivel de proyecto y no en el nivel de solución. A continuación, agregue un nuevo proyecto de biblioteca de clases a la solución, denominado . El nuevo proyecto se compilará en un ensamblado independiente.
Abra el Diseñador de propiedades de proyecto para el proyecto ExtendedOperations y haga clic en la pestaña Compilar o Compilar . Cambie la ruta de acceso de salida de compilación o ruta de acceso de salida para que apunte al directorio Extensiones del directorio del proyecto SimpleCalculator (.. \SimpleCalculator\Extensions\).
En Module1.vb o Program.cs, agregue la siguiente línea al constructor:
catalog.Catalogs.Add(
New DirectoryCatalog(
"C:\SimpleCalculator\SimpleCalculator\Extensions"))
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 solo para fines de depuración. En una aplicación de producción, usaría una ruta de acceso relativa). Ahora, el agregará cualquier parte encontrada en cualquier ensamblado del directorio Extensions al contenedor de composición.
En el proyecto, agregue referencias a y . En el archivo de clase , agregue una directiva o para . En Visual Basic, agregue también una instrucción Imports para SimpleCalculator. A continuación, agregue la siguiente clase al archivo de clase:
<Export(GetType(SimpleCalculator.IOperation))>
<ExportMetadata("Symbol", "%"c)>
Public Class Modulo
Implements IOperation
Public Function Operate(left As Integer, right As Integer) As Integer Implements IOperation.Operate
Return left Mod right
End Function
End Class
[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 que el contrato coincida, el atributo debe tener el mismo tipo que .
Compile y ejecute el proyecto. Pruebe el nuevo operador Mod (%).
Conclusión
En este tema se trataron los conceptos básicos de MEF.
Elementos, catálogos y el contenedor de composición
Las partes y el contenedor de composición son los bloques de creación básicos de una aplicación MEF. Una parte es cualquier objeto que importa o exporta un valor, incluido él mismo. 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 exportaciones son la forma en que se comunican los componentes. Con una importación, el componente especifica una necesidad de un valor o objeto determinado y, con una exportación, especifica la disponibilidad de un valor. Cada importación coincide con una lista de exportaciones por medio de su contrato.
Pasos siguientes
Para descargar el código completo de este ejemplo, consulte el ejemplo SimpleCalculator (Visual Basic).
Para obtener más información y ejemplos de código, consulte Managed Extensibility Framework. Para obtener una lista de los tipos MEF, consulte el espacio de nombres.