.NET - Apresentando o MEF - Microsoft Extensibility Framework


O Microsoft Extensibility Framework (MEF) é um framework que foi introduzido na versão 4.0 do .NET Framework e também no SilverLight 4.0 e tem como objetivo principal permitir a criação e o gerenciamento de aplicações extensíveis.

Você já ouviu o jargão : "Trocar a roda com o carro andando..."

Pois é justamente isso que a utilização do MEF permite : que você possa estender sua aplicação em tempo de execução sem ter que recompilar toda a aplicação ou ter que redesenhá-la novamente.

Todos nós sabemos que um produto de software sofre constantes mudanças durante o seu ciclo de vida. Para minimizar os impactos dessas mudanças é aconselhável que a aplicação esteja aderente a certos princípios de arquitetura como:

O quesito extensibilidade é muito importante, pois sua adoção permite que você defina que partes de sua aplicação sejam extensíveis, de forma que essas funcionalidades possam ser substituídas em tempo de execução por outras implementações da mesma funcionalidade seja através de componentes de terceiros, plugins, etc.

Para dar suporte a uma arquitetura extensível você precisa definir em sua aplicação os pontos de extensibilidade que, em caso de necessidade, poderão ser substituídos em tempo de execução por implementações diferentes. Por exemplo substituir a implementação do cache por outra.

Naturalmente isso deve ser feito com o menor custo possível e sem impactar a sua aplicação como um todo, ou seja, isso tem que ser feito sem você ter que alterar o código ou ter que recompilar a sua aplicação.

Dependendo da complexidade envolvida na sua aplicação isso pode dar muito trabalho e por isso existem alguns frameworks que nos ajudam a realizar esta tarefa como : Unity Framework, Structure Map, Mono.Addins, Castle Windsor, etc.

O MEF é portanto mais um framework que podemos usar e ele apresenta as seguintes vantagens :

  1. Esta integrado com o .NET Framework 4.0;
  2. É mais simples de utilizar pois utiliza menos código;
  3. Não necessita manter arquivos de configurações - O MEF descobre os componentes definidos implicitamente;
  4. Dá suporte a API de forma simples e direta - Basta referenciar o assembly System.ComponentModel.Composition.dll;
  5. Usa o estilo de programação por atributos (Attribute Programming Model) - Através de atributos é feita a especificação de como os componentes podem ser definidos;

E como o MEF funciona ?

