воскресенье, 14 марта 2010 г.

Введение в Managed Extensibility Framework

Автор: Derek Greer
Оригинал: http://www.aspiringcraftsman.com/2008/10/introduction-to-managed-extensibility_799/

Введение

На протяжении многих лет приложения следуют тенденции к расширению основной функциональности за счет использования различных механизмов. От пакета приложений Microsoft Office до интернет браузеров, instance messenger, ... каждого приложения которое мы используем сегодня предлагает различные формы расширяемости. К несчастью, разработка расширяемых приложений на платформе .NET постоянно требует что бы команда разработчиков обеспечивала инфраструктуру для этого. Так было, до сих пор.
Microsoft разработал новую библиотеку Managed Extensibility Framework которая обеспечивает создание расширяемых приложений на платформе .NET. Ниже приводится введение в особенности  Managed Extensibility Framework.

Обзор

Инфраструктура MEF позволяет приложению легко расширятся  за счет возможности динамического связывания внутренних и подключенных (Add-in) компонентов вместе внутри контейнера, каталога и набора компонентов.
 

Контейнер

Контейнер отвечает за управление процессом создания и построения зависимостей между компонентами. В фреймворке он представлен типом CompositionContainer. 
Этот тип реализует интерфейс ICompositionService, который может самостоятельно добавляться в контейнер для явного использования компонентами во время работы приложения. 

Каталоги

Каталоги отвечают за обнаружение зависимостей между зарегистрированными компонентами. Все каталоги расширяют абстрактный базовый тип ComposablePartCatalog. MEF предоставляет четыре типа каталогов:
  • AssemblyCatalog1 - обеспечивает обнаружение компонентов в коллекции типов;
  • TypeCatalog2 - обеспечивает обнаружение компонентов в сборке;
  • DirectoryCatalog3 - обеспечивает обнаружение компонентов в каталоге файловой системы;
  • AggregateCatalog4 - обеспечивает возможность комбинации множества каталогов в один составной каталог.

Компоненты

Компоненты это логические элементы композиции MEF которые используются для инкапсуляции информации о типах используемых во время процесса сборки. Каждый компонент идентифицируется Контрактом и содержит список атрибутов Export и Import. Контракт выглядит как строка, которая используется для идентификации зависимостей между компонентами. Атрибут Export указывает на предоставляемые компонентами сервисы, а атрибут Import указывает на требуемые компонентами сервисы.

Пример приложения MefPad

Следующий пример демонстрирует простые особенности MEF через создание простого текстового редактора которое использует расширения как механизм сохранения файлов.

Шаг 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.
  • Добавьте следующий метод:
private void Compose()
        {
            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;

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);
    }
}
Этот интерфейс определяет типы которые будут использоваться классом MefPad для чтения и сохранения файлов на диск. Свойства Description и Extension будут использоваться для файлового диалога представленного пользователю, а метода Read() и Write() будут использоваться для открытия и сохранения соответственно.

Шаг 4 - Создание и импорт

  • Добавьте следующее свойство в класс MefPad:
[ImportMany()]
        public List<IFilePersistenceExtension> FilePersistenceExtensions { get; set; }

Это свойство представляет собой коллекцию типов реализующих IFilePersistenceExtension которые будут представлены как типы файловых операций. Атрибут [Import] используется для индикации того что компонент MefPad имеет зависимость от типов реализующих интерфейс IFilePersistenceExtension. Во время обнаружения любых компонентов с контрактом данного типа в каталоге, контейнер будет добавлять экземпляры компонентов в коллекцию FilePersistenceExtension.

Шаг 5 - Создание бизнес логики

  • Добавьте следующий метод в класс MefPad:
        T GetFileDialog<T>() where T : FileDialog, new()
        {
            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;
        }

Этот метод обеспечивает настройку поведения при конфигурировании диалогов открытия и сохранения представленных пользователю. Метод ForEach() используется для добавления типов файлов в фильтр диалога.

  • Замените обработчики событий 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;
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();
            }
        }
    }
}
Этот класс обеспечивает приложение MefPad возможностями по открытию и сохранению файлов.

  • Создайте новый класс EncryptedFilePersistenceExtension:
using System.ComponentModel.Composition;
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();
            }
        }
    }
}
Этот класс предоставляет приложению MefPad возможности по открытию и сохранению зашифрованных файлов.

Шаг 7 - Запуск приложения

  • Запустите приложение введите в текстовое поле любой текст.

  • В меню выберите пункт File -> Save
Тип сохранения представлен в ниспадающем списке выбором из двух пунктов: "Text file" и "Encrypted". Выбор любой из этих типов приведет в результате к тому что для сохранения введенного текста будет использоваться соответствующее расширение. Исходные коды примера можно скачать отсюда.

Заключение

Managed Extensibility Framework предоставляет разработчикам инструменты необходимые для легкого создания расширяемых .NET приложений. Более того, его будущее повсеместное использование как опции для обеспечения расширяемости предоставит легко доступное решение в разных проектах и компаниях. Используя MEF для своих нужд по расширяемости, вы сможете меньше фокусироваться на проблемах развития инфраструктуры и больше на создании хорошего программного обеспечения для своей компании.

notes



1 Ранее тип назывался AttributedAssemblyPartCatalog

2 ранее AttributedTypesPartCatalog

3 DirectoryPartCatalog

4 AggregatingComposablePartCatalog

Комментариев нет: