Оригинал: http://www.aspiringcraftsman.com/2008/10/introduction-to-managed-extensibility_799/
Введение
Обзор
Инфраструктура MEF позволяет приложению легко расширятся за счет возможности динамического связывания внутренних и подключенных (Add-in) компонентов вместе внутри контейнера, каталога и набора компонентов.Контейнер
Контейнер отвечает за управление процессом создания и построения зависимостей между компонентами. В фреймворке он представлен типом CompositionContainer.Этот тип реализует интерфейс ICompositionService, который может самостоятельно добавляться в контейнер для явного использования компонентами во время работы приложения.
Каталоги
- AssemblyCatalog1 - обеспечивает обнаружение компонентов в коллекции типов;
- TypeCatalog2 - обеспечивает обнаружение компонентов в сборке;
- DirectoryCatalog3 - обеспечивает обнаружение компонентов в каталоге файловой системы;
- AggregateCatalog4 - обеспечивает возможность комбинации множества каталогов в один составной каталог.
Компоненты
Пример приложения MefPad
Шаг 1 - Создание проекта и формы
- Создайте новый проект Windows Forms Application с именем "MefPad".
- Добавьте ссылку на сборку System.ComponentModel.Composition.dll.
- Переименуйте "Form.cs" в "MefPad.cs".
- Добавьте контрол MenuStrip пристыкованный к верху формы.
- Добавьте верхний пункт меню "File".
- Добавьте подпункт "Open" в меню "File".
- Двойным кликом по пункту "Open" создайте обработчик события "Click".
- Вернитесь в дизайнер формы и добавьте следующий подпункт "Save" в меню "File".
- Двойным кликом по пункту "Save" создайте обработчик события "Click".
- Вернитесь в дизайнер формы и добавьте контрол TextBox c именем "mainContentBox".
- Измените значение свойства Dock на "Fill".
На следующем рисунке изображен результат дизайна формы "MefPad".
Шаг 2 - Конфигурация MEF контейнера
- Добавьте следующий вызов Compose() в конструктор класса MefPad:
public MefPad()
{
InitializeComponent();
Compose();
}
- Добавьте пространство имен System.ComponentModel.Composition.
- Добавьте следующий метод:
{
var catalog = new DirectoryCatalog(".", "*Extensions.dll");
_container = new CompositionContainer(catalog);
_container.ComposeParts(this);
}
Этот метод использует DirectoryCatalog для поиска любых расширений MefPad. В данном случае каталог будет искать сборки в текущей директории запуска, имена которых оканчиваются на "Extensions.dll". Далее, создается экземпляр CompositionContainer c каталогом в качестве параметра конструктора. Затем в контейнер добавляется форма MefPad в качестве компонента. Это позволит контейнеру обнаруживать любые компоненты с атрибутами Export или Import в классе MefPad. И наконец, указываем контейнеру собрать все компоненты.
Шаг 3 - Объявление расширения
- Создадим новый интерфейс с именем IFilePersistenceExtension:
using System.IO;Этот интерфейс определяет типы которые будут использоваться классом MefPad для чтения и сохранения файлов на диск. Свойства Description и Extension будут использоваться для файлового диалога представленного пользователю, а метода Read() и Write() будут использоваться для открытия и сохранения соответственно.
namespace MefPad
{
public interface IFilePersistenceExtension
{
/// <summary>
/// Description of the file type.
/// </summary>
string Description { get; }
/// <summary>
/// The extension of the file type.
/// </summary>
string Extension { get; }
/// <summary>
/// Reads from an open stream.
/// </summary>
/// <param name="stream">An open <see cref="Stream"/></param>
/// <returns>character array of text read.</returns>
char[] Read(Stream stream);
/// <summary>
/// Writes to an open stream.
/// </summary>
/// <param name="stream">An open <see cref="Stream"/></param>
/// <param name="text">The text to be written to the stream</param>
void Write(Stream stream, char[] text);
}
}Шаг 4 - Создание и импорт
- Добавьте следующее свойство в класс MefPad:
[ImportMany()]
public List<IFilePersistenceExtension> FilePersistenceExtensions { get; set; }
Это свойство представляет собой коллекцию типов реализующих IFilePersistenceExtension которые будут представлены как типы файловых операций. Атрибут [Import] используется для индикации того что компонент MefPad имеет зависимость от типов реализующих интерфейс IFilePersistenceExtension. Во время обнаружения любых компонентов с контрактом данного типа в каталоге, контейнер будет добавлять экземпляры компонентов в коллекцию FilePersistenceExtension.Шаг 5 - Создание бизнес логики
- Добавьте следующий метод в класс MefPad:
T GetFileDialog<T>() where T : FileDialog, new()Этот метод обеспечивает настройку поведения при конфигурировании диалогов открытия и сохранения представленных пользователю. Метод ForEach() используется для добавления типов файлов в фильтр диалога.
{
var fileDialog = new T();
var fileTypesBuffer = new StringBuilder();
if (FilePersistenceExtensions != null)
FilePersistenceExtensions
.ForEach(x =>
fileTypesBuffer.Append(string.Format("{0}{1}|*{2}",
(fileTypesBuffer.Length == 0)
? null
: "|", x.Description,
x.Extension)));
fileDialog.Filter = fileTypesBuffer.ToString();
return fileDialog;
}
- Замените обработчики событий openToolStripMenuItem_Click и saveToolStripMenuItem_Click следующими методами:
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
Stream stream;
OpenFileDialog openFileDialog = GetFileDialog<OpenFileDialog>();
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
if ((stream = openFileDialog.OpenFile()) != null)
{
char[] content = FilePersistenceExtensions[openFileDialog.FilterIndex - 1].Read(stream);
mainContentTextBox.Text = new String(content);
stream.Close();
}
}
}
private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
Stream stream;
SaveFileDialog saveFileDialog = GetFileDialog<SaveFileDialog>();
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
if ((stream = saveFileDialog.OpenFile()) != null)
{
FilePersistenceExtensions[saveFileDialog.FilterIndex - 1].Write(stream, mainContentTextBox.Text.ToCharArray());
stream.Close();
}
}
}
Эти методы обработчиков событий Click для пунктов меню "Open" и "Save" соответственно. Метод openToolStripMenuItem_Click вызывает метод Read() экземпляра IFilePersistenceExtension соответствующего выбранному пункту фильтра. Аналогично с методом saveToolStripMenuItem_Click вызывающего метод Write() экземпляра IFilePersistenceExtension соответствующего выбранному пункту фильтра.Шаг 6 - Создание Extension
- Создайте новый проект ClassLibrary с именем MefPadExtensions.
- Добавьте ссылку на проект MefPad.
- Добавьте ссылку на сборку System.ComponentModel.Composition.dll.
- В разделе "Build" настроек проекта измените свойство "Ouput path" на "..\MefPad\bin\Debug".
- Создайте новый класс TextFilePersistenceExtension:
using System.ComponentModel.Composition;Этот класс обеспечивает приложение MefPad возможностями по открытию и сохранению файлов.
using System.IO;
using MefPad;
namespace MEFPadExtensions
{
[Export(typeof(IFilePersistenceExtension))]
public class TextFileSaverExtension : IFilePersistenceExtension
{
public string Description
{
get { return "Text file"; }
}
public string Extension
{
get { return ".txt"; }
}
public char[] Read(Stream stream)
{
var streamReader = new StreamReader(stream);
string bytes;
try
{
bytes = streamReader.ReadToEnd();
return bytes.ToCharArray();
}
finally
{
streamReader.Close();
stream.Close();
}
}
public void Write(Stream stream, char[] text)
{
var streamWriter = new StreamWriter(stream);
try
{
streamWriter.Write(text);
}
finally
{
streamWriter.Close();
stream.Close();
}
}
}
}
- Создайте новый класс EncryptedFilePersistenceExtension:
using System.ComponentModel.Composition;Этот класс предоставляет приложению MefPad возможности по открытию и сохранению зашифрованных файлов.
using System.IO;
using System.Security.Cryptography;
using System.Text;
using MefPad;
namespace MEFPadExtensions
{
[Export(typeof(IFilePersistenceExtension))]
public class EncryptedFilePersistenceExtension : IFilePersistenceExtension
{
readonly byte[] iv = Encoding.ASCII.GetBytes("ABCDEFGHIJKLMNOP");
readonly byte[] key = Encoding.ASCII.GetBytes("ABCDEFGHIJKLMNOP");
public string Description
{
get { return "Encrypted"; }
}
public string Extension
{
get { return ".crypt"; }
}
public char[] Read(Stream stream)
{
Rijndael rijndael = Rijndael.Create();
var cryptoStream = new CryptoStream(stream,
rijndael.CreateDecryptor(key, iv),
CryptoStreamMode.Read);
var streamReader = new StreamReader(cryptoStream);
string bytes;
try
{
bytes = streamReader.ReadToEnd();
return bytes.ToCharArray();
}
finally
{
streamReader.Close();
cryptoStream.Close();
stream.Close();
}
}
public void Write(Stream stream, char[] text)
{
Rijndael rijndael = Rijndael.Create();
var cryptoStream = new CryptoStream(stream,
rijndael.CreateEncryptor(key, iv),
CryptoStreamMode.Write);
var streamWriter = new StreamWriter(cryptoStream);
try
{
streamWriter.Write(text);
}
finally
{
streamWriter.Close();
cryptoStream.Close();
stream.Close();
}
}
}
}Шаг 7 - Запуск приложения
- Запустите приложение введите в текстовое поле любой текст.
- В меню выберите пункт File -> Save