Visual Basic, .NET, ASP, VBScript
 

   
 
Описание для автора не найдено
 
     
   
 
Создание подключаемой инфраструктуры
Автор: Рой Ошеров, N/A
Перевод: Шатохина Надежда(sna@uneta.org), Ukraine .Net Alliance (http://www.uneta.org/)
Декабрь 2003
Применяется к:
  • Microsoft® ASP.NET
  • Microsoft® Visual Studio .NET 2003
  • Обзор:
    здесь показано, как добавить поддержку подключаемых модулей (plug-in) в ваши .NET-приложения и представлена инфраструктура, которую вы можете использовать для добавления этой функциональной возможности.
    Содерджание:
  • Зачем вашему приложению нужна подключаемая инфраструктура?
  • Шаг 1. Создание простого текстового редактора
  • Шаг 2. Создание подключаемого SDK
  • Шаг 3. Создание вашего специального подключаемого модуля
  • Шаг 4. Заставьте ваше приложение знать о новом подключаемом модуле
  • Шаг 5. Синтаксический анализ файла конфигурации с использованием IConfigurationSectionHandler
  • Создание экземпляров подключаемых модулей и их инициализация
  • Инициализация подключаемых модулей
  • Заключение
  • Зачем вашему приложению нужна подключаемая инфраструктура?

    Люди, как правило, добавляют поддержку подключаемых модулей в свои приложения по следующим причинам:

    • Чтобы расширить функциональные возможности приложения без необходимости перекомпиляции или повторного распространения его среди заказчиков.
    • Чтобы добавить функциональные возможности без необходимости доступа к оригинальному исходному коду.
    • Бизнес-правила для приложения меняются часто или часто добавляются новые правила.

    В этой статье вы создадите очень простой текстовый редактор, состоящий всего из одной формы. Все, что он сможет делать — выводить на экран текст в единственном текстовом окне в центре формы. Как только будет готово это приложение, вы создадите простой подключаемый модуль и добавите его в приложение. Этот подключаемый модуль сможет читать текст, находящийся в настоящее время в текстовом окне, проводить его синтаксический разбор в поисках действительных адресов электронной почты и возвращать строку, содержащую только эти адреса. Затем вы поместите этот текст в текстовое окно.

    Как видите, в этом учебном примере есть несколько «неизвестных»:

    • Как вы найдете подключаемый модуль из приложения?
    • Откуда подключаемый модуль знает, какой текст находится в текстовом окне?
    • Как вы активируете этот подключаемый модуль?

    Ответы на все эти вопросы появятся, когда мы создадим решение.

    Шаг 1. Создание простого текстового редактора

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

    Шаг 2. Создание подключаемого SDK

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

    Решение в том, чтобы сориентировать работу приложения на опубликованный интерфейс, набор общих членов и методов, которые будут реализовываться всеми специальными подключаемыми модулями. Я назову этот интерфейс IPlugin. С этих пор любой разработчик, который захочет создать подключаемый модуль для вашего приложения, должен будет реализовать этот интерфейс. Он будет располагаться в общей библиотеке, которую будут использовать и ваше приложение, и любой специальный подключаемый модуль.

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

    public interface IPlugin
    {
       string Name{get;}
       void PerformAction(IPluginContext context);
    }
    

    Код прост, но зачем передавать интерфейс IPluginContext в PerformAction? Причина, по которой вы посылаете интерфейс, а не просто строку — обеспечение большей гибкости в отношении того, какой объект вы сможете посылать. В настоящее время, интерфейс очень прост:

    public interface IPluginContext
    {
       string CurrentDocumentText{get;set;}
    }
    

    Теперь все, что вам надо сделать — реализовать этот интерфейс в одном или более объектов и отправить их в любой подключаемый модуль для получения результата. В будущем это позволит вам изменять строку не только текстового окна, но любого объекта.

    Шаг 3. Создание вашего специального подключаемого модуля

    Все, что вам сейчас надо сделать:

    • Создать отдельный объект библиотеки классов.
    • Создать класс, который реализовывает интерфейс IPlugin.
    • Откомпилировать этот класс и поместить его в ту же папку, к которой находится главное приложение.

    public class EmailPlugin:IPlugin
    {
       public EmailPlugin()
       {
       }
       // Единственная точка для входа в ваш подключаемый модуль
       // Принимает объект IPluginContext,
       // в котором находится текущее
       // содержимое редактора.
       // Затем он проводит синтаксический разбор текста, обнаруженного в 
       // редакторе, и оставляет в нем только обнаруженные электронные адреса.
       public void PerformAction(IPluginContext context)
       {
          context.CurrentDocumentText=
                 ParseEmails(context.CurrentDocumentText);
       }
     
       // Имя подключаемого модуля, которое появится
       // в меню «Подключаемые модули» («Plugins») редактора
       public string Name
       {
          get
          {
             return "Email Parsing Plugin";
          }
       }
    
       // Проводит синтаксический разбор данной строки в поисках любых адресов
       // электронной почты, используя класс Regex,
       // и возвращает строку, содержащую только адреса электронной почты
       private string ParseEmails(string text)
       {
          const string emailPattern= @"\w+@\w+\.\w+((\.\w+)*)?";
          MatchCollection emails = 
                   Regex.Matches(text,emailPattern,
                   RegexOptions.IgnoreCase);
          StringBuilder emailString = new StringBuilder();
          foreach(Match email in emails)
          {
             emailString.Append(email.Value + Environment.NewLine);
          }
    
          return emailString.ToString();
       }
    }
    
    Шаг 4. Заставьте ваше приложение знать о новом подключаемом модуле

    Когда вы скомпилировали ваш подключаемый модуль, как приложение узнает о его существовании?

    Решение простое:

    • Создайте файл конфигурации приложения.
    • Создайте секцию в файле конфигурации, в которой перечислены все существующие подключаемые модули.
    • Создайте синтаксический анализатор для этой секции файла конфигурации.

    Чтобы завершить первый шаг, просто добавьте XML-файл в главное приложение.

    Комментарий: Назовите этот файл App.Config. В этом случае при каждой сборке вашего приложения Microsoft® Visual Studio .NET автоматически будет копировать этот файл в выходную папку и переименовывать его в <yourApp>.Config, избавляя вас от хлопот.

    Теперь разработчик подключаемого модуля должен иметь возможность легко добавить данные в файл конфигурации, чтобы опубликовать все созданные подключаемые модули. Здесь показано, как должен выглядеть файл конфигурации:

    <configuration>
    <configSections>
          <section name="plugins"
                type="Royo.PluggableApp.PluginSectionHandler, PluggableApp"
                />
    </configSections>
       <plugins>
          <plugin type="Royo.Plugins.Custom.EmailPlugin, CustomPlugin" />
       </plugins>
    </configuration>
    

    Обратите внимание на тэг configSections. Он сообщает настройкам конфигурации приложения, что у вас в этом файле конфигурации есть секция подключаемых модулей, и что у вас есть синтаксический анализатор для этой секции. Он находится в классе Royo.PluggableApp.PluginSectionHandler, который располагается в сборке PluggableApp. Ниже я покажу вам код этого класса.

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

    Закончив файл конфигурации, вы завершаете один из этапов этого цикла. Подключаемый модуль готов к использованию, и сообщил о своем существовании во все необходимые каналы. Все, что вам осталось теперь сделать — научить ваше приложение считывать эту информацию и создавать в соответствие с нею экземпляры опубликованных подключаемых модулей.

    Шаг 5. Синтаксический анализ файла конфигурации с использованием IConfigurationSectionHandler

    Чтобы провести синтаксический анализ подключаемых модулей, обнаруженных в файле .config приложения, инфраструктура предлагает очень простой механизм, позволяющий вам регистрировать определенный класса в качестве обработчика определенной части вашего файла конфигурации. У вас должен быть обработчик любой части файла, для которой инфраструктура не проводит автоматического синтаксического анализа; в противном случае, возникнет ConfigurationException.

    Чтобы обеспечить класс, проводящий синтаксический разбор секции подключаемых модулей, вам всего лишь надо реализовать интерфейс System.Configuration.IConfigurationSectionHandler. Сам по себе этот интерфейс очень прост:

    public interface IConfigurationSectionHandler
    {
       public object Create(object parent, object configContext, System.Xml.XmlNode section);
    }
    

    Все, что вам надо сделать — переопределить метод Create в своем специальном классе и провести синтаксический разбор XML-узла, предоставленного вам. Этот XML-узел, в данном случае, будет XML-узлом «Подключаемые модули» («Plugins»). После этого у вас есть вся информация, необходимая для создания экземпляра подключаемых модулей для вашего приложения.

    Ваш специальный класс должен поставлять стандартный конструктор, поскольку его экземпляр создается инфраструктурой автоматически во время выполнения, а затем вызывается его метод Create. Вот код для класса PluginSectionHandler:

    public class PluginSectionHandler:IConfigurationSectionHandler
    {
       public PluginSectionHandler()
       {
       }
       //   Пройдите по всем дочерним узлам
       //   XMLNode, который был передан вам, и создайте экземпляры
       //   заданных в значениях атрибутов узлов типов
       //   мы используем здесь try/Catch, потому что некоторые из узлов
       //   могут содержать неверные ссылки на тип подключаемого модуля
       public object Create(object parent, 
             object configContext, 
             System.Xml.XmlNode section)
       {
          PluginCollection plugins = new PluginCollection();
          foreach(XmlNode node in section.ChildNodes)
          {
             //Здесь располагается код для создания экземпляров
             // подключаемых модулей и их инициализации 
             .
             .
             .
          }
          return plugins;
       }
    }
    

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

    <configuration>
    <configSections>
       <section name="plugins"
          type="Royo.PluggableApp.PluginSectionHandler, PluggableApp"
       />
    </configSections>
    .
    .
    .
    

    Обратите внимание, как определять класс. Строка состоит из двух частей: полного имени класса (включая пространство имен), запятой, имени сборки, в которой размещается этот класс. Это все, что нужно инфраструктуре для создания экземпляра класса, и не удивительно, что именно эта информация нужна и при регистрации любого подключаемого модуля для вашего приложения.

    Создание экземпляров подключаемых модулей и их инициализация

    Итак, как на самом деле вы будете создавать экземпляр подключаемого модуля, приведенного в этой строке?

    String ClassName = "Royo.Plugins.MyCustomPlugin, MyCustomPlugin"
    IPlugin plugin =  (IPlugin )Activator.CreateInstance(Type.GetType(ClassName));
    

    Здесь происходит следующее: поскольку ваше приложение не делает прямых ссылок на сборку специального подключаемого модуля, вы используете класс System.Activator. Activator — это специальный класс, который может создавать экземпляры объекта, заданного с любым количеством определенных параметров. Он даже может создавать экземпляры объектов и возвращать их. Если вы когда-нибудь писали код в ASP или Microsoft® Visual Basic®, вы должны помнить функцию CreateObject(), которая использовалась для создания экземпляров и возвращения объектов на основании CLSID класса. Activator действует по той же схеме: использует различные аргументы и возвращает экземпляр System.Object.

    В этом обращении к Activator вы передаете в качестве параметра Type, экземпляр которого хотите создать. Используйте метод Type.GetType() для возвращения экземпляра Type, который соответствует Type подключаемого модуля. Обратите внимание, что метод Type.GetType() в качестве параметра принимает именно ту строку, которая была помещена в тэг подключаемых модулей, которая описывает имя класса и сборку, в которой он находится.

    Создав экземпляр подключаемого модуля, приведите его к интерфейсу IPlugin и поместите его в объект вашего подключаемого модуля. Здесь должен присутствовать блок Try-Catch, поскольку вы не можете быть уверенными, что описанный там подключаемый модуль существует на самом деле или действительно поддерживает необходимый вам интерфейс IPlugin.

    Создав экземпляр подключаемого модуля, добавьте его в ArrayList подключаемых модулей вашего приложения и переходите к следующему XML-узлу.

    Вот код приложения:

    public object Create(object parent, 
        object configContext, 
        System.Xml.XmlNode section)
    {
       //Происходит от CollectionBase
       PluginCollection plugins = new PluginCollection();
       foreach(XmlNode node in section.ChildNodes)
       {
          try
          {
             //Используйте метод 'CreateInstance' класса Activator
             //при попытке создать экземпляр подключаемого модуля,
             //передавая в него имя типа, определенного в значении атрибута
             object plugObject = 
                       Activator.CreateInstance(Type.GetType(node.Attributes["type"].Value));
    
             //Приведите это к интерфейсу IPlugin и добавьте в коллекцию
             IPlugin plugin = (IPlugin)plugObject;
             plugins.Add(plugin);
          }
          catch(Exception e)
          {
             //Регистрируйте все возникающие исключения,
             //но продолжайте перебор в поисках подключаемых модулей
          }
       }
       return plugins;
    }
    
    Инициализация подключаемых модулей

    Сделав все это, вы можете использовать подключаемые модули. Однако кое-что еще мы пропустили. Помните, что IPlugin.PerformAction() требует аргумент типа IPluginContext, в котором находятся все необходимые для работы подключаемого модуля данные. вы реализуете простой класс, реализовывающий этот интерфейс, который вы посылаете в метод PerformAction() всякий раз при вызове подключаемого модуля. Вот код класса:

    public interface IPluginContext
    {
       string CurrentDocumentText{get;set;}
    }
    
    public class EditorContext:IPluginContext
    {
       private string m_CurrentText= string.Empty;
       public EditorContext(string CurrentEditorText)
       {
          m_CurrentText = CurrentEditorText;
       }
    
       public string CurrentDocumentText
       {
    get{return m_CurrentText;}
          set{m_CurrentText = value;}
       }
    }
    

    Когда этот класс готов, вы можете просто осуществлять операции на текущим текстом редактора:

    private void ExecutePlugin(IPlugin plugin)
    {
       //создаем объект 'context' для передачи в подключаемый модуль
       EditorContext context = new EditorContext(txtText.Text);
       
       //Подключаемый модуль изменяет свойство 'Text' объекта 'context'
       plugin.PerformAction(context);
       txtText.Text= context.CurrentDocumentText;
       }
    }
    
    Заключение

    Как видите, обеспечить поддержку подключаемых модулей в своем приложении чрезвычайно просто. Вы только:

    • Создаете общую библиотеку интерфейсов.
    • Создаете специальные подключаемые модули, реализовывающие специальные интерфейсы.
    • Создаете контекстные аргументы для передачи их в подключаемые модули.
    • Создаете секцию в своем файле конфигурации для размещения имен подключаемых модулей.
    • Создаете экземпляры подключаемых модулей, используя IConfigurationSectionHandler.
    • Вызываете ваши подключаемые модули.
    • Отправляетесь домой и с пользой проводите время подальше от своего компьютера.

    Никакая часть настоящей статьи не может быть воспроизведена или передана в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механические, если на то нет письменного разрешения владельцев авторских прав.

    Материал, изложенный в данной статье, многократно проверен. Но, поскольку вероятность технических ошибок все равно существует, сообщество не может гарантировать абсолютную точность и правильность приводимых сведений. В связи с этим сообщество не несет ответственности за возможные ошибки, связанные с использованием статьи.
     
         

       
       
         
      VBNet рекомендует