sábado, 19 de março de 2011

Utilizando o MEF com Silverlight na prática

Utilizo o MEF constantemente para instanciar e apresentar as telas (UserControls/XAML) dentro de uma aplicação Silverlight. Dessa forma não preciso me preocupar em controlar instâncias de objetos (usando o new) pois o MEF por convenção trabalha no modo Singleton.

Ao final deste artigo, teremos uma aplicação simples que nos mostrará como visualizar diferentes telas que estão disponíveis em um Catálogo MEF:

printscreen da aplicação silverlight

Para ver a aplicação funcionando, clique aqui: http://dl.dropbox.com/u/14150556/ArtigoMef/SilverlightApplication1TestPage.html

Há muito material na internet para explicar o que é o MEF, então neste artigo vou apenas mostrar na prática como utilizar o MEF para criar e apresentar novas telas.

Crie uma nova aplicação Silverlight 4 e adicione as seguintes referências aos assemblies do MEF: 
  • System.ComponentModel.Composition  
  • System.ComponentModel.Composition.Initialization
Criando um Serviço de Catálogo

Para auxiliar a criação do catálogo MEF costumo optar por criar uma classe estática que irá nos auxilixiar com alguns métodos. Para isso, crie uma nova classe dentro do projeto Silverlight. Vou chamar essa classe de “CatalogoMef.cs”. Segue o conteúdo da classe com comentários:

public static class CatalogoMef
{
    private static readonly AggregateCatalog Catalog;
    private static readonly CompositionContainer Container;

    public static void InicializaCatalogo()
    {
        //esse método apenas força a execução do construtor static
        //e deve ser chamado na inicialização em App.xaml.cs
    }

    static CatalogoMef()
    {
        //cria um novo catálogo agregado
        Catalog = new AggregateCatalog();
        //deploymentCatalog representa o próprio arquivo XAP da aplicação Silverlight
        var deploymentCatalog = new DeploymentCatalog();
        //adiciona o deploymentCatalog ao catálogo agregado
        Catalog.Catalogs.Add(deploymentCatalog);
        Container = new CompositionContainer(Catalog);
        CompositionHost.Initialize(Container);
    }

    public static T GetInstance<T>(string contract = null)
    {
        //esse método será utilizado quando for solicitada uma instância de um determinado tipo
        return !string.IsNullOrEmpty(contract) ?
            Container.GetExportedValueOrDefault<T>(contract) :
            Container.GetExportedValueOrDefault<T>();
    }
}

Temos agora uma classe que irá nos auxiliar com o MEF.

MEF na prática

Abra o arquivo MainPage.xaml.cs e decore a classe com um atributo [Export]:

[Export("MainPageExport", typeof(UserControl))]
public partial class MainPage : UserControl

Esse atributo fará com que a classe MainPage do tipo UserControl seja exportada para o catálogo com uma string identificando-a, nesse caso, “MainPageExport”. Essa string é importante para identificar exclusivamente uma classe exportada.

O catálogo do MEF agora vai conter esse UserControl para ser utilizado a qualquer momento quando solicitado. Nesse caso, vamos atribuir o RootVisual da aplicação com o MEF. Abra o arquivo App.xaml.cs e no evento StartUp, altere para ficar da seguinte maneira:

private void Application_Startup(object sender, StartupEventArgs e)
{
     CatalogoMef.InicializaCatalogo();
     this.RootVisual = CatalogoMef.GetInstance<UserControl>("MainPageExport");
}

Passamos o parâmetro genérico <UserControl> para o método GetInstance, pois queremos uma instância do tipo UserControl que foi exportada com a string “MainPageExport”.

Repare que não precisamos mais criar um novo UserControl com a palavra reservada new. O MEF mantém o controle sobre as instâncias de seu catálogo, e caso já exista uma instância do tipo solicitado, ele devolve apenas a mesma referência, evitando que fique na memória duas instâncias iguais, e caso não haja nenhum instância criada, será criada uma nova instância e sua referência será retornada.

Agora vamos criar uma região dentro de MainPage que será usaremos para importar UserControl.

Em  MainPage.Xaml coloque o seguinte conteúdo:

<Grid x:Name="LayoutRoot" Background="White">
    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="MainPage" />
            <Button Content="Tela1" Click="Tela1_Click" />
            <Button Content="Tela2" Click="Tela2_Click" />
        </StackPanel>

        <ContentControl Name="mainPageContentControl" />
    </StackPanel>
</Grid>

Repare que agora temos dois botões com eventos Click Tela1_Click e Tela2_Click. Precisamos adicionar os manipuladores de evento no codebehind (MainPage.xaml.cs):