Vou descrever um cenário básico retirado do site da Microsoft, e que eu traduzi, para ilustrar o problema, e, depois mostrar como o MEF atua:
(fonte: http://msdn.microsoft.com/en-us/library/dd460648.aspx

O problema:

Imagine que você é o arquiteto de uma aplicação de grande porte que deve fornecer suporte para extensibilidade. A sua aplicação tem de incluir um número potencialmente grande de componentes menores, e é responsável pela sua criação e execução.

A abordagem mais simples para o problema é incluir os componentes como código-fonte em seu aplicativo, e chamá-los diretamente em seu código. Isto tem uma série de inconvenientes óbvios. Além disso, você não vai poder adicionar novos componentes sem modificar o código-fonte, uma restrição que pode ser aceitável, por exemplo, em uma aplicação Web, mas é impraticável em um aplicativo cliente. Outro problema é que você pode não ter acesso ao código-fonte para os componentes, porque podem ser desenvolvidos por terceiros.

Uma abordagem um pouco mais sofisticada seria fornecer um ponto de extensão ou de interface, para permitir o desacoplamento entre o aplicativo e seus componentes. Sob este modelo, você pode fornecer uma interface que um componente pode implementar, e uma API para que possa interagir com seu aplicativo. Isso resolve o problema de exigir acesso ao código fonte, mas ainda tem suas próprias dificuldades.

Como a aplicação não tem qualquer capacidade para descobrir os componentes por conta própria, você terá que definir explicitamente quais componentes estão disponíveis e devem ser carregados. Isto é feito através do registro dos componentes disponíveis em um arquivo de configuração. Isso significa que para garantir que os componentes estejam corretos criamos um problema de manutenção, especialmente se você for o usuário final e não o desenvolvedor.

Além disso, os componentes são incapazes de se comunicar uns com os outros, exceto por meio dos canais rigidamente definidos no próprio aplicativo. Se o arquiteto da  aplicação não antecipou a necessidade de uma comunicação particular, isso pode torna-se um problema praticamente impossível de resolver.

Finalmente, os desenvolvedores do componente devem aceitar uma forte dependência ao assembly que contém a interface a implementar. Isso torna difícil para um componente ser usado em mais de um aplicativo, e também pode criar problemas quando você cria um framework de teste para componentes.

A proposta do MEF:

Ao invés de ter que realizar o registro dos componentes disponíveis em arquivos de configuração o MEF fornece uma forma de descobri-los implicitamente via composição.

Um componente MEF, chamado part, declarativamente especifica suas dependências (conhecido como importações) e quais capacidades (conhecido como exportações) ele disponibiliza. Quando um componente part é criado, o mecanismo de composição MEF satisfaz suas importações com o que está disponível a partir de outros componentes part.

Em vez deste registro explícito dos componentes disponíveis o  MEF fornece uma maneira de descobri-los implicitamente, via composição.

O núcleo do modelo de composição MEF é o recipiente composition, que contém todas as peças disponíveis e executa a composição.( a correspondência entre as importações e as exportações).
O tipo mais comum de recipiente composição é o CompositionContainer.

Quando um part é criado, o mecanismo de composição MEF satisfaz suas importações com o que está disponível a partir de outros componentes part. Esta abordagem resolve os problemas discutidos na seção anterior.

Como os componentes part do MEF declarativamente especificam as suas capacidades, eles são descobertos em tempo de execução, o que significa que um aplicativo pode fazer uso dos componentes part sem qualquer referência fixa ou arquivos de configuração.

O MEF permite que os aplicativos descubram e examinam os componentes part por seus metadados, sem instanciá-los ou até mesmo carregar seus assemblies. Como resultado, não existe a necessidade de especificar quando e como as extensões devem ser carregadas.

Além de suas exportações, um part pode especificar as suas importações, que será preenchido por outros componentes part. Isso faz com que a comunicação entre os part, seja possível e fácil e permite boa fatoração do código.

Como o modelo MEF não requer nenhuma dependência fixa de um assembly particular ele permite que as extensões sejam reutilizadas de aplicação para aplicação. Isso também torna mais fácil o desenvolvimento de testes, independente da aplicação, para testar os componentes de extensão.

Uma aplicação extensível escrita através da utilização do MEF declara uma importação que pode ser preenchida por componentes de extensão, e pode também declarar as exportações, a fim de expor serviços do aplicativo para as extensões. Cada componente de extensão declara uma exportação, e pode também declarar importações. Desta forma, os componentes de extensão são automaticamente extensíveis entre si.

Isso simplifica muito a definição de extensibilidade nas aplicações pois podemos resumir a estrutura de atuação do MEF em 3 partes básicas:

  1. A parte que vai exportar o que queremos estender:  (Export)
  2. A parte que vai importar o que foi estendido: (Import)
  3. A parte que vai conter o container destas extensões: (Catalog)

Como já foi mencionado essas definições são feitas através do modelo de programação por atributos onde os Importações e as Exportações são feitos através da marcação usando os atributos Import e Export.

Além disso temos que definir um contrato que é uma interface de comunicação que serve de base para a extensão, ou seja, temos que definir o contrato que vai ser seguido, neste contrato definimos o que queremos exportar de funcionalidade e como vamos exportar.

A fim de descobrir os componentes part que estão disponíveis, o container de composição utiliza um catálogo (Catalog).

Um catálogo é um objeto que torna disponíveis os componentes part que foram descobertos a partir de alguma fonte. O MEF fornece catálogos para descobrir os componentes part de um tipo previsto, um assembly ou um diretório. (Os desenvolvedores podem facilmente criar novos catálogos para descobrir componentes part de outras fontes, tais como um serviço Web.)

Resumindo:

1 - Uma aplicação declara usando um atributo Import que depende de uma implementação que respeite o contrato definido;
2 - Todos os componentes que respeitarem o contrato definido são marcados com o atributo
Export;

Para utilizar o MEF, você precisa de quatro elementos:

  1. Contrato
  2. Import
  3. Export
  4. Catálogo

- O Contrato é o ponto comum entre um import e um export, e indica a funcionalidade esperada de uma parte. Em geral é definido através de uma interface, mas pode ser um tipo abstrato também;
- O Import é onde a parte será plugada, e pode ser uma propriedade, uma coleção  ou construtor de uma classe. É definido através do atributo [Import] ou [ImportMany]
- O Export é o plugin, a parte que será plugada, e, contém a implementação da funcionalidade definida pelo contrato. É definido pelo atributo [Export].
- O Catálogo contém os tipos exportados e que podem ser utilizados para satisfazer os Imports de cada contrato;

O MEF apresenta quatro tipos de catálogos:

  1. DirectoryCatalog - Procura os tipos exportados nos assemblies em um determinado diretório;
  2. AssemblyCatalog - Representa todos os tipos de um assembly;
  3. TypeCatalog - Permite especificar explicitamente quais tipos compõem o catálogo;
  4. AggregateCatalog - Combina vários catálogos;

Existem diversos outros tipos de catálogos que podem ser encontrados na internet, inclusive alguns que usam mapeamento XML. Os catálogos são utilizados pelo container no MEF (CompositionContainer) para criação dos tipos.

Abaixo vemos a figura que mostra um esquema simplificado do funcionamento do MEF:(fonte: http://mef.codeplex.com/wikipage?title=Overview&ProjectName=mef)

 Colocando a teoria em prática

Vamos agora mostrar como usar os recursos básicos do MEF em uma aplicação bem simples que embora não apresenta um cenário real serve como propósito de apresentação e entendimento do MEF.

Se você ainda não o fez baixe os binários atualizados do MEF em : http://mef.codeplex.com/releases/view/79090

Vamos criar um exemplo bem básico de um aplicativo do tipo Console (baseado em um exemplo de Brad Abrams) usando o Visual C# 2010 Express Edition que imprima a mensagem: "Bem vindo ao MEF".

Abra então o Visual C# 2010 Express Edition e crie um novo projeto do tipo Console Application com o nome Usando_MEF;

A seguir defina o seguinte código no arquivo Program.cs:

using System;

namespace Usando_MEF

{

   class Program

   {

     public void Run()

     {

       Console.WriteLine("Apresentando MEF");

       Console.ReadKey();

     }
 

     static void Main(string[] args)

     {

         Program p = new Program();

         p.Run();

     }

   }

}

Vamos agora alterar nosso programa conforme o código abaixo apenas para termos uma motivação para usar o MEF:

using System;

namespace Usando_MEF

{

   class Program

   {

      public string Mensagem { get; set; }
 

      public class Apresentacao

      {

         public String Mensagem

         {

            get

            {

               return "Apresentando MEF";

            }

         }

      }


     public
void Run()

     {

       Apresentacao ap = new Apresentacao();

      Mensagem = ap.Mensagem;
 

       Console.WriteLine(Mensagem);

       Console.ReadKey();

     }
 

     static void Main(string[] args)

     {

       Program p = new Program();

       p.Run();

     }

  }

}

O código alterado funciona da mesma forma mas com a alteração introduzimos um forte acoplamento com a classe
Apresentação pois temos que criar uma instância desta classe
no método Run();

Vamos então criar um ponto de extensão nas linhas:

Apresentacao ap = new Apresentacao();

Mensagem = ap.Mensagem;


de forma que elas possam ser controladas sem afetar o resto da lógica do programa.
 

Vamos fazer isso usando os recursos do MEF.

 

Clique com o botão direito sobre o nome do projeto e selecione Add Reference;

A seguir na janela Add Reference clique na aba Browse e localize os binários onde você instalou os arquivos do MEF:

Você só precisa selecionar a referência a System.ComponentModel.Composition.CodePlex.dll e clicar no botão OK;

A seguir defina os seguintes namespaces no início do arquivo Program.cs:

using System;
using
System.ComponentModel.Composition;
using
System.ComponentModel.Composition.Hosting;
using
System.Reflection;

Agora vamos alterar o código do método Run() usando os recursos do MEF conforme abaixo:

1   public void Run()

2  {

3     //Apresentacao ap = new Apresentacao();

   //Mensagem = ap.Mensagem;

5     var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

6     var container = new CompositionContainer(catalog);

7     var batch = new CompositionBatch();

8     batch.AddPart(this);

9     container.Compose(batch);
10

11    Console.WriteLine(Mensagem);

12    Console.ReadKey();

13 }

 

No código acima temos :

- Na linha 5 criamos um catalogo chamado catalog que indica ao MEF onde procurar os imports e exports. Neste caso nós estamos informando que para procurar no assembly atual;

- Na linha 6 criamos um container Composition e dessa forma juntamos todas as partes;
- Na linha 7 criamos um CompositionBatch que contém todas as partes que serão adicionadas ou removidas;
- Na linha 8 incluímos uma instância do Programa para o container de forma que suas dependências sejam vinculadas;
- Na linha 9 efetuamos a composição; é aqui onde a propriedade Mensagem do programa é obtida;

Executando o projeto iremos obter o mesmo resultado inicial mostrando que o MEF fez o trabalho direitinho...

Esta é uma pequena introdução ao MEF onde eu não entrei em detalhes de funcionamento nem na sua estrutura; preocupei-me apenas em apresentar o conceito e mostrar como ele funciona de forma bem simples.

Aguarde em breve mais artigos sobre o assunto...

Pegue o projeto completo aqui: Usando_MEF.zip

Col 3:2 Pensai nas coisas que são de cima, e não nas que são da terra;
Col 3:3
porque morrestes, e a vossa vida está escondida com Cristo em Deus.

Col 3:4
Quando Cristo, que é a nossa vida, se manifestar, então também vós vos manifestareis com ele em glória.

Referências:

José Carlos Macoratti