Импорт MEF

37

Теперь давайте приступим к использованию пользовательских элементов управления WPF и приложения-хоста WPF. Представление визуального конструктора приложения-хоста показано на рисунке:

Пример приложения-калькулятора, являющегося хостом MEF
<Window x:Class="Wrox.ProCSharp.MEF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Wrox.ProCSharp.MEF"
        Title="Calculator" Height="240" Width="500">
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Close" Executed="OnClose" />
        <CommandBinding Command="local:CalculatorCommands.RefreshExtensions" Executed="OnRefreshExtensions" />
        <CommandBinding Command="local:CalculatorCommands.GetExports" Executed="OnGetExports" />
    </Window.CommandBindings>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="4*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Menu Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
            <MenuItem Header="_File">
                <MenuItem Header="E_xit" Command="ApplicationCommands.Close" />
            </MenuItem>
            <MenuItem x:Name="menuAddins" Header="_AddIns">
                
            </MenuItem>
            <MenuItem Header="Manage">
                <MenuItem Header="_Refresh Extensions" Command="local:CalculatorCommands.RefreshExtensions" />
                <MenuItem Header="Get _Exports" Command="local:CalculatorCommands.GetExports" />
            </MenuItem>
        </Menu>

        <Grid Grid.Row="1" Grid.Column="0" Margin="2">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Grid Grid.Column="0" Grid.Row="0" Grid.RowSpan="3" Button.Click="OnNumberClick">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition />
                    <RowDefinition />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <Button Grid.Row="0" Grid.Column="0" Content="7" Margin="1" />
                <Button Grid.Row="0" Grid.Column="1"  Content="8" Margin="1" />
                <Button Grid.Row="0" Grid.Column="2"  Content="9" Margin="1" />
                <Button Grid.Row="1" Grid.Column="0"  Content="4" Margin="1" />
                <Button Grid.Row="1" Grid.Column="1"  Content="5" Margin="1" />
                <Button Grid.Row="1" Grid.Column="2"  Content="6" Margin="1" />
                <Button Grid.Row="2" Grid.Column="0"  Content="1" Margin="1" />
                <Button Grid.Row="2" Grid.Column="1"  Content="2" Margin="1" />
                <Button Grid.Row="2" Grid.Column="2"  Content="3" Margin="1" />
                <Button Grid.Row="3" Grid.Column="0"  Content="0" Grid.ColumnSpan="2" Margin="1" />
                <Button Grid.Row="3" Grid.Column="2"  Content="." Margin="1" />
            </Grid>
            <ListBox Grid.Row="0" Grid.Column="1" x:Name="listOperators">
                
            </ListBox>
            <TextBlock x:Name="textInput" Grid.Row="1" Grid.Column="1" />
            <TextBlock x:Name="textResult" Grid.Row="2" Grid.Column="1" />
            <Button  Click="InvokeOperation" Content="Calculate" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="3" Margin="5" />
        </Grid>
        <GridSplitter Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Stretch" Width="5" ShowsPreview="True" />
        <TabControl x:Name="tabExtensions" Grid.Row="1" Grid.Column="1" Margin="2">
            
        </TabControl>
        <ScrollViewer Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
            <TextBlock ScrollViewer.VerticalScrollBarVisibility="Auto" x:Name="textStatus" />
        </ScrollViewer>
    </Grid>
</Window>

WPFCalculator — это приложение WPF, которое загружает функциональное дополнение-калькулятор, реализующее интерфейсы ICalculator и IOperation, а также дополнения с пользовательским интерфейсом, которые реализуют интерфейс ICalculatorExtension. Чтобы подключиться к экспортам частей, нужны импорты.

Импорт подключается к экспорту. При использовании экспортированных частей необходим импорт для установки соединения. С помощью атрибута Import можно подключиться к одному экспорту. Если должно загружаться более одного дополнения, необходим атрибут ImportMany, и он должен быть определен как массив типа IEnumerable<T>. Поскольку приложение-хост калькулятора допускает загрузку многих расширений калькуляторов, реализующих интерфейс ICalculatorExtension, в классе CalculatorExtensionImport определено свойство CalculatorExtensions типа IEnumerable<ICalculatorExtension> для доступа ко всем частям — расширениям калькуляторов:

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Windows.Controls;

