COM+ и .NET – практический подход – часть 3

ОГЛАВЛЕНИЕ

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

Предыдущие части

•    Первая часть
•    Вторая часть

Первая часть рассматривала все стороны использования COM+. После ее прочтения вы узнаете, какие возможности есть для размещения сборки в COM+, влияния на производительность приложения ASP.NET и каковы, при наличии таковых, ограничения, налагаемые выбором определенного варианта размещения в COM+. Вторая часть описывала все управляющие преимущества, которые приложение может получить от COM+. Эта часть содержит подробности об улучшении доступности и стабильности с помощью COM+ и о том, кого надо отслеживать и как использовать данные слежения для быстрого и легкого обнаружения источников проблемы.

Уведомить других при изменении состояния приложения

Слабосвязанные события (LCE) – одно из самых полезных средств COM+. LCE позволяет компоненту COM+ инициировать событие, которое в итоге будет возбуждено для неизвестных клиентов. Клиенты должны зарегистрироваться как подписчики компонента события COM+ с помощью COM+ MMC или динамически. Издатель вызывает один из методов компонентов события COM+, вызывающих отправку и возбуждение события во всех подписчиках. Контракт между подписчиками компонента события и издателем является единственным интерфейсом, который должны реализовывать все стороны контракта.

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

Для имитации мощи LCE будет создан механизм, позволяющий веб-приложению получить уведомление при изменении определенной таблицы базы данных одним из веб-приложений или любым другим приложением. Для обеспечения этого механизма надо контролировать доступ всех приложений к базе данных. Эту задачу можно осуществить путем создания нового серверного приложения COM+, реализующего функции выборки, вставки, обновления и удаления. Каждый класс слоя данных в каждом приложении должен использовать этот класс при отправке инструкций SQL в базу данных. Этот класс канала базы данных является издателем. Каждая команда базы данных будет заканчиваться вызовом класса, зарегистрированного как COM+ и служащего классом события. Каждое веб-приложение, нуждающееся в уведомлении, должно реализовать класс подписчика и зарегистрировать его (класс подписчика) как подписчик события.

Следует начать с компонента события – LCEventLibrary.Dll. Это простоя сборка, содержащая интерфейс и класс, регистрирующийся как библиотека COM+, служащая классом события, использующим атрибут EventClass. Этот класс не должен ничего реализовывать и не должен вызываться напрямую. COM+ использует этот класс для вызова подписчиков.

public interface IMySink
{
   void OnDBChange(string Action, string DBEntity);
}
[EventClass(FireInParallel = true)]
public class MyEventClass : ServicedComponent,IMySink

   public void OnDBChange(string Action, string DBEntity)
   {
      throw(new
NotImplementedException("You should not call an event class directly! "));
   }
}

Чтобы обеспечить подписку веб-приложения на MyEventClass, создается служебный класс (TransientSubscription), инкапсулирующий все строки кода, написанные для динамического добавления подписчика. Динамическая подписка производится путем манипулирования метаданными COM+. Для манипулирования метаданными COM+ необходимо использовать библиотеку типа администрирования COM+ 1.0, поэтому используется tlbimp.exe для создания управляемой обертки для этой библиотеки COM. TransientSubscription предоставляет статическую функцию для добавления и удаления подписчиков. Чтобы добавить подписчика, надо отправить уникальное имя, тип класса события и класс подписчика. Интерфейс приемника находится путем обхода в цикле интерфейсов класса подписчика.

static public void Add(string subName,Type eventClass,Type sinkInterface,
string method,object subscriber)
{
   Type sinkType = subscriber.GetType();
   if(method != "")
   {
      MethodInfo info = sinkInterface.GetMethod(method);
   }
   //добавление новой временной подписки на каталог
   ICOMAdminCatalog catalog;
   ICatalogCollection transientCollection;
   ICatalogObject subscription = null;
   catalog = (ICOMAdminCatalog)new COMAdminCatalog();
   transientCollection =
(ICatalogCollection)catalog.GetCollection("TransientSubscriptions");
   subscription = (ICatalogObject)transientCollection.Add();
   subscription.set_Value("Name",subName);
   subscription.set_Value("SubscriberInterface",subscriber);
   string eventClassString = "{"+eventClass.GUID.ToString()+"}";
   subscription.set_Value("EventCLSID",eventClassString);
   string sinkString = "{" +sinkInterface.GUID.ToString()+ "}";
   subscription.set_Value("InterfaceID",sinkString);
   subscription.set_Value("MethodName",method);
   subscription.set_Value("FilterCriteria","");
   subscription.set_Value("PublisherID","");
   transientCollection.SaveChanges();
}

