Introdução ao MEF - Managed Extensibility Framework

O MEF é um framework que faz mágica quando o assunto é modularização. Disponível a partir do .NET 4.0, o MEF é um framework com o foco em extensibilidade.

Através de catálogos de assembly, o MEF descobre objetos exportáveis e os encaixa nos objetos importáveis, lembrando que a tarefa de marcar os objetos exportáveis e importáveis é do desenvolvedor, afinal, o .NET é bom mas ainda não adivinha o que você quer.

Por que usar?

O principal uso do MEF é modularizar aplicações, assim como prover um mecanismo de extensão. Um bom exemplo é o sistema de add-in do Visual Studio pois ele utiliza o MEF.

Agora imagine uma aplicação de back office, teríamos vários módulos como: financeiro, comercial, suprimentos, etc. Seria fantástico se pudessemos implantar novos recursos no módulo financeiro sem ter que atualizar toda a aplicação, não?

Pois é, com o MEF é possível. Devido ao fato do framework carregar um catálogo de assembly, cada módulo pode ter seu próprio assembly.

Mão na massa

Idealizei um pequeno exemplo para mostrar o mais básico possível sobre o MEF.

Antes começarmos, para este post estou usando o Visual Studio Express 2013, um projeto Console Application, em C#, com o .NET 4.5.1.

Imagine que temos um personagem, e para protegê-lo vamos vesti-lo com uma armadura, assim temos a classe Personagem:

using System.ComponentModel.Composition;

namespace MEF_Teste
{
    public class Personagem
    {
        [Import]
        private Armadura armadura = null;

        public string nomeArmadura()
        {
            if (armadura == null)
            {
                return "Armadura não definida";
            }
            else
            {
                return armadura.getNome();
            }
        }
    }
}

E a classe Armadura:

namespace MEF_Teste
{
    [Export]
    public class Armadura
    {
        private string nome;

        public Armadura()
        {
            this.nome = "Armadura de madeira";
        }

        public string getNome()
        {
            return this.nome;
        }
    }
}

Note que o namespace do MEF é System.ComponentModel.Composition. Para que funcione corretamente, adicione a referência deste namespace ao seu projeto.

Bom, marcamos a classe Armadura com o atributo Export e a propriedade armadura da classe Personagem com o atributo Import. Assim dizemos ao MEF: a classe Armadura é exportável e a propriedade armadura da classe Personagem pode ser importada.

O programa principal é muito simples:

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

namespace MEF_Teste
{
    public class Program
    {
        static void Main()
        {
            Personagem p = new Personagem();

            Console.WriteLine("Armadura antes: " + p.nomeArmadura());

            AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            CompositionContainer container = new CompositionContainer(catalog);

            container.ComposeParts(p);

            Console.WriteLine("Armadura depois: " + p.nomeArmadura());

            Console.ReadKey();
        }
    }
}

Vamos estudá-lo. Primeiro criamos uma nova instância de Personagem e exibimos qual armadura o personagem tem.

Criamos um catálogo de assembly com o assembly que está sendo executado:

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

E também um container de composição:

CompositionContainer container = new CompositionContainer(catalog);

Em relação ao catálogo, podemos ter mais de um assembly em um catálogo. Também podemos ter mais de um tipo de catálogo como por exemplo um diretório inteiro de assembly, mas isso irei mostrar mais adiante.

A mágica do MEF está no CompositionContainer. Ele recebe um catálogo e realiza a busca por classes exportáveis dentro de cada assembly do catálogo, que no nosso caso é apenas um.

No comando abaixo, o CompositionContainer entra em ação:

container.ComposeParts(p);

Detalhe: o ComposeParts padrão pode ocasionar um erro de compilação. O método ComposeParts padrão não aceita este comportamento, então declare o using do namespace System.ComponentModel.Composition.Hosting que permitirá este comportamento.

O CompositionContainer criou uma instância da classe Armadura e a colocou na propriedade armadura da classe Pessoa. Note que mesmo em propriedades privadas o MEF funciona normalmente.

O resultado foi:

Armadura antes: Armadura não definida
Armadura depois: Armadura de madeira

Incrível, não?

Mais um exemplo

Carregar os objetos do assembly em execução parece fácil, mas agora vamos ver como carregar objetos de outro assembly. Utilizando a idéia (e um pouco de criatividade) é possível criar partes de sistemas que possam ser distribuídas separadamente. Vamos lá.

Primeiramente, crie um projeto do tipo Class Library (no meu caso dei o nome de MEF_Teste.Interfaces) e crie uma interface chamada IArmadura:

namespace MEF_Teste.Interfaces
{
    public interface IArmadura
    {
        string getNome();
    }
}

Por que criar um projeto só pra interface? De alguma forma, você precisa saber o que vai usar mesmo que somente o essencial, por exemplo: você cria um recurso de impressão e o distribui numa DLL e uma aplicação sua utiliza tal recurso através do MEF, no mínimo, você precisa ter acesso ao comando que inicia a impressão certo? Aí entra as interfaces.

Maravilha, agora crie outro projeto Class Library (no meu caso ficou como MEF_Teste.Madeira) e adicione a refência do projeto de interfaces, que no meu caso é MEF_Teste.Interfaces. Agora crie uma classe ArmaduraMadeira implementando a interface IArmadura:

using MEF_Teste.Interfaces;
using System.ComponentModel.Composition;

namespace MEF_Teste.Madeira
{
    [Export(typeof(IArmadura))]
    public class ArmaduraMadeira : IArmadura
    {
        public string getNome()
        {
            return "Armadura de madeira";
        }
    }
}

Note a anotação Export, onde dizemos que estamos exportando a interface IArmadura, independente de nossa implementação. O uso de interfaces ajuda no sentido de que podemos ter várias implementações para a mesma coisa, então poderíamos ter uma armadura que faz conexão com o banco de dados para obter o nome e para a classe Personagem só iria interessar o método getNome, independente do que ele faz por trás.

Atualizando o projeto principal, temos:

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

namespace MEF_Teste
{
    public class Program
    {
        static void Main()
        {
            Personagem p = new Personagem();

            Console.WriteLine("Armadura antes: " + p.nomeArmadura());

            AssemblyCatalog catalog = new AssemblyCatalog(@"D:MEF_Teste.Madeira.dll");
            CompositionContainer container = new CompositionContainer(catalog);

            container.ComposeParts(p);

            Console.WriteLine("Armadura depois: " + p.nomeArmadura());

            Console.ReadKey();
        }
    }
}

A única diferença é que o AssemblyCatalog é de uma DLL que foi gerada pelo projeto MEF_Teste.Madeira.

Executando o projeto principal novamente:

Armadura antes: Armadura não definida
Armadura depois: Armadura de madeira

Aqui vai um exercício: criar uma nova armadura e substituir a armadura de madeira, mudando apenas o arquivo DLL que é carregado pelo MEF.

Conclusão

O que foi mostrado aqui é somente a ponta do iceberg, e já mostra que o MEF pode ser uma ferramenta muito boa se aplicada corretamente. Mas lembre-se: sair usando o MEF em tudo que puder pode não ser a melhor solução, por isso é sempre bom analisar o cenário e se no seu caso será uma vantagem a utilização do framework.

Criei um repositório no GitHub para quem quiser um exemplo.

Até mais.