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 es una parte integral de .NET Framework 4 y está disponible siempre que se use .NET Framework. Puede usar MEF en las aplicaciones cliente, tanto si usan Windows Forms, WPF como 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 extensos, consulte Managed Extensibility Framework en GitHub.
Para empezar, en Visual Studio, cree un nuevo proyecto de aplicación de consola y asígnelo el nombre
SimpleCalculator
.Agregue una referencia al ensamblado
System.ComponentModel.Composition
, donde se encuentra MEF.Abra Module1.vb o Program.cs y agregue
Imports
directivas ousing
paraSystem.ComponentModel.Composition
ySystem.ComponentModel.Composition.Hosting
. Estos dos espacios de nombres contienen tipos MEF que necesitará para desarrollar una aplicación extensible.Si usa Visual Basic, agregue la
Public
palabra clave a la línea que declara elModule1
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 CompositionContainery 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 Program
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 Program
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 ComposeParts le indica al contenedor de composición que componga un conjunto específico de partes, en este caso, la instancia actual de Program
. Sin embargo, en este momento no ocurrirá nada, ya que Program
no tiene elementos que importar.
Importaciones y exportaciones con atributos
En primer lugar, debes Program
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 Program
, desde la lógica de la calculadora.
Agregue el siguiente código a la clase Program
:
<Import(GetType(ICalculator))>
Public Property calculator As ICalculator
[Import(typeof(ICalculator))]
public ICalculator calculator;
Observe que la declaración del calculator
objeto no es inusual, pero que está decorada con el ImportAttribute 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 ICalculator
. Cualquier exportación declarada con un contrato coincidente cumplirá esta importación. Tenga en cuenta que, aunque el tipo del calculator
objeto es de hecho ICalculator
, esto no es necesario. El contrato es independiente del tipo del objeto de importación. (En este caso, podría dejar fuera el typeof(ICalculator)
. 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 SimpleCalculator
.
Public Interface ICalculator
Function Calculate(input As String) As String
End Interface
public interface ICalculator
{
string Calculate(string input);
}
Ahora que ha definido ICalculator
, necesita una clase que la implemente. Agregue la siguiente clase al módulo o al espacio de nombres SimpleCalculator
:
<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 Program
. 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 typeof(MySimpleCalculator)
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 MySimpleCalculator
parte estará disponible. Cuando el constructor para Program
realiza la composición en el objeto Program
, su importación se llenará con un objeto MySimpleCalculator
que se creará para ese propósito.
La capa de interfaz de usuario (Program
) 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 Main
.
Agregue el código siguiente al método Main
:
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 Calculate
de ICalculator
en el resultado, que lo vuelve a escribir en la consola. Eso es todo el código que necesita en Program
. 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 ImportAttribute ordinario lo completa un atributo ExportAttribute 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 ImportManyAttribute atributo .
Agregue la siguiente propiedad de operaciones a la clase MySimpleCalculator
:
<ImportMany()>
Public Property operations As IEnumerable(Of Lazy(Of IOperation, IOperationData))
[ImportMany]
IEnumerable<Lazy<IOperation, IOperationData>> operations;
Lazy<T,TMetadata> 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 Lazy<T,TMetadata> contiene un IOperation
objeto , que representa una operación real y un IOperationData
objeto , que representa sus metadatos.
Agregue las siguientes interfaces simples al módulo o al espacio de nombres SimpleCalculator
:
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 SimpleCalculator
:
<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 ExportAttribute atributo funciona como lo hizo antes. El ExportMetadataAttribute atributo adjunta metadatos, en forma de par nombre-valor, a esa exportación. Aunque la Add
clase implementa IOperation
, una clase que implementa IOperationData
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 Program
explícitamente, que importó un ICalculator
que resultó ser del tipo MySimpleCalculator
. MySimpleCalculator
, a su vez, importa una colección de IOperation
objetos y esa importación se rellenará cuando MySimpleCalculator
se cree, al mismo tiempo que las importaciones de Program
. Si la Add
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 MySimpleCalculator
clase para implementar el Calculate
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 foreach
bucle, se examinan todos los miembros de la operations
colección. Estos objetos son de tipo Lazy<T,TMetadata>y se puede tener acceso a sus valores de metadatos y al objeto exportado con la Metadata propiedad y la Value propiedad respectivamente. En este caso, si se detecta que la Symbol
propiedad del IOperationData
objeto es una coincidencia, la calculadora llama al Operate
método del IOperation
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 MySimpleCalculator
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 Public
palabra clave 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 SimpleCalculator
:
<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 DirectoryCatalog.
Agregue un nuevo directorio denominado Extensions
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 ExtendedOperations
. 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 Program
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 DirectoryCatalog agregará cualquier parte encontrada en cualquier ensamblado del directorio Extensions al contenedor de composición.
En el ExtendedOperations
proyecto, agregue referencias a SimpleCalculator
y System.ComponentModel.Composition
. En el archivo de clase ExtendedOperations
, agregue una directiva Imports
o using
para System.ComponentModel.Composition
. En Visual Basic, agregue también una instrucción Imports
para SimpleCalculator
. A continuación, agregue la siguiente clase al archivo de ExtendedOperations
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 ExportAttribute atributo debe tener el mismo tipo que .ImportAttribute
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 System.ComponentModel.Composition espacio de nombres.