Класс подписчика (MySubscriber) реализуется в составе веб-приложения. MySubscriber реализует интерфейс IMySink и пишет код, вызываемый, когда класс события вызывает своих подписчиков. В данном примере просто обновляется переменная приложения, чтобы указать, что что-то изменилось. Проблема обновления объекта приложения требует обходного решения. Нельзя использовать HTTPContext.Current, так как вызов подписчика не подключается ни к какому запросу. Для обхода этой проблемы был добавлен конструктор, получающий HttpApplicationState в качестве параметра и устанавливающий внутренний член в него.

public class MySubscriber : LCEventLibrary.IMySink 
{
   private System.Web.HttpApplicationState App;
   public MySubscriber()
   {
   }
   public MySubscriber(System.Web.HttpApplicationState inApp)
   {
      App = inApp;
   }
   public void OnDBChange(string Action, string DBEntity)
   {
      App["DbChange"] = true;
   }
}

Фактическая подписка происходит в событии Application_start.

m_Subscriber = new MySubscriber(System.Web.HttpContext.Current.Application);
TransientSubMgr.TransientSubscription.Add
("MySub",typeof(LCEventLibrary.MyEventClass),m_Subscriber);
Application["DbChange"] = false;

Сборка PublisherClass содержит класс PubClass, играющий роль издателя. PubClass служит  единственной точкой подключения к базе данных, регистрируясь как приложение COM+ с методами для всех действий базы данных. Каждый метод создает экземпляр MyEventClass на базе интерфейса IMySink и вызывает OnDBChange с правильными параметрами. UpdateData извлекает имя таблицы из инструкции SQL и отправляет его в качестве параметра.

public class PubClass : System.EnterpriseServices.ServicedComponent 
{
   public PubClass()
   {
   }
   public void UpdateData(string SQL)
   {
      LCEventLibrary.IMySink sink;
      sink = new LCEventLibrary.MyEventClass();
      string[] arr = SQL.Split (' ');
      sink.OnDBChange("Update",arr[1]);
   }

Для активации издателя добавляется новая страница (CallPublisher.aspx) в веб-приложение. Эта страница содержит две кнопки: одну –  для вызова метода UpdateData PubClass и другую – для обновления страницы. На page_load проверяется переменная DbChange приложения. Если она установлена в истину, выполняется одно из действий базы данных и уведомление отображается в виде HTML.

   private void Page_Load(object sender, System.EventArgs e)
   {
      if ((bool)Application["DbChange"] == true)
      {
         Response.Write ("DB data changed!");
      }
   }
   private void Button1_Click(object sender, System.EventArgs e)
   {
      PublisherClass.PubClass oPC = new PublisherClass.PubClass();
      oPC.UpdateData("update MyTable set MyField=value");
   }
   private void Button2_Click(object sender, System.EventArgs e)
   {
   }
 

Запуск страницы впервые покажет форму с двумя кнопками. Нажатие на кнопку “Обновить” заставит издателя вызвать метод класса события с последующим вызовом всех подписчиков. Класс подписчика в веб-приложении обновит данную переменную приложения. Нажатие кнопки обновления теперь выдаст страницу, содержащую текстовое уведомление об изменении.

Снятие плода COM+ без ухода за деревом COM+.(COM+ 1.5 / CLR 1.1)

COM+ 1.5 вводит новую схему работы, позволяющую строителям классов использовать часть служб COM+ без регистрации классов как приложения COM+ (службы без компонентов - SWC). Эта возможность введена для разработчиков C++, главной заботой которых является производительность. Разработчики C++ могут использовать класс CServiceConfig для запроса интерфейса одной из служб и затем использовать API, чтобы использовать выбранные службы COM+ в качестве встроенного блока или в пакетном режиме.

Хотя SWC может использоваться приложением .NET, использующим стыкуемость, CLR 1.1 вводит новый класс (ServiceConfig) в пространстве имен EnterpriceServices, позволяющий использовать COM+ SWC посредством объектов .NET. ServiceConfig инкапсулирует требуемые функции API для обеспечения SWC. Использовать этот класс просто. Надо создать экземпляр, установить атрибуты для установки средств, требующих использования, с помощью “Войти” и “Уйти” пометить блок, который будет использовать  средства COM+, и написать код внутри этого блока.

