Este artigo foi traduzido por máquina.

Managed Extensibility Framework

Criando aplicativos extensíveis no .NET 4 com o Managed Extensibility Framework

Glenn Block

Com futuro Microsoft .NET Framework 4, você verá uma nova tecnologia empolgante na porta da sua casa que simplificará bastante o desenvolvimento de aplicativos. Se tiver lutava com como projetar aplicativos que são mais fáceis de manter e estender, continue lendo.

O Managed Extensibility Framework (MEF) é uma nova biblioteca de remessa no .NET Framework 4 e no Silverlight 4 que simplifica o design dos sistemas compostos que podem ser estendidos por terceiros após ter sido implantados. MEF abre seus aplicativos, permitindo que a nova funcionalidade ao ser introduzido incrementalmente, os desenvolvedores de aplicativos, os autores de estrutura e extensores de terceiros.

Por que é criado IT

Vários anos atrás, dentro da Microsoft, um número de grupos estava trabalhando para encontrar soluções para um problema — como criar aplicativos de componentes reutilizáveis que podem ser descobertas, reutilizado e composta dinamicamente:

  • Visual Studio 2010 estava criando um novo editor de código extensíveis. Principais recursos do editor, bem como pela recursos de terceiros, eram todos para serem implantados como binários que poderiam ser descobertos em tempo de execução. Um dos principais requisitos foi ociosamente suporte as extensões de carregamento para melhorar o consumo de tempo e memória de inicialização.
  • “ Oslo ” introduziu “ Intellipad ”, um novo editor de texto extensível para trabalhar com MEF. Plug-ins Intellipad, eram para ser criado em IronPython.
  • Acropolis foi fornecendo uma estrutura para a criação de aplicativos compostos. O tempo de execução Acropolis descobertos aplicativo “ componentes ” no tempo de execução e fornecidos essas partes com serviços de forma rígida. Acropolis fez uso intenso de XAML para autoria de componentes.

Esse problema não era específico da Microsoft. Os clientes têm implementando suas próprias soluções personalizadas de extensibilidade para idades. Aqui foi oportunidade clara para a plataforma para a etapa e fornecer uma solução mais uso geral para ajudar a Microsoft e os clientes.

We preciso algo novo?

MEF não é por qualquer meio, a primeira solução neste espaço de problema. Houve muitas soluções propostas — uma longa lista de empreendimentos que ultrapassam os limites de plataforma e incluir esforços como EJB, CORBA, implementação de OSGI e Spring no lado Java do Eclipse. Na plataforma da Microsoft, há o modelo de componente e System.Addin dentro do .NET Framework propriamente dito. E há diversas soluções de código-fonte aberto, incluindo a arquitetura da SODA e contêineres de inversão de controle como Castle Windsor, Structure Map e padrões & Unity práticas ’ do SharpDevelop.

Com todas as abordagens existentes, por isso que surgir com algo novo? Percebemos que nenhuma das nossas soluções atuais foram ideais para extensibilidade geral de terceiros. Eles foram muito heavyweight para uso geral ou necessário muito esforço por parte do host ou o desenvolvedor de extensão. MEF representa o apogeu de aprendizado de cada uma dessas soluções e uma tentativa de resolver os pontos problemáticos que acabei de mencionar.

Let’s dar uma olhada no principais conceitos do MEF, ilustradas Figura 1.

image: Core Concepts in the Managed Extensibility Framework

Figura 1 Principais conceitos do Managed Extensibility Framework

Conceitos

O cerne de MEF são alguns conceitos essenciais:

Compostos parte (ou, simplesmente, parte) — parte fornece serviços para outras partes e consome serviços fornecidos por outras partes. Peças no MEF podem vir de qualquer lugar, de dentro do aplicativo ou externamente, da perspectiva de MEF, ele não faz diferença.

Exportar — uma exportação é um serviço que fornece uma parte. Quando uma parte fornece uma exportação, ele é considerado que a parte Exporta it. Por exemplo, uma parte pode exportar um agente de log, ou no caso do Visual Studio, uma extensão de editor. Partes podem fornecer várias exportações, embora a maioria das partes fornecem uma única exportação.

Importar — uma importação é um serviço que consome uma parte. Quando uma parte consome uma importação, a parte importa it. Partes podem importar serviços único, como o agente de log, ou importar vários serviços, como uma extensão de editor.

Contratos — um contrato é um identificador para uma importação ou de exportação. Um exportador Especifica um contrato de seqüência de caracteres que ele fornece e um importador Especifica o contrato que ele precisa. MEF deriva contrato nomes dos tipos que estão sendo exportados e importadas, isso na maioria dos casos, você Don precisará pensar nisso.

Composição — peças são compostas por MEF, que instancia-los e, em seguida, corresponde ao backup exporters para importadores.

Modelos de programação — Face(s) de MEF

Os desenvolvedores consumam MEF por meio de um modelo de programação. Um modelo de programação fornece um meio para declarar componentes como MEF partes. Fora da caixa, MEF oferece um modelo de programação atribuído, que será o principal foco deste artigo. Esse modelo é apenas um dos muitos modelos de programação possíveis MEF permite. Núcleo do MEF API é completamente independente para atributos.

Mergulhar no modelo de programação atribuída