namespace MEF
{
    public class CalculatorExtensionImport : IPartImportsSatisfiedNotification
    {
        public event EventHandler<ImportEventArgs> ImportsSatisfied;

        [ImportMany(AllowRecomposition=true)]
        public IEnumerable<ICalculatorExtension> CalculatorExtensions { get; set; }

        public void OnImportsSatisfied()
        {
            if (ImportsSatisfied != null)
                ImportsSatisfied(this, new ImportEventArgs { StatusMessage = "ICalculatorExtension imports successful" });

        }
    }
}

Атрибуты Import и ImportMany позволяют использовать ContractName и ContractType для отображения импорта на экспорт. Другие свойства, которые могут быть установлены с этими атрибутами — это AllowRecomposition и RequiredCreationPolicy.

Свойство AllowRecomposition допускает динамическое отображение на новые экспорты во время работы приложения, а также динамическую выгрузку экспортов. С помощью RequiredCreationPolicy можно выбирать, должна часть разделяться между запрашивающими сторонами (CreationPolicy.Shared) или нет(CreationPolicy.Nonshared), либо же политика в этом отношении определяется контейнером (CreationPolicy.Any).

Чтобы удостовериться, что все импорты успешны, можно реализовать интерфейс IPartImportsSatisfiedNotification.

Интерфейс определяет единственный метод — OnImportsSatifsifed(), который вызывается, когда все импорты класса успешны. В классе CalculatorImport метод инициирует событие ImportsSatisfied.

Событие CalculatorImport подключается к обработчику события при создании Calculatorlmport для записи сообщения в элемент TextBlock, который отображает информацию о состоянии:

private void InitializeContainer()
        {
            catalog = new DirectoryCatalog(Properties.Settings.Default.AddInDirectory);
            catalog.Changed += (sender, e) =>
                {
                    this.textStatus.Text += sb.ToString();
                };
            container = new CompositionContainer(catalog);
            InitializeOperations();
          }

По умолчанию части загружаются из контейнера, например, путем вызова расширяющего метода ComposeParts() класса CompositionContainer. С помощью класса Lazy<T> части могут быть загружены при первом к ним обращении. Тип Lazy<T> позволяет выполнять позднее создание экземпляров любого типа T и определяет свойства IsValueCreated и Value. Свойство IsValueCreated имеет булевский тип и возвращает информацию о том, создан ли уже внутренний экземпляр типа Т. Свойство Value инициализирует вложенный тип T при первом обращении и возвращает его экземпляр.

Импорт дополнений может быть объявлен, как относящийся к типу Lazy<T>, что показано на примере Lazy<ICalculator>:

[Import(typeof(ICalculator))]
public Lazy<ICalculator> Calculator { get; set; }

Вызов импортированных средств также требует некоторых изменений в доступе к свойству Value типа Lazy<T>. Переменная calcImport имеет тип CalculatorImport. Свойство Calculator возвращает Lazy<ICalculator>. Свойство Value отложенным образом создает экземпляр импортированного типа и возвращает интерфейс ICalculator, на котором может быть вызван метод GetOperations() для получения всех поддерживаемых дополнением калькулятора операций:

private void InitializeOperations()
{
            Contract.Requires(calcImport != null);
            Contract.Requires(calcImport.Calculator != null);

            var operators = calcImport.Calculator.Value.GetOperations();
            foreach (var op in operators)
            {
                var b = new Button();
                b.Width = 40;
                b.Height = 30;
                b.Content = op.Name;
                b.Margin = new Thickness(2);
                b.Padding = new Thickness(4);
                b.Tag = op;
                b.Click += new RoutedEventHandler(DefineOperation);
                listOperators.Items.Add(b);
            }

}
Пройди тесты
Лучший чат для C# программистов