   public void HandleUser(string UID)
   {
      DataLayerClass oDLC = new DataLayerClass();
      try
      {
         ServiceConfig sc = new ServiceConfig();
         sc.Transaction = TransactionOption.RequiresNew;
         sc.TransactionTimeout = 30;
         sc.TrackingAppName = "SWC Test";
         sc.TransactionDescription = "Test";
         sc.TrackingEnabled = true;
         //начать блок
         ServiceDomain.Enter(sc);
         // вызвать слой данных для установки базы данных
         oDLC.DeleteUser(UID);
         ContextUtil.SetComplete();
      }
      catch(Exception ex)
      {
         ContextUtil.SetAbort();
      }
      finally
      {
         // закончить блок
         ServiceDomain.Leave();
      }
   }

SWC активирует лишь часть служб COM+, точнее, SWC использует следующие службы COM+:
•    COMTIIntrinsic
•    IISIntrinsic
•    Переключение контекста
•    Уровень изоляции
•    Сегменты
•    Соседние сборки
•    Синхронизация
•    Пул потоков
•    Транзакции.

Однако в его длинный список атрибутов самые полезные службы, вроде организации пула объектов, JITA, компоненты с поддержкой очередей и слабосвязанные события, не включены. Эти средства COM+ решают повседневные проблемы и повышают надежность и доступность приложения.


Как уведомить ASP.NET из серверного приложения COM+

Регистрация класса в качестве серверного приложения COM+ повышает устойчивость и доступность приложения, но имеет недостатки. Один недостаток – необходимость использовать дистанционную связь между компонентом COM+ и вызывающим приложением. Если вызывается компонент COM+ -  дистанционная связь осуществляется CLR, но если надо вызвать приложение из приложения COM+, для ее обеспечения придется сделать некоторую работу.

Один из распространенных сценариев – события обратного вызова из приложения COM+ к вызывающему приложению. Когда приложению COM+ надо вызвать приложение, приложение превращается в сервер дистанционной связи. Чтобы приложение служило сервером дистанционной связи, ему надо назначить порт TCP.

Пора посмотреть, как реальная задача решается с помощью серверных приложений COM+ и событий обратного вызова. Допустим, надо прочитать из базы данных очень большой объем данных, обработать эти данные и отобразить все данные пользователю. При стандартной схеме обработки страницы извлекаются все данные, форматируются и отправляются клиенту в одном буфере. Конечный пользователь получит очень медленное приложение. При работе с большим объемом данных лучше прочитать кусок от всех данных, отформатировать его и отправить непосредственно пользователю. Применение такой схемы дает ощущение использования хорошо работающей системы. Пользователь быстро видит какие-то данные, и по мере увеличения времени он видит все больше данных, пока не отправятся все данные.

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

Пробный проект состоит из нового веб-приложения (ASPNET_COMPLUSE_CALLBACK), обычной сборки (ComPlusFacade) и класса серверного приложения COM+ (ComPlusLib). Задача ComPlusFacade – получить события компонента COM+ и записать полученные данные в выходной буфер. ComPlusFacade является отдельной сборкой, потому что и вызывающее приложение, и вызываемый компонент COM+ должны быть в GAC(глобальный кэш сборок), чтобы позволить абсолютно всем им использовать другого. Перед запуском примера не забудьте зарегистрировать эти сборки в GAC.

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


   [Serializable()]
   public class XMLArg : EventArgs
   {
      string XMLBuffer;
      public XMLArg()
      {
      }
      public XMLArg(string XMLBufferArg)
      {
         this.XMLBuffer = XMLBufferArg;
      }
      public string XMLBufferArg
      {
         get
         {
            return this.XMLBuffer;
         }
         set
         {
            this.XMLBuffer = value;
         }
      }
      public override string ToString()
      {
         return this.XMLBuffer;
      }
   }

clsComPlusLib является типичным компонентом COM+, содержащим объявленное событие, запускающим каждый сквозной оператор цикла for внутри метода ProcessData.