No modelo de programação atribuído, partes (conhecidas como partes atribuídas) são definidos com um conjunto de atributos .NET que vivem no namespace System.ComponentModel.Composition. As seções a seguir, explorarei criando um aplicativo de gerenciamento de ordem de venda Windows Presentation Foundation (WPF) extensíveis usando esse modelo. Este aplicativo permite que os clientes adicionar novos modos de exibição personalizados em seus ambientes simplesmente ao implantar um binário para a pasta bin. Veremos como isso pode ser implementado por meio de MEF. Eu incrementalmente irá melhorar o design como vou e explique mais sobre os recursos do MEF e o que fornece o modelo de programação atribuído no processo.

Exportação de uma classe

O aplicativo de gerenciamento de ordem permite conectar novos modos de exibição. Para exportar algo para MEF, você exportá-lo usando o atributo de exportação conforme mostrado aqui:

[Export]
public partial class SalesOrderView : UserControl
{
public SalesOrderView()
  {
InitializeComponent();
  }
}

A parte acima exporta o contrato SalesOrderView. Por padrão, o atributo de exportação usa o tipo concreto do membro (neste caso, a classe) como o contrato. Você pode especificar explicitamente o contrato, passando um parâmetro para construtor o atributo do.

Importando através de propriedades e campos

Partes atribuídas podem expressar as coisas que precisam, usando o atributo de importação em uma propriedade ou campo. O aplicativo exporta uma parte ViewFactory, que podem usar outras peças para obter modos de exibição. Que ViewFactory importa SalesOrderView usando uma importação de propriedade. Importar uma propriedade significa simplesmente decorando uma propriedade com um atributo import:

[Export]
public class ViewFactory
{
  [Import]
  public SalesOrderView OrderView { get; set; }
}

Importando através de construtores

Partes também podem importar por meio de construtores (normalmente conhecidos como injeção de construtor) usando o atributo ImportingConstructor conforme mostrado abaixo. Ao usar um construtor de importação, MEF assume que todos os parâmetros são importações, tornando desnecessária atributo import:

[Export]
public class ViewFactory
{
  [ImportingConstructor]
  public ViewFactory(SalesOrderView salesOrderView)
{
}
}

Em geral, importando via construtores, em vez de propriedades é uma questão de preferência, embora existam ocasiões em que é apropriado usar importações de propriedade, especialmente quando há partes que não são instanciados pelo MEF, como no exemplo de aplicativo do WPF. Recomposition também não é compatível com parâmetros do construtor.

Composição

Com SalesOrderView e ViewFactory in-loco, agora você pode começar a composição. Partes MEF automaticamente Don obter descobertos ou criados. Em vez disso, você precisa escrever um código de inicialização será aplicada a composição. Um lugar comum para fazer isso é no ponto de entrada do seu aplicativo, que nesse caso é a classe de aplicativo.

Para inicializar MEF envolve algumas etapas:

  • Adicione as importações dos contratos que você precisa criar o contêiner.
  • Crie um catálogo MEF usa para descobrir partes.
  • Crie um recipiente que compõe a instâncias de partes.
  • Compor chamando o método Composeparts no recipiente e passando a instância que tenha as importações.

Como você pode ver, adicionei uma importação ViewFactory na classe de aplicativo. Em seguida, criei um DirectoryCatalog apontando para a pasta bin e criou um contêiner que usa o catálogo. Por fim, chamei Composeparts, que causou uma instância do aplicativo seja composto e a importação ViewFactory sejam satisfeitos:

