Контейнеры и поставщики экспорта MEF
31C# и .NET --- Основы .NET --- Контейнеры и поставщики экспорта MEF
Импорт частей осуществляется с помощью контейнеров. Типы хостинг-частей определены в пространстве имен System.ComponentModel.Composition.Hosting. Класс CompositionContainer — это контейнер частей. В конструкторе этого класса может быть присвоено несколько объектов ExportProvider, как и ComposablePartCatalog.
Каталоги — это источники частей, которые обсуждаются в следующей статье. Поставщики экспорта позволяют обращаться ко всем экспортам программно, с помощью перегруженных методов GetExport<T>(). Поставщик экспорта используется для доступа к каталогу, а сам CompositionContainer является поставщиком экспорта. Это позволяет вкладывать контейнеры друг в друга.
Части загружаются при вызове метода Compose() (если только не выбрана отложенная загрузка). До сих пор всегда применялся метод ComposePart(), как показано в следующем методеInitializeContainer():
container.Compose(calcImport);
Расширяющий метод ComposePart() определен классом AttributedModelServices. Этот класс предоставляет методы, которые используют атрибуты и рефлексию .NET для доступа к информации части и добавления частей к контейнеру. Вместо метода расширения может применяться метод Compose() класса CompositionContainer. Метод Compose() работает с классом CompositionBatch. Класс CompostionBatch может быть использован для определения того, какие части должны быть удалены из контейнера.
Методы AddPart() и RemovePart() имеют перегрузки, позволяющие добавлять снабженную атрибутами часть (calcImport — экземпляр класса CalculatorImport, имеющего атрибуты Import) либо часть, унаследованную от базового класса ComposablePart:
var batch = new CompositionBatch();
batch.AddPart(calcImport);
container.Compose(batch);
Приложение-хост WPFCalculator загружает два разных вида частей. Первая часть реализует интерфейс ICalculator и загружается в методе InitialiseContainer, который показан ранее. Части, реализующие интерфейс ICalculatorExtension, загружаются в методе RefreshExtensions. Метод Container.ComposeParts() получает импорт из класса CalculatorExtensionImport и подключает его ко всем доступным импортам из каталога.
Поскольку здесь динамически загружается несколько дополнений (пользователь может выбрать нужное), элемент управления Menu динамически расширяется. Приложение выполняет итерацию по всем расширениям калькулятора и для каждого создается экземпляр MenuItem. Заголовок элемента управления MenuItem содержит элемент Label со свойством Title расширения и CheckBox. Элемент CheckBox позднее будет применяться для динамического удаления экспорта.
Свойство ToolTip класса MenuItem устанавливается равным свойству Description расширяющего элемента управления. Свойство Tag устанавливается в сам элемент-расширитель, что облегчает получение элемента-расширителя при выборе пункта меню. Событие Click элемента MenuItem устанавливается в метод ShowAddIn(). Этот метод вызывает метод GetUI() расширяющего элемента управления для отображения элемента внутри нового TabItem контейнера TabControl:
private void RefreshExensions()
{
catalog.Refresh();
calcExtensionImport = new CalculatorExtensionImport();
calcExtensionImport.ImportsSatisfied += (sender, e) =>
{
this.textStatus.Text += String.Format("{0}\n", e.StatusMessage);
};
container.ComposeParts(calcExtensionImport);
menuAddins.Items.Clear();
foreach (var extension in calcExtensionImport.CalculatorExtensions)
{
var menuItemHeader = new StackPanel { Orientation = Orientation.Horizontal };
menuItemHeader.Children.Add(new Label { Content = extension.Title });
var menuCheck = new CheckBox { IsChecked = true };
menuCheck.Unchecked += (sender1, e1) =>
{
MenuItem mi = (sender1 as CheckBox).Tag as MenuItem;
ICalculatorExtension ext = mi.Tag as ICalculatorExtension;
ComposablePart part = AttributedModelServices.CreatePart(ext);
var batch = new CompositionBatch();
batch.RemovePart(part);
container.Compose(batch);
MenuItem parentMenu = mi.Parent as MenuItem;
parentMenu.Items.Remove(mi);
};
menuItemHeader.Children.Add(menuCheck);
var menuItem = new MenuItem { Header = menuItemHeader, ToolTip = extension.Description, Tag = extension };
menuCheck.Tag = menuItem;
menuItem.Click += ShowAddIn;
menuAddins.Items.Add(menuItem);
}
}
private void ShowAddIn(object sender, RoutedEventArgs e)
{
var mi = e.Source as MenuItem;
var ext = mi.Tag as ICalculatorExtension;
FrameworkElement uiControl = ext.GetUI();
var headerPanel = new StackPanel { Orientation = Orientation.Horizontal };
headerPanel.Children.Add(new Label { Content = ext.Title });
var closeButton = new Button { Content = "X" };
var ti = new TabItem { Header = headerPanel, Content = uiControl };
closeButton.Click += delegate
{
tabExtensions.Items.Remove(ti);
};
headerPanel.Children.Add(closeButton);
tabExtensions.SelectedIndex = tabExtensions.Items.Add(ti);
}
Для динамического удаления экспорта устанавливается событие Unchecked в элементе управления CheckBox, расположенного внутри заголовка MenuItem. Экземпляр ICalculatorExtension доступен через свойство Tag элемента управления MenuItem, потому что он был установлен в предыдущем фрагменте кода. Чтобы удалить часть из контейнера, должен быть создан объект CompositeBatch, содержащий части для удаления. Метод RemovePart() класса CompositeBatch принимает объект ComposablePart; не предусмотрено перегрузки для объекта с атрибутами. С помощью класса AttributedModelServices из объекта с атрибутами методом CreatePart() может быть создан экземпляр ComposablePart. Объект CompositeBatch затем передается в метод Compose() экземпляра CompositionContainer для удаления из него части.