   [ProgId("clsComPlusLib"),
   Transaction(TransactionOption.NotSupported),
   MustRunInClientContextAttribute(false),
   EventTrackingEnabledAttribute(true),
   JustInTimeActivation(true),
   Synchronization(SynchronizationOption.Required),
   Serializable
   ]
   public class clsComPlusLib : ServicedComponent
   {
      public new event EventHandler DataArrive;
      public clsComPlusLib()
      {
      }
      public void ProcessData()
      {
      for (int i=0 ; i<100000 ; i++)
      {
         EventArgs XML = new XMLArg ("i=" + i + "<br>\n");
         this.DataArrive(null,XML);
      }
   }
}

CLR зарегистрирует ComPlusLib в COM+. Вы должны зарегистрировать ComPlusLib в GAC посредством перетаскивания или gacutil.exe.

ComPlusFacade – интересная часть примера. ComPlusFacade является связующим звеном между веб-приложением и серверным приложением COM+. FacadeObjByRef также декорирован сериализацией для обеспечения передачи класса через дистанционную связь. Чтобы FacadeObjByRef мог служить сервером дистанционной связи, он унаследован от MarshalByRefObject, и объявлен член типа TcpChannel.

[Serializable()]
public class FacadeObjByRef : MarshalByRefObject
{
   protected ComPlusLib.clsComPlusLib eventHandler =
new ComPlusLib.clsComPlusLib();
   protected System.IO.Stream outStream;
   public static TcpChannel theChannel;

Подрядчик класса получает поток в качестве параметра. Поток отправки должен быть потоком вывода ответа страницы, используемым для выдачи полученных данных клиенту. Внутри конструктора регистрируется TcpChannel, чтобы позволить среде выполнения дистанционной связи выбрать свободный порт и установить обработчик события для события компонента COM+.

   public FacadeObjByRef(System.IO.Stream outStream) 
   {
      lock(this)
      {
         if (theChannel == null)
         {
            theChannel = new TcpChannel(0);
            ChannelServices.RegisterChannel(theChannel);
         }
      }
      if (textEncoder == null)
      {
         textEncoder = Encoding.UTF8;
      }
      this.outStream = outStream;
      this.eventHandler.DataArrive += new
System.EventHandler(this.DataArive);
   }

ProcessData является открытым методом класса, вызываемым веб-страницей и вызывающим приложение COM+ синхронно, чтобы запустить его работу.

public void proccessData()
{
   eventHandler.ProcessData();
   this.CloseStream();
   this.cleanUp();
}

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

public void DataArive(object sender, System.EventArgs e)
{
   byte[] toWrite = textEncoder.
GetBytes(((ComPlusLib.XMLArg)e).XMLBufferArg + "\n");
   outStream.Write(toWrite, 0, toWrite.Length);
}

ComPlusFacade выполняется в процессе веб-приложения, надо только зарегистрировать его в GAC.

Веб-приложение ASPNET_COMPLUSE_CALLBACK имеет одну страницу aspx (webform1.aspx), отключающую стандартную буферизацию и создающую объект из класса FacadeObjByRef и вызывающую метод ProcessData.

private void Page_Load(object sender, System.EventArgs e)
{
   Response.BufferOutput = false;
   ComPlusFacade.FacadeObjByRef oObj = new
ComPlusFacade.FacadeObjByRef (Response.OutputStream );
   oObj.proccessData ();
}

Осталось запустить ASPNET_COMPLUSE_CALLBACK и посмотреть на его поведение. Страница быстро показывает куски данных, продолжающие приходить, и присоединяет их к HTML в браузере.

Заключение

Цель статьи – показать, как использовать службы COM+ для решения типичных задач программирования при разработке веб-приложений. Начинается с изучения влияния приложения сервера и библиотеки COM+ на производительность и недостатков использования серверного приложения.

После рассмотрения влияния на производительность объясняется и показывается, насколько огромен вклад использования серверного приложения COM+ в устойчивость, надежность приложений и контроль над ними. Решение повседневных задач, таких как инициализация длинного компонента, задача длинных компонентов, уведомление и другие, показывает, как использование служб COM+ и особенно серверных служб COM+ сокращает написание кода и усилия программистов.

COM+ предоставляет мощные службы, помогающие быстро создать развитое и устойчивое приложение. Основной недостаток использования служб COM+ - снижение производительности. Можно использовать часть служб COM+ с помощью служб без компонентов без снижения производительности и приложения библиотеки COM+ с приемлемым снижением. Сервер COM+, предоставляющий наиболее интересные службы, плохо влияет на производительность преимущественно из-за использования DCOM (распределённая компонентная модель объектов).

Перед использованием служб COM+ следует создать пробное приложение и протестировать его с ACT, чтобы выяснить, соответствует ли использование сервера COM+ целевой производительности приложения. Будем  надеяться, следующие версии windows, такие как лонгхорн, будут поставляться со службами COM+, не основанными на DCOM, что позволит большему числу приложений использовать серверное приложение COM+.