public partial class App : Application
{
  [Import]
public ViewFactory ViewFactory { get; set; }

public App()
  {
this.Startup += new StartupEventHandler(App_Startup);
  }

void App_Startup(object sender, StartupEventArgs e)
  {
var catalog = new DirectoryCatalog(@".\");
var container = new CompositionContainer(catalog);
container.Composeparts(this);
  }
}

Durante a composição, o recipiente criará o ViewFactory e satisfazer sua importação SalesOrderView. Isso resultará em SalesOrderView que está sendo criado. Finalmente, a classe Application terá sua importação ViewFactory satisfeita. Dessa forma, MEF tem montados no gráfico de objeto inteiro com base nas informações declarativas, em vez de exigir manualmente código imperativo para fazer o assembly.

Exportando itens não MEF para MEF através de propriedades

Ao integrar MEF em um aplicativo existente ou com outras estruturas, você encontrará com freqüência não-relacionado MEF (o que significa que eles não são partes) instâncias de classe que você deseja disponibilizar para importadores. Podem ser tipos de estrutura lacrado, como System.String; singletons de todo o aplicativo, como Application.Current; ou instâncias recuperados de uma fábrica, como uma instância de agente de log recuperado do Log4Net.

Para dar suporte a isso, MEF permite exportações de propriedade. Para usar a propriedade exportações, você cria uma parte intermediária com uma propriedade que está decorada com uma exportação. Que a propriedade é essencialmente uma fábrica e executa qualquer lógica personalizada são necessário para recuperar o valor não MEF. O exemplo de código a seguir, você pode ver Loggerpart exporta um agente de log Log4Net, permitindo que outras partes, como o aplicativo para importá-lo em vez de com em acessando o método do acessador estático:

public class Loggerpart
{
  [Export]
public ILog Logger
  {
get { return LogManager.GetLogger("Logger"); }
  }
}

Propriedade exportações são como facas exército suíço na sua funcionalidade, permitindo MEF jogar bem com outras pessoas. Você irá encontrá-las extremamente útil para integrar MEF em seus aplicativos existentes e para comunicar-se com sistemas herdados.

Implementação com uma interface de separação

Voltando ao exemplo SalesOrderView, ter sido formada uma relação estreita entre ViewFactory e SalesOrderView. A fábrica espera um SalesOrderView concreta que limita as opções de extensibilidade, assim como a capacidade de teste da fábrica propriamente dito. MEF permite importações seja dissociado da implementação do exportador usando uma interface como o contrato:

public interface ISalesOrderView{}

[Export(typeof(ISalesOrderView))]
public partial class SalesOrderView : UserControl, ISalesOrderView
{
   ...
}

[Export]
public class ViewFactory
{
  [Import]
ISalesOrderView OrderView{ get; set; }
}

O código anterior, alterei SalesOrderView implementar ISalesOrderView e exportá-lo explicitamente. Também alterei a fábrica do lado do importador para importar ISalesOrderView. Observe que o importador não precisa especificar o tipo explicitamente, como MEF pode ser obtidos-lo com o tipo de propriedade, que é ISalesOrderView.

Isso levanta a questão de se ViewFactory também deve implementar uma interface como IViewFactory. Isso não é um requisito, embora talvez faça sentido para fins de simulação. Nesse caso, estou esperando alguém substitua ViewFactory não e ele é projetado de maneira testável, por isso é bom. Você pode ter várias exportações em uma parte para que a parte importada em vários contratos. SalesOrderView, por exemplo, pode exportar UserControl e ISalesOrderView por ter um atributo de exportação adicionais:

[Export (typeof(ISalesOrderView))]
[Export (typeof(UserControl))]
public partial class SalesOrderView : UserControl, ISalesOrderView
{
   ...
}

Assemblies de contrato

À medida que você começa a criar contratos, você precisará de uma maneira de implantar esses contratos a terceiros. Uma maneira comum de fazer isso é fazendo com que um assembly de contrato que contém interfaces para os contratos que serão implementados pelo extensores. O conjunto do contrato torna-se um formulário do SDK as partes fará referência. Um padrão comum é nomear o conjunto de contrato como o nome do aplicativo + .Contracts como SalesOrderManager.Contracts.

Importando muitas exportações do mesmo contrato

No momento ViewFactory importa somente um modo de exibição. Codificar um membro (param de propriedade) para cada modo de exibição funciona para um número muito pequeno de tipos de modos de exibição que não estejam alterando freqüentemente predefinidos. No entanto, com a abordagem, adicionar novos modos de exibição requer a fábrica para ser recompilado.

Se muitos tipos de modos de exibição são esperados, MEF oferece uma abordagem melhor. Em vez de usar uma interface de modo de exibição específico, você pode criar uma interface genérica do IView que exporta todos os modos de exibição. A fábrica, em seguida, importa uma coleção de todos os IViews disponíveis. Para importar um conjunto no modelo atribuído, use o atributo ImportMany:

[Export]
public class ViewFactory
{
  [ImportMany]
IEnumerable<IView> Views { get; set; }
}

[Export(typeof(IView))]
public partial class SalesOrderView : UserControl, IView
{
}
//in a contract assembly
public interface IView{}

Aqui você pode ver que ViewFactory agora importa uma coleção de instâncias do IView em vez de um modo de exibição específico. SalesOrder implementa IView e exporta-vez ISalesOrderView. Esta refatoração ViewFactory agora oferece suporte um conjunto de modos de exibição aberto.

MEF também oferece suporte à importação usando coleções concretas como ObservableCollection <T>ou lista <T>, bem como coleções personalizadas que fornecem um construtor padrão.

Controlando a diretiva de criação da parte

Por padrão, todas as instâncias de parte do recipiente singletons, assim, eles são compartilhados por quaisquer partes importação-los dentro do contêiner. Por esse motivo, todos os importadores de SalesOrderView e ViewFactory terá a mesma instância. Em muitos casos isso é desejável, pois ele substitui ter membros estáticos que dependem de outros componentes. No entanto, às vezes, é necessário para cada importador para obter sua própria instância, por exemplo, para permitir várias instâncias de SalesOrderView ser exibido na tela ao mesmo tempo.

Diretiva de criação de parte no MEF pode ser um dos três valores: CreationPolicy.Shared CreationPolicy.NonShared ou CreationPolicy.Any. Para especificar a diretiva de criação em uma parte, você Decore-lo com o atributo partCreationPolicy, conforme mostrado aqui:

[partCreationPolicy(CreationPolicy.NonShared)]
[Export(typeof(ISalesOrderView))]
public partial class SalesOrderView : UserControl, ISalesOrdderView
{
public SalesOrderView()
  {
  }
}

É possível especificar PartCreationPolicy no lado do importador, definindo a propriedade RequiredCreationPolicy sobre a importação.

Distinguir exportações de metadados

ViewFactory agora funciona com um conjunto de modos de exibição aberto, mas não tenho nenhuma forma de distinguir um modo de exibição de outro. Eu poderia adicionar um membro a IView chamado ViewType, que o modo de exibição poderia fornecer e filtre contra essa propriedade. Uma alternativa é usar recursos de metadados de exportação do MEF para anotar o modo de exibição com seu ViewType. Usar os metadados fornece uma vantagem adicional de permitindo a instanciação do modo de exibição ser atrasada até que seja necessário, o que pode preservar os recursos e melhorar o desempenho.

Definindo exportar metadados

Para definir metadados uma exportação, você usa o atributo ExportMetadata. Abaixo, SalesOrderView foi alterado para exportar uma interface de marcador IView como seu contrato. Em seguida, ele adiciona metadados adicionais de “ ViewType ” para que possa ser localizado entre os modos de exibição de outros que compartilham o mesmo contrato:

[ExportMetadata("ViewType", "SalesOrder")]
[Export(typeof(IView)]
public partial class SalesOrderView : UserControl, IView
{
}

ExportMetadata tem dois parâmetros, uma chave que é uma seqüência de caracteres e um valor de tipo de objeto. Usar magic cadeias de caracteres como no exemplo anterior pode ser problemático porque isso não é segura para compilação. Em vez de uma seqüência de caracteres de mágica, pode fornecer uma constante para a chave e para o valor de enum:

[ExportMetadata(ViewMetadata.ViewType, ViewTypes.SalesOrder)]
[Export(typeof(IView)]
public partial class SalesOrderView : UserControl, IView
{
  ...
}
//in a contract assembly
public enum ViewTypes {SalesOrderView}

public class ViewMetadata
{
public const string ViewType = "ViewType";
}

Usando o atributo ExportMetadata oferece muita flexibilidade, mas existem várias advertências ao usá-lo:

  • Chaves de metadados não detectáveis no IDE. O autor da parte deve saber quais chaves de metadados e tipos são válidos para a exportação.
  • O compilador não valida metadados para garantir que esteja correto.
  • ExportMetadata adiciona mais ruído ao código, ocultando a intenção.

MEF oferece uma solução para abordar as questões acima: Exporta personalizada.

Atributos de exportação personalizado

MEF permite a criação de exportações personalizadas que incluem seus próprios metadados. Criar uma exportação personalizada envolve a criação de um ExportAttribute derivada que também especifica os metadados. Podemos usar exportações personalizadas para criar um atributo ExportView que contém metadados para ViewType:

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class ExportViewAttribute : ExportAttribute {
public ExportViewAttribute()
:base(typeof(IView))
  {}

public ViewTypes ViewType { get; set; }
}

ExportViewAttribute Especifica que ele exporta IView chamando construtor base da exportação. Ele está decorado com um MetadataAttribute, que especifica o atributo fornece metadados. Esse atributo instrui MEF examinar todas as propriedades públicas e criar metadados associados em exportação usando o nome da propriedade como a chave. Nesse caso, os metadados somente são ViewType.

A última coisa importante para a nota sobre o atributo ExportView é que ele está decorado com um atributo AttributeUsage. Especifica se o atributo é válido somente em classes e apenas um único atributo ExportView pode estar presente.

Em geral, AllowMultiple deve ser definido para false; se for true, que o importador será passado a uma matriz de valores em vez de um único valor. AllowMultiple deve ser deixado como verdadeiro quando há várias exportações com metadados diferentes do mesmo contrato no mesmo membro.

Aplicar ExportViewAttribute novo o SalesOrderView agora resultará no seguinte:

[ExportView(ViewType = ViewTypes.SalesOrder)]  
public partial class SalesOrderView : UserControl, IView
{
}

Como você pode ver exportações personalizadas garantir que metadados correto é fornecido para uma determinada exportação. Eles também reduzem ruído no código, são mais detectáveis através do IntelliSense e melhor expressam intenção por meio de sendo específicos do domínio.

Agora que metadados tem sido definido no modo de exibição, o ViewFactory pode importá-lo.

Importando exportações lentas e acessar os metadados

Para permitir o acesso a metadados, MEF utiliza uma nova API do .NET Framework 4, System.Lazy <t>. Ele permite atrasar a instanciação de uma instância até que a propriedade do valor da lazy é acessada. Ainda mais MEF amplia lazy <t>com lazy <T,TMetadata> para permitir o acesso a metadados de exportação sem instanciar a exportação subjacente.

TMetadata é um tipo de modo de exibição de metadados. Um modo de exibição de metadados é uma interface que define propriedades somente leitura que correspondem às chaves nos metadados exportado. Quando a propriedade de metadados é acessada, MEF dinamicamente implementará TMetadata e definirá os valores de acordo com os metadados fornecidos da exportação.

Esta é a aparência de ViewFactory quando a propriedade View é alterada para importar usando lazy <T,TMetadata>:

[Export]
public class ViewFactory
{
  [ImportMany]
IEnumerable<Lazy<IView, IViewMetadata>> Views { get; set; }
}

public interface IViewMetadata
{
ViewTypes ViewType {get;}
}

Depois de uma coleção de exportações preguiçosos com metadados for importada, você pode usar o LINQ para filtrar em relação ao conjunto. O trecho de código a seguir, implementei um método GetViews em ViewFactory para recuperar todos os modos de exibição do tipo especificado. Observe que ela acessa a propriedade Value para fabricar as instâncias de modo real somente para os modos de exibição que correspondem ao filtro:

[Export]
public class ViewFactory
{
  [ImportMany]
IEnumerable<Lazy<IView, IViewMetadata>> Views { get; set; }

public IEnumerable<View> GetViews(ViewTypesviewType) {
return Views.Where(v=>v.Metadata.ViewType.Equals(viewType)).Select(v=>v.Value);
  }
}

Com essas alterações, ViewFactory agora detecta todas as exibições que estão disponíveis no momento em que a fábrica é composta pelo MEF. Se novas implementações de aparecem no recipiente ou catálogos após essa composição inicial, eles não serão vistos pelo ViewFactory, pois ele já foi composto. Além disso, mas MEF realmente evitará os modos de exibição seja adicionado ao catálogo, lançando uma CompositionException — ou seja, a menos que recomposition esteja habilitado.

Recomposition

Recomposition é um recurso do MEF que permite que partes automaticamente que suas importações atualizadas à medida que novo exportações correspondentes aparecem no sistema. Um cenário em que é útil recomposition é para o download de partes de um servidor remoto. SalesOrderManager pode ser alterado para que quando ele é iniciado, ele inicia um download de vários modos de exibição opcionais. Como os modos de exibição aparecem, eles serão exibidos na fábrica de modo de exibição. Para tornar o ViewFactory recomposable, definimos a propriedade AllowRecomposition no atributo ImportMany da propriedade Views como true, como mostrado aqui:

[Export]
public class ViewFactory
{
[ImportMany(AllowRecomposition=true)]
IEnumerable<Lazy<IView, IViewMetadata>> Views { get; set; }

public IEnumerable<View>GetViews(ViewTypesviewType) {
return Views.Where(v=>v.Metadata.ViewType.Equals(viewType)).Select(v=>v.Value);
  }
}

Quando recomposition ocorre, a coleção Views instantaneamente será substituída por uma nova coleção que contém o conjunto de modos de exibição atualizado.

Com recomposition habilitado, o aplicativo pode fazer o download de assemblies adicionais do servidor e adicioná-los ao contêiner. Você pode fazer isso por meio catálogos do MEF. MEF oferece vários catálogos, dois dos quais são recomposable. DirectoryCatalog, que você já viu, é aquele que é recomposto chamando seu método de atualização. Outro catálogo recomposable é AggregateCatalog, que é um catálogo de catálogos. Adicionar catálogos a ele usando a propriedade de coleção de catálogos, que inicia recomposition. O último catálogo que usarei é um AssemblyCatalog, que aceita um assembly no qual ele, em seguida, cria um catálogo. Figura 2 mostra um exemplo que ilustra como você pode usar esses catálogos juntos para download dinâmico.

Figura 2 Usando MEF catálogos para download dinâmico

void App_Startup(object sender, StartupEventArgs e)
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(newDirectoryCatalog((@"\.")));
var container = new CompositionContainer(catalog);
container.Composeparts(this);
base.MainWindow = MainWindow;
this.DownloadAssemblies(catalog);
}

private void DownloadAssemblies(AggregateCatalog catalog)
{
//asynchronously downloads assemblies and calls AddAssemblies
}

private void AddAssemblies(Assembly[] assemblies, AggregateCatalog catalog)
{
var assemblyCatalogs = new AggregateCatalog();
foreach(Assembly assembly in assemblies)
assemblyCatalogs.Catalogs.Add(new AssemblyCatalog(assembly));
catalog.Catalogs.Add(assemblyCatalogs);
}

O recipiente no Figura 2 é criada com um AggregateCatalog. Em seguida, ele tem um DirectoryCatalog adicionado a ele para capturar as partes na pasta bin locais. Catálogo agregado é passado para o método DownloadAssemblies, que faz o download de assemblies assincronamente e, em seguida, chama AddAssemblies. Esse método cria um novo AggregateCatalog, ao qual ele adiciona que AssemblyCatalogs para cada assembly de download. AddAssemblies adiciona AggregateCatalog contendo módulos (assemblies) para a agregação principal. O motivo pelo qual que ele adiciona dessa maneira é ter recomposition ocorrer em uma captura, em vez de várias vezes, que é o que aconteceria se adicionamos assembly catálogos diretamente.

Quando recomposition ocorre, a coleção é imediatamente atualizada. Dependendo do tipo de propriedade de coleção, o resultado é diferente. Se a propriedade for do tipo IEnumerable <t>, ele será substituído com uma nova instância. Se é uma coleção concreta que herda da lista <t>ou ICollection, MEF irá chamar limpar e em seguida adicionar para cada item. Em ambos os casos, ele significa que você terá de considerar a segurança do thread ao usar Recomposition. Recomposition não só está relacionado ao adiciona, ela também se relaciona à constante remoções. Se os catálogos são removidos do recipiente, essas partes também serão removidas.

Estáveis composição, rejeição e diagnósticos

Às vezes, uma parte pode especificar uma importação estiver ausente, pois ele não estiver presente no catálogo. Quando isso acontece, MEF impede que a parte a dependência ausente — ou qualquer coisa que depende dele — de serem descobertos. MEF faz isso para estabilizar o sistema e evitar falhas de tempo de execução que certamente ocorrerá se a parte foram criada.

Aqui, SalesOrderView foi alterado para importar um ILogger que não haja nenhuma instância de agente de log presente:

[ExportView(ViewType = ViewTypes.SalesOrder)]  
public partial class SalesOrderView : UserControl, IView
{
[Import]
public ILogger Logger { get; set; }
}

Porque há uma exportação ILogger não disponível, não aparecerão exportação da SalesOrderView para o recipiente. Isso não gera uma exceção; em vez disso SalesOrderView apenas será ignorada. Se você marcar a coleção de Views do ViewFactory, ele estará vazio.

Rejeição também irá acontecer em casos onde há várias exportações disponíveis para uma importação simples. Nesses casos, a parte que importa a exportação única é rejeitada:

[ExportView(ViewType = ViewTypes.SalesOrder)]  
public partial class SalesOrderView : UserControl, IView
{
[Import]
public ILogger Logger { get; set; }
}
 [Export(typeof(ILogger))]  
public partial class Logger1 : ILogger
{
}
 [Export(typeof(ILogger))]  
public partial class Logger2 : ILogger
{
}

No exemplo anterior, SalesOrderView serão rejeitadas porque existem várias implementações ILogger, mas uma implementação única é importada.MEF fornece recursos para permitir uma exportação padrão na presença de múltiplos.Para obter mais informações sobre isso, consulte codebetter.com/blogs/Glenn.Block/Archive/2009/05/14/Customizing-container-Behavior-Part-2-of-n-Defaults.aspx.

Você pode perguntar por que MEF não SalesOrderView criar e lançar uma exceção.Em um sistema extensível aberto, se MEF lança uma exceção, seria muito difícil para o aplicativo para lidar com ele, ou ter o contexto para saber o que fazer, porque a parte pode estar ausente ou a importação pode ser aninhada muito profundamente na composição.Sem manipulação adequada, o aplicativo seria um inválido, estado e inutilizável.MEF rejeita a parte, garantindo a estabilidade do aplicativo é mantido.Para obter mais informações sobre a composição estável, consulte: blogs.msdn.com/gblock/Archive/2009/08/02/stable-Composition-in-MEF-Preview-6.aspx.

Diagnóstico de rejeição

Rejeição é um recurso muito eficiente, mas às vezes pode ser difícil de diagnosticar, especialmente quando o gráfico de dependência inteira será rejeitado.No primeiro exemplo anterior, ViewFactory importa diretamente um SalesOrderView.Diga Let’s MainWindow importados ViewFactory e SalesOrderView será rejeitada.Em seguida, ViewFactory e MainWindow irão também obter rejeitadas.Uma pode ser pequena cabeça se isso ocorrer, como você sabe que MainWindow e ViewFactory realmente estão presentes; o motivo da rejeição é uma dependência ausente.

MEF não deixar você escuro.Para ajudar a diagnosticar esse problema, ele fornece rastreamento.No IDE, rejeição todas as mensagens são rastreadas para a janela de saída, embora eles também poderão ser rastreados a qualquer ouvinte de rastreamento válidos.Por exemplo, quando o aplicativo tenta importar MainWindow, as mensagens de rastreamento na Figura 3 irá ser saída.

Figura 3 Mensagens de rastreamento MEF

System.ComponentModel.Composition Warning: 1 : The ComposablepartDefinition 'Mef_MSDN_Article.SalesOrderView' has been rejected. The composition remains unchanged. The changes were rejected because of the following error(s): The composition produced a single composition error. The root cause is provided below. Review the CompositionException.Errors property for more detailed information.

1) No valid exports were found that match the constraint '((exportDefinition.ContractName == "Mef_MSDN_Article.ILogger") AndAlso (exportDefini-tion.Metadata.ContainsKey("ExportTypeIdentity") AndAlso "Mef_MSDN_Article.ILogger".Equals(exportDefinition.Metadata.get_Item("ExportTypeIdentity"))))', invalid exports may have been rejected.

Resulting in: Cannot set import 'Mef_MSDN_Article.SalesOrderView.Logger (ContractName="Mef_MSDN_Article.ILogger")' on part 'Mef_MSDN_Article.SalesOrderView'.
Element: Mef_MSDN_Article.SalesOrderView.logger (ContractName="Mef_MSDN_Article.ILogger") -->Mef_MSDN_Article.SalesOrderView -->TypeCatalog (Types='Mef_MSDN_Article.MainWindow, Mef_MSDN_Article.SalesOrderView, ...').

A saída do rastreamento mostra a causa raiz do problema: SalesOrderView requer um ILogger e um não puder ser localizado. Em seguida, podemos ver que rejeitando-causou a fábrica para ser rejeitadas e, em última análise, o MainWindow.

Inspecionando Parts no depurador

Pode ir um passo além e realmente inspecionar as partes disponíveis no catálogo, que discutiremos na seção sobre o host. In Figura 4 você pode ver na janela de inspeção as partes disponíveis (em círculos verdes), bem como ILogger necessário importar (no círculo azul).

image: Available Parts and Required ILogger Shown in a Watch Window

Figura 4 Parts disponíveis e necessário ILogger exibidas na janela de inspeção

Diagnosticando rejeição at the Command Line

Um dos objetivos do MEF era suporte analyzability estático, permitindo a composição a serem analisados fora do ambiente de tempo de execução. Nós ainda Don possui tais ferramentas suporte no Visual Studio, porém Nicholas Blumhardt criados MEFX.exe (MEF.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=33536), uma ferramenta de linha de comando que faz a rodada. MEFX analisa os assemblies e determina quais partes estão sendo rejeitadas e por quê.

Se você executar MEFX.exe na linha de comando, você irá ver um grande número de opções; pode listar específicas importações, exportações ou todas as partes disponíveis. Por exemplo, aqui você pode ver usando MEFX para exibir a lista de peças:

C:\mefx>mefx.exe /dir:C:\SalesOrderManagement\bin\debug /parts 
SalesOrderManagement.SalesOrderView
SalesOrderManagement.ViewFactory
SalesOrderManagement.MainWindow

Isso é útil para obter um inventário de parte, mas MEFX pode também rastrear rejeições, que é o nosso interesse aqui, como mostra a Figura 5.

Figura 5 Controle para baixo de rejeições com MEFX.exe

C:\mefx>mefx.exe /dir:C:\SalesOrderManagement\bin\debug /rejected /verbose 

[part] SalesOrderManagement.SalesOrderView from: DirectoryCatalog (Path="C:\SalesOrderManagement\bin\debug")
  [Primary Rejection]
  [Export] SalesOrderManagement.SalesOrderView (ContractName="SalesOrderManagement.IView")
  [Export] SalesOrderManagement.SalesOrderView (ContractName="SalesOrderManagement.IView")
  [Import] SalesOrderManagement.SalesOrderView.logger (ContractName="SalesOrderManagement.ILogger")
    [Exception] System.ComponentModel.Composition.ImportCardinalityMismatchException: No valid exports were found that match the constraint '((exportDefinition.ContractName == "SalesOrderManagement.ILogger") AndAlso (exportDefinition.Metadata.ContainsKey("ExportTypeIdentity") AndAlso "SalesOrderManagement.ILogger".Equals(exportDefinition.Metadata.get_Item("ExportTypeIdentity"))))', invalid exports may have been rejected.
at System.ComponentModel.Composition.Hosting.ExportProvider.GetExports(ImportDefinition definition, AtomicCompositionatomicComposition)
at System.ComponentModel.Composition.Hosting.ExportProvider.GetExports(ImportDefinition definition)
at Microsoft.ComponentModel.Composition.Diagnostics.CompositionInfo.AnalyzeImportDefinition(ExportProvider host, IEnumerable`1 availableparts, ImportDefinition id)

Dissecando a saída em Figura 6 revela a causa raiz do problema: ILogger não pode ser localizado. Como você pode ver, em sistemas grandes com várias partes, MEFX é uma ferramenta inestimável. Para obter mais informações sobre MEFX, consulte blogs.msdn.com/nblumhardt/Archive/2009/08/28/ANALYZE-MEF-assemblies-from-the-Command-line.aspx.

image: A Part Example in IronRuby

Figura 6 Um exemplo de Part IronRuby

Resumindo, existem diversas vantagens do modelo atribuído:

  • Ele fornece uma maneira universal para partes declarar suas exportações e importa.
  • Ele permite que os sistemas detectar dinamicamente partes disponíveis em vez de exigir preregistration.
  • É estaticamente analyzable, permitindo que ferramentas como MEFX para determinar falhas antes do tempo.

Agora eu será faça um tour rápido da arquitetura e ver o que possibilita. Em um alto nível, arquitetura do MEF é dividida em camadas: Modelos de programação, hospedagem e primitivos.

Modelos de programação revisitados

O modelo atribuído é simplesmente uma implementação desses primitivos que usa atributos como o meio da detecção. Os primitivos podem representar partes não atribuído ou até mesmo partes que não são digitados estaticamente, como no DLR (Runtime de linguagem dinâmica). In Figura 6Você pode ver uma parte do IronRuby que exporta um IOperation . Observe que ele usa nativo sintaxe do IronRuby para declarar uma parte em vez do modelo atribuído, como atributos não são suportados no DLR.

MEF não é fornecido com um modelo de programação IronRuby, embora seja provável que adicionaremos suporte ao idioma dinâmico no futuro.

Você pode ler mais sobre experiências na criação de um modelo de programação Ruby nesta série de blog: blogs.msdn.com/nblumhardt/archive/tags/Ruby/default.aspx.

Hospedagem: Onde acontece a composição

Modelos de programação definir partes, importações e exportações. Para criar, na verdade, instâncias e gráficos de objetos, MEF fornece APIs de hospedagem que principalmente estão localizadas no namespace System.ComponentModel.Composition.Hosting. A camada de hospedagem oferece muita flexibilidade, capacidade de configuração e extensibilidade. É o local onde grande parte do “ trabalho ” MEF é feito e onde começa a descoberta no MEF. A maioria das pessoas que simplesmente criar partes nunca serão toque neste namespace. Se você for um hoster, no entanto, você estará usando-os como fiz anteriormente para composição de inicialização.

Os catálogos oferecem definições de parte (ComposablepartDefinition) que descrevem as exportações disponíveis e importações. Eles são a unidade principal para descoberta no MEF. MEF fornece vários catálogos no namespace System.ComponentModel.Composition, que alguns dos quais você já viu, incluindo DirectoryCatalog, que examina um diretório; AssemblyCatalog, que examina um assembly; e TypeCatalog, que examina por meio de um conjunto específico de tipos. Cada um desses catálogos é específica para o modelo de programação atribuído. AggregateCatalog, no entanto, é independente modelos de programação. Catálogos herdam ComposablepartCatalog e são um ponto de extensibilidade no MEF. Catálogos personalizados têm vários usos, de fornecer um modelo de programação totalmente novo para quebra e filtrando catálogos existentes.

Figura 7 mostra um exemplo de um catálogo filtrado, que aceita um predicado para filtrar o catálogo interno dos quais partes serão retornados.

Figura 7 Um catálogo filtrado

public class FilteredCatalog : ComposablepartCatalog, 
{
private readonly composablepartcatalog _inner;
private readonly IQueryable<ComposablepartDefinition> _partsQuery;

public FilteredCatalog(ComposablepartCatalog inner,
Expression<Func<ComposablepartDefinition, bool>> expression)
  {
      _inner = inner;
    _partsQuery = inner.parts.Where(expression);
  }

public override IQueryable<ComposablepartDefinition> parts
  {
get
      {
return _partsQuery;
      }
  }
}

Compõe o CompositionContainer, que significa que ele cria partes e satisfaça importações dessas partes. Em que satisfaz as importações, ela irá capturar do pool de exportações disponíveis. Se essas exportações também possui importações, o recipiente irá satisfazê-los primeiro. Dessa forma, o recipiente monta gráficos de objeto inteiro sob demanda. A principal fonte para o pool de exportações é um catálogo, mas o contêiner também pode ter instâncias existentes da parte diretamente adicionado a ele e composto. É habitual adicionar manualmente a classe de ponto de entrada ao contêiner, combinado com peças retiradas do catálogo, embora na maioria dos casos partes virá do catálogo.

Recipientes também podem ser aninhadas em uma hierarquia para oferecer suporte a cenários de escopo. Os recipientes filho por padrão consultará o pai, mas eles também podem fornecer seus próprios catálogos de partes filho, que serão criadas no recipiente filho:

var catalog = new DirectoryCatalog(@".\");
var childCatalog = new DirectoryCatalog(@".\Child\";
var rootContainer = new CompositionContainer(rootCatalog));
var childContainer = new CompositionContainer(childCatalog, 
rootContainer);

No código anterior, childContainer é organizado como um filho do rootContainer.Tanto rootContainer e childContainer fornecem seus próprios catálogos.Para obter mais informações sobre como usar o contêiner de host MEF nos aplicativos, consulte codebetter.com/blogs/Glenn.Block/Archive/2010/01/15/Hosting-MEF-WITHIN-your-Applications.aspx.

Primitivos: Onde estão Born Parts e modelos de programação

Os primitivos localizados em System.ComponentModel.Composition.Primitives são o nível mais baixo no MEF.Eles são universo quântico de MEF, por assim dizer, e sua extensibilidade on apontam.Até agora, abordei o modelo de programação atribuído.Recipiente do MEF, no entanto, em todos os não estiver acoplada a atributos, em vez disso, está acoplado para os primitivos.Os primitivos de definem uma representação abstrata de partes, que inclui definições como ComposablepartDefinition, ImportDefinition e ExportDefinition, bem como Composablepart e exportação, que representam instâncias reais.

Explorar os primitivos é uma jornada de si próprio e um será provavelmente pego em um artigo futuro.Por enquanto, você pode encontrar mais informações sobre ela em blogs.msdn.com/dsplaisted/Archive/2009/06/08/a-Crash-Course-on-the-MEF-Primitives.aspx.

MEF no Silverlight 4 — e diante

MEF também é fornecido como parte do Silverlight 4.Tudo o que abordei aqui é relevante para o desenvolvimento de aplicações ricas para Internet extensível.No Silverlight, nós já não existe mais ainda mais e introduzidos APIs adicionais para facilitar a experiência de criação de aplicativos no MEF.Esses aperfeiçoamentos serão eventualmente ser implementados no .NET Framework.

Você pode descobrir mais sobre MEF no Silverlight 4 esta postagem: codebetter.com/blogs/Glenn.Block/Archive/2009/11/29/MEF-has-landed-in-Silverlight-4-We-come-in-the-Name-of-Extensibility.aspx.

Eu tiver apenas uma pequena amostra do que você pode fazer com MEF.É uma ferramenta eficiente, robusta e flexível que você pode adicionar a seu arsenal de ajudar a abrir seus aplicativos para um mundo inteiramente novo de possibilidades.Espero poder ver o que fazer com ele!

Glenn Blocké um para o novo Managed Extensibility Framework (MEF) no .NET Framework 4 PM.Antes de MEF, ele era um planejador de produto em patterns & practices responsável pelo Prism, assim como outras diretrizes de cliente.Block é um craque coração e passa uma boa parte de seu tempo disseminação desse DOM por meio de conferências e grupos, como o ALT.NET.Leia seu blog em codebetter.com/blogs/Glenn.Block.

Graças aos seguintes especialistas técnicos para revisar este artigo: Ward Bell, Nicholas Blumhardt, Krzysztof Cwalina, Andreas Håkansson, Krzysztof Kozmic, Phil Langeberg, Amanda Launcher, Jesse Liberty, Roger Pence, Clemens Szypierski, Mike Taulty, Micrea Trofin e Hamilton Verissimo