private void Tela1_Click(object sender, RoutedEventArgs e)
{
    mainPageContentControl.Content = CatalogoMef.GetInstance<UserControl>("Tela1");
}

private void Tela2_Click(object sender, RoutedEventArgs e)
{
    mainPageContentControl.Content = CatalogoMef.GetInstance<UserControl>("Tela2");
}

Quando o usuário clicar em um botão, setaremos o conteúdo interno do componente chamado mainPageContentControl para o valor de um outro userControl. Nesse momento ao rodar a aplicação, nada irá acontecer, pois não criamos ainda um UserControl com o atributo [Export] com a string “Tela1”. É o que faremos agora ! Adicione um novo “Silverlight User Control” com o nome Tela1. No arquivo Tela1.xaml, coloque o seguinte conteúdo:

<Grid x:Name="LayoutRoot" Background="Green">
    <StackPanel>
        <TextBlock Text="Esta é a tela 1!!" FontSize="18" />
        <TextBlock Name="dataHoraCriacao" />
    </StackPanel>
</Grid>

Repare que coloquei um TextBlock nomeado “dataHoraCriacao”. Vamos setar o texto desse controle para a data e hora em que a tela foi criada. Para isso no codebehind Tela1.xaml.cs coloque o seguinte código:

[Export("Tela1", typeof(UserControl))]
public partial class Tela1 : UserControl
{
    public Tela1()
    {
       InitializeComponent();
       dataHoraCriacao.Text = DateTime.Now.ToString();
    }
}

A razão pela qual coloquei esse textblock com o valor da data e hora de criação é para provar que o MEF retornará sempre a mesma instância que foi criada quando for apresentada novamente a Tela1.

Vamos adicionar mais um usercontrol que será a tela2, faça da mesma maneira anterior mas com o seguinte conteúdo agora em Tela2.xaml:

<Grid x:Name="LayoutRoot" Background="Aqua">
    <StackPanel>
        <TextBlock Text="Esta é a tela 2!!" FontSize="18" />
        <TextBlock Name="dataHoraCriacao" />
    </StackPanel>
</Grid>

Para diferenciar a Tela1 da Tela2, repare as cores de background são diferentes.

No codebehind Tela2.xaml.cs coloque o seguinte código:

[Export("Tela2", typeof(UserControl))]
public partial class Tela2 : UserControl
{
   public Tela2()
   {
       InitializeComponent();
       dataHoraCriacao.Text = DateTime.Now.ToString();
   }
}

Agora compile e execute a aplicação. Você verá que a mainpage possui 2 botões, sendo que ao clicar no primeiro a Tela1 será apresentada, e o segundo chamará a Tela2. Clique alternadamente nos botões e repare na data e hora de criação, que permanece sempre a mesma data e hora no momento em que foi criado pelo MEF.

Espero que tenham gostado do artigo. Estou preparando um próximo artigo em que mostrarei como utilizar interfaces com MEF e aplicar o conceito de MVVM para transições de telas.


Código fonte do projeto usado neste artigo:


4 comentários:

  1. MEF é a abreviação para "Managed Extensibiity Framework", um framework disponível no .NET 4.0 que permite o desenvolvimento de aplicações extensíveis e plugáveis.

    Na prática, o MEF nos disponibiliza um Contêiner onde encontramos referências às classes que foram exportadas para o catálogo que criamos durante a execução da aplicação.

    Podemos usar esse catálogo para procurar uma implementação de uma determinada classe que procuramos.

    Nesse artigo mostro de uma forma simples e direta como usar essa funcionalidade com MEF & Silverlight.

    Lembrando que o MEF possui outras funcionalidades, sendo que a principal é a criação de plugins e extensibilidade.

    ResponderExcluir
  2. Seguindo o seu exemplo, adicionei a classe "CatalogoMef" a opção de AdicionarXap, para adicionar outros projetos Silverlight. o que está acontecendo é o seguinte:

    - Quando clico no botão para carregar a Tela(Usercontrol), ele está baixando o arquivo, mas não é encontrado pelo "Container".
    - Na segunda vez que clico no botão ele carrega normalmente, sem precisar fazer o donwload do XAP.

    O que pode estar acontecendo?

    ResponderExcluir
  3. Marcos, Acredito que faltou você colocar a propriedade AllowRecomposition=true no atributo Import

    ResponderExcluir
  4. Olá, como usamos essa solucao com WCF RIA?
    Por exemplo em uma aplicacao BusinessApplication??
    Obrigado.
    Joao Carlos

    ResponderExcluir