Visão geral sobre estrutura de extensibilidade gerenciada
Este tópico fornece uma visão geral da estrutura de extensibilidade gerenciada introduzido na.NET Framework 4.
Este tópico contém as seguintes seções.
- O que é MEF?
- O problema de extensibilidade
- O que fornece MEF
- Onde MEF está disponível?
- MEF e MAF
- SimpleCalculator: Um aplicativo de exemplo
- Catálogos e o recipiente de composição
- Importa e exporta com atributos
- Outras importações e ImportMany
- Lógica da Calculadora
- Estendendo usando uma nova classe de SimpleCalculator
- Estendendo usando um novo conjunto de SimpleCalculator
- Conclusão
- Onde devo ir agora?
O que é MEF?
A estrutura de extensibilidade gerenciado ou MEF é uma biblioteca para a criação de aplicativos leves e extensíveis. Ele permite que os desenvolvedores de aplicativos descobrir e usar as extensões com nenhuma configuração necessária. Ele também permite que os desenvolvedores de extensão encapsular o código e evitar dependências de disco rígidas frágil. MEF não só permite que extensões para ser reutilizado em aplicativos, mas em todos os aplicativos também.
O problema de extensibilidade
Imagine que você é o arquiteto de um aplicativo grande, deve fornecer o suporte para extensibilidade. Seu aplicativo deve incluir um número potencialmente grande de componentes menores e é responsável pela criação e executá-los.
A abordagem mais simples para o problema é incluir os componentes como o código-fonte em seu aplicativo e chamá-las diretamente em seu código. Isso tem várias desvantagens óbvias. Mais importante, é possível adicionar novos componentes sem modificar o código-fonte, uma restrição que pode ser aceitável em, por exemplo, um aplicativo da Web, mas não funciona em um aplicativo cliente. Igualmente problemático, você não tenha acesso ao código-fonte para os componentes porque eles podem ser desenvolvidos por terceiros e pelo mesmo motivo, que você não pode permitir que eles acessem a sua.
Seria uma abordagem um pouco mais sofisticada fornecer um ponto de extensão ou interface, para permitir a separação entre o aplicativo e seus componentes. Com esse modelo, você poderá fornecer uma interface que um componente pode implementar e uma API para habilitá-lo para interagir com seu aplicativo. Este procedimento resolve o problema da exigência de acesso ao código fonte, mas ele ainda tem suas próprias dificuldades.
Porque o aplicativo não tem qualquer capacidade para descobrir os componentes em seu próprio, ele deverá ainda ser explicitamente instruído quais componentes estão disponíveis e devem ser carregados. Normalmente, isso é conseguido explicitamente registrando componentes disponíveis em um arquivo de configuração. Isso significa que, assegurando que os componentes está correta torna-se um problema de manutenção, principalmente se ela for o usuário final e não o desenvolvedor é esperado para fazer a atualização.
Além disso, os componentes são incapazes de se comunicar com outro, exceto por meio de canais definidos rigidamente do próprio aplicativo. Se o arquiteto do aplicativo não tem prevista a necessidade de uma comunicação específica, é geralmente impossível.
Finalmente, os desenvolvedores de componentes devem aceitar uma dependência de disco rígida no qual assembly contém a implementação de interface. Isso torna difícil para um componente a ser usado em mais de um aplicativo e também pode criar problemas quando você cria uma estrutura de teste para componentes.
O que fornece MEF
Em vez desse registro explícito de componentes disponíveis, o MEF fornece uma maneira para descobri-las, implicitamente, por meio de composição. Um componente MEF, chamado um parte, declarativamente Especifica ambas as suas dependências (conhecido como importa) e quais recursos (conhecido como exporta) torna disponível. Quando uma parte é criada, o mecanismo de composição MEF atende às suas importações com o que está disponível a partir de outras partes.
Essa abordagem resolve os problemas abordados na seção anterior. Como partes MEF declarativamente especificar seus recursos, são descobertos em tempo de execução, que significa que um aplicativo pode fazer uso de partes sem referências embutido ou de arquivos de configuração frágil. MEF permite que aplicativos descobrir e examinar partes por seus metadados, sem instanciá-los ou até mesmo carregando seus assemblies. Como resultado, é necessário especificar cuidadosamente a quando e como as extensões devem ser carregadas.
Além de suas exportações fornecidas, uma parte pode especificar suas importações, que serão preenchidas por outras partes. Isso facilita a comunicação entre partes não apenas possíveis, mas é fácil e permite o cálculo de BOM do código. Por exemplo, serviços comuns a muitos componentes podem ser fatorados uma parte separada e facilmente modificados ou substituídos.
Como o modelo MEF não requer nenhuma dependência de disco rígida de um assembly de aplicativo específico, ele permite que extensões para ser reutilizado no aplicativo para aplicativo. Isso também torna fácil desenvolver uma estrutura de teste independente do aplicativo, para testar os componentes de extensão.
Um aplicativo extensível gravado usando MEF declara uma importação que pode ser preenchida pelos componentes de extensão e pode também declarar exporta para expor os serviços de aplicativos para extensões. Cada componente de extensão declara uma exportação e também pode declarar imports. Dessa forma, os próprios componentes de extensão são automaticamente extensíveis.
Onde MEF está disponível?
MEF é parte integrante da .NET Framework 4e está disponível onde quer que o.NET Framework é usado. Você pode usar MEF em seus aplicativos de cliente, se usar o Windows Forms, WPF ou qualquer outra tecnologia ou em aplicativos de servidor que usam o ASP.NET.
MEF e MAF
Versões anteriores do.NET Framework introduziu o Managed suplemento Framework (MAF), projetado para permitir que os aplicativos isolar e gerenciar extensões. O foco MAF é um pouco de nível superior, em seguida, MEF, concentrando-se em isolamento de extensão e o assembly carregar e descarregar, enquanto o foco do MEF é descoberta, extensibilidade e portabilidade. As duas estruturas interoperam sem problemas e um único aplicativo pode aproveitar de ambos.
SimpleCalculator: Um aplicativo de exemplo
A maneira mais simples de ver o que MEF pode fazer é criar um aplicativo simples do MEF. Neste exemplo, você pode criar uma calculadora muito simple, denominada SimpleCalculator. O objetivo do SimpleCalculator é criar um aplicativo de console que aceita comandos aritméticos básicos, no formulário "5 + 3" ou "6-2" e retorna as respostas corretas. Usando MEF, você poderá adicionar novos operadores, sem alterar o código do aplicativo.
Para baixar o código completo para esse exemplo, consulte o a amostra de SimpleCalculator.
Observação
O objetivo do SimpleCalculator é demonstrar os conceitos e a sintaxe do MEF, em vez de necessariamente fornecer um cenário realista para seu uso.Muitos dos aplicativos que seriam aproveitar o poder do MEF são mais complexos do que SimpleCalculator.Para obter exemplos mais abrangentes, consulte o Estrutura de extensibilidade gerenciado no Codeplex.
Para iniciar, Visual Studio 2010, crie um novo projeto de aplicativo de Console chamado SimpleCalculator. Adicione uma referência ao assembly System.ComponentModel.Composition, onde reside o MEF. Abra o Module1. vb ou Program. cs e adicione Imports ou using instruções para System.ComponentModel.Composition e System.ComponentModel.Composition.Hosting. Esses dois espaços para nome contém tipos MEF, que você precisará desenvolver um aplicativo extensível. No Visual Basic, adicione a Public palavra-chave para a linha que declara o Module1 module.
Catálogos e o recipiente de composição
O núcleo do modelo de composição MEF é o o recipiente de composição, que contém todas as partes disponíveis e realiza a composição. (Isto é, a correspondência entre importações exporta.) O tipo mais comum de contêiner de composição é CompositionContainer, e você o usará para SimpleCalculator.
No Visual Basic, no Module1, adicione uma classe pública denominada Program. Em seguida, adicione a seguinte linha para o Program classe no Module1. vb ou Program. cs:
Dim _container As CompositionContainer
private CompositionContainer _container;
Para descobrir as partes disponíveis para ele, os recipientes de composição que faz uso de um Catálogo de. Um catálogo é um objeto que o torna disponíveis partes descobertas de origem. MEF fornece catálogos para descobrir as partes de um tipo fornecido, um assembly ou um diretório. Os desenvolvedores de aplicativos podem facilmente criar novos catálogos para descobrir as partes de outras fontes, como, por exemplo, um serviço da Web.
Adicione o seguinte construtor para o Program classe:
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 Exception
Console.WriteLine(ex.ToString)
End Try
End Sub
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());
}
}
A chamada para ComposeParts informa o recipiente de composição para compor um conjunto específico de partes, neste caso, a instância atual do Program. Neste ponto, no entanto, nada acontecerá, pois Program não tem nenhuma importação para preenchimento.
Importa e exporta com atributos
Primeiro, você tem Program Importar uma calculadora. Isso permite a separação de usuário, preocupações de interface, como, por exemplo, a entrada do console e a saída que entrará em Program, da lógica da Calculadora.
Adicione o seguinte código à classe Program:
<Import(GetType(ICalculator))>
Public Property calculator As ICalculator
[Import(typeof(ICalculator))]
public ICalculator calculator;
Observe que a declaração da calculator o objeto não é incomum, mas que ela está decorada com o ImportAttribute atributo. Este atributo declara que algo seja uma importação; ou seja, ele será preenchido pelo mecanismo de composição quando o objeto é composto por.
Cada importação tem um contrato, que determina quais exportações haverá correspondência com. O contrato pode ser uma seqüência de caracteres especificada explicitamente ou ele pode ser gerado automaticamente pelo MEF a partir de um determinado tipo, neste caso, a interface ICalculator. Qualquer exportação declarada com um contrato correspondente atenderá a esta importação. Observe que enquanto o tipo da calculator na verdade, o objeto é ICalculator, não é necessária. O contrato é independente do tipo de objeto de importação. (Nesse caso, você poderia deixar o typeof(ICalculator). MEF assumirá o contrato deve ser baseado no tipo de importação, a menos que você especifique explicitamente.)
Adicione esta interface muito simple ao módulo ou SimpleCalculator namespace:
Public Interface ICalculator
Function Calculate(ByVal input As String) As String
End Interface
public interface ICalculator
{
String Calculate(String input);
}
Agora que você definiu ICalculator, você precisa de uma classe que implementa o proprietário. Adicione a seguinte classe ao módulo ou SimpleCalculator namespace:
<Export(GetType(ICalculator))>
Public Class MySimpleCalculator
Implements ICalculator
End Class
[Export(typeof(ICalculator))]
class MySimpleCalculator : ICalculator
{
}
Aqui está a exportação que corresponderá a importação em Program. Para exportar coincidir com a importação, exportação deve ter o mesmo contrato. Exportando sob um contrato com base em typeof(MySimpleCalculator) produziria uma incompatibilidade, e a importação não poderia ser preenchida; o contrato precisa coincidir exatamente.
Desde que o recipiente de composição será preenchido com todas as partes disponíveis neste assembly, o MySimpleCalculator parte estará disponível. Quando o construtor para Program executa a composição a Program o objeto, a sua importação será preenchida com um MySimpleCalculator objeto, que será criado para essa finalidade.
A camada de interface do usuário (Program) não precisa saber coisa. Portanto, você pode preencher o restante da lógica de interface do usuário no Main método.
Adicione o seguinte código para o Main método:
Sub Main()
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)
{
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));
}
}
Esse código simplesmente lê uma linha de entrada e chama o Calculate a função de ICalculator no resultado, ele grava de volta para o console. Isto é todo o código necessário em Program. Todo o resto do trabalho acontecerá em partes.
Outras importações e ImportMany
Para SimpleCalculator ser extensível, ele precisa importar uma lista das operações. Um comum ImportAttribute atributo é preenchido por um e somente um ExportAttribute. Se mais de um estiver disponível, o mecanismo de composição produz um erro. Para criar uma importação que pode ser preenchida por qualquer número de exportações, você pode usar o ImportManyAttribute atributo.
Adicione a seguinte propriedade de operações para o o MySimpleCalculator classe:
<ImportMany()>
Public Property operations As IEnumerable(Of Lazy(Of IOperation, IOperationData))
[ImportMany]
IEnumerable<Lazy<IOperation, IOperationData>> operations;
Lazy<T, TMetadata>um tipo é fornecido pelo MEF para manter referências indiretas para exportações. Aqui, com o objeto exportado propriamente dito, você também obtém exportar metadados, ou informações que descrevem o objeto exportado. Cada Lazy<T, TMetadata> contém um IOperation objeto representando uma operação real e um IOperationData o objeto, que representa metadados.
Adicione as seguintes interfaces simples ao módulo ou SimpleCalculator namespace:
Public Interface IOperation
Function Operate(ByVal left As Integer, ByVal 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; }
}
Nesse caso, os metadados para cada operação são o símbolo que representa a operação, como +, -, *, e assim por diante. Para disponibilizar a operação de adição, adicione a seguinte classe ao módulo ou SimpleCalculator namespace:
<Export(GetType(IOperation))>
<ExportMetadata("Symbol", "+"c)>
Public Class Add
Implements IOperation
Public Function Operate(ByVal left As Integer, ByVal 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;
}
}
O ExportAttribute funções de atributo, como fazia antes. O ExportMetadataAttribute atributo anexa metadados, na forma de um par de nome-valor, a exportação. Enquanto o Add classe implementa IOperation, uma classe que implementa IOperationData não definido explicitamente. Em vez disso, uma classe é implicitamente criada pelo MEF com propriedades com base nos nomes dos metadados fornecidos. (Essa é uma das várias maneiras de acessar os metadados no MEF.)
Composição na MEF é recursiva. Você explicitamente composta de Program objeto importado um ICalculator que tornou para ser do tipo MySimpleCalculator. MySimpleCalculator, por sua vez, importa uma coleção de IOperation de objetos, e que importação será preenchida quando MySimpleCalculator é criado, ao mesmo tempo, como as importações de Program. Se o Add classe declarada uma importação adicional, que também teria que ser preenchida e assim por diante. Qualquer importar esquerdos resultados não preenchidos em um erro de composição. (É possível, entretanto, para declarar imports para que sejam opcionais ou atribuir a eles valores padrão.)
Lógica da Calculadora
Com essas peças no local, tudo o que resta é a lógica de Calculadora. Adicione o seguinte código na MySimpleCalculator classe para implementar a Calculate método:
Public Function Calculate(ByVal input As String) As String Implements ICalculator.Calculate
Dim left, right As Integer
Dim operation As Char
Dim fn = FindFirstNonDigit(input) 'Finds the operator
If fn < 0 Then
Return "Could not parse command."
End If
operation = input(fn)
Try
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;
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!";
}
As etapas iniciais analisar a seqüência de caracteres de entrada em operandos esquerda e direita e um caractere de operador. No foreach loop, cada membro da operations coleção é examinada. Esses objetos são do tipo Lazy<T, TMetadata>, e seus valores de metadados e o objeto exportado podem ser acessados com o Metadata propriedade e o Value propriedade respectivamente. Nesse caso, se o Symbol propriedade da IOperationData objeto é descoberto para corresponder as chamadas de calculadora a Operate método o IOperation object e retorna o resultado.
Para concluir a Calculadora, você também precisa de um método auxiliar que retorna a posição do primeiro caractere não dígito em uma seqüência. Adicione o seguinte método auxiliar para o MySimpleCalculator classe:
Private Function FindFirstNonDigit(ByVal s As String) As Integer
For i = 0 To s.Length
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;
}
Você agora deve ser capaz de compilar e executar o projeto. Na Visual Basic, certifique-se de que você adicionou o Public palavra-chave para Module1. Na janela do console, digite uma operação de adição, como, por exemplo, "5 + 3", e a Calculadora retornará os resultados. Qualquer outro operador fará o "operação não encontrado!" mensagem.
Estendendo usando uma nova classe de SimpleCalculator
Agora que a Calculadora funciona, é fácil adicionar uma nova operação. Adicione a seguinte classe ao módulo ou SimpleCalculator namespace:
<Export(GetType(IOperation))>
<ExportMetadata("Symbol", "-"c)>
Public Class Subtract
Implements IOperation
Public Function Operate(ByVal left As Integer, ByVal 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;
}
}
Compilar e executar o projeto. Digite uma operação de subtração, como, por exemplo, "5-3". A Calculadora agora oferece suporte a subtração, bem como a adição.
Estendendo usando um novo conjunto de SimpleCalculator
Adicionar classes para a fonte de código é bastante simple, mas MEF fornece a capacidade de procurar parts fora de uma fonte do aplicativo. Para demonstrar isso, você precisará modificar o SimpleCalculator para pesquisar um diretório, bem como seu próprio assembly, peças, adicionando um DirectoryCatalog.
Adicionar um novo diretório chamado extensões para o projeto SimpleCalculator. Certifique-se de adicioná-lo no nível do projeto e não no nível da solução. Em seguida, adicione um novo projeto de biblioteca de classes à solução, chamada ExtendedOperations. O novo projeto será compilado em um assembly separado.
Abra o Designer de propriedades de projeto para o projeto de ExtendedOperations e clique no compilar ou Build guia. Alterar o Build output path ou caminho de saída para apontar para o diretório de extensões no diretório do projeto SimpleCalculator (... \SimpleCalculator\Extensions\).
Em Module1. vb ou Program. cs, adicione a seguinte linha para o Program construtor:
catalog.Catalogs.Add(New DirectoryCatalog("C:\SimpleCalculator\SimpleCalculator\Extensions"))
catalog.Catalogs.Add(new DirectoryCatalog("C:\\SimpleCalculator\\SimpleCalculator\\Extensions"));
Substitua o caminho de exemplo com o caminho para o diretório de extensões. (Esse caminho absoluto é somente para fins de depuração. Em um aplicativo de produção, você usaria um caminho relativo.) O DirectoryCatalog Agora adicionaremos quaisquer partes encontrados em todos os assemblies no diretório de extensões para o recipiente de composição.
No projeto ExtendedOperations, adicione referências a SimpleCalculator e System.ComponentModel.Composition. No arquivo de classe ExtendedOperations, adicione um Imports ou using a instrução para System.ComponentModel.Composition. No Visual Basic, também adicionar um Imports a instrução para SimpleCalculator. Em seguida, adicione a seguinte classe ao arquivo de classe ExtendedOperations:
<Export(GetType(SimpleCalculator.IOperation))>
<ExportMetadata("Symbol", "%"c)>
Public Class Modulo
Implements IOperation
Public Function Operate(ByVal left As Integer, ByVal 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;
}
}
Observe que na ordem do contrato corresponder, o ExportAttribute atributo deve ter o mesmo tipo que o ImportAttribute.
Compilar e executar o projeto. Teste o novo operador Mod (%).
Conclusão
Este tópico abordou os conceitos básicos do MEF.
O recipiente de composição, catálogos e partes
Partes e o contêiner de composição são os blocos de construção básicos de um aplicativo MEF. Uma parte é qualquer objeto que importa ou exporta um valor, até e incluindo o próprio. Um catálogo fornece uma coleção de partes de uma fonte específica. O recipiente de composição usa as partes fornecidas por um catálogo para executar a composição, a ligação de importações para exportações.
Importações e exportações
Importa e exporta é a maneira pela qual os componentes se comunicam. Com uma importação, o componente Especifica uma necessidade de um determinado valor ou o objeto e, com uma exportação Especifica a disponibilidade de um valor. Cada importação é comparada com uma lista de exportações por meio de seu contrato.
Onde devo ir agora?
Para baixar o código completo para esse exemplo, consulte o a amostra de SimpleCalculator.
Para obter mais informações e exemplos de código, consulte Estrutura de extensibilidade gerenciado. Para obter uma lista dos tipos MEF, consulte o System.ComponentModel.Composition namespace.
Histórico de alterações
Date |
History |
Motivo |
---|---|---|
Julho de 2010 |
Etapas atualizadas. Etapas adicionais de ausentes para o VB. Link para baixar exemplo adicionado. |
Comentários do cliente. |