• Введение
  • Службы COM+ в ретроспективе
  • Состав служб COM+ 
  • Snap-in служб компонентов
  • Транзакции COM+ 
  • Назначение транзакций
  • Принципы транзакций
  • Транзакции в N-звенной архитектуре
  • Службы COM+ и время жизни объекта
  • Создание пулов объектов
  • Оперативная активизация (JIT)
  • Безопасность
  • Новые службы COM+
  • События
  • Очереди сообщений
  • Выравнивание нагрузки компонентов
  • Использование служб COM со сборками .NET
  • Подготовка сборок .NET для служб COM+
  • Предоставление атрибутов сборок
  • Развертывание сборки для служб COM+
  • Предварительные итоги
  • Использование транзакций со сборками .NET
  • Определение транзакционной поддержки
  • Кодирование транзакций с помощью ContextUtil
  • Другие полезные методы ContextUtil
  • Использование пудов объектов со сборками .NET
  • Атрибут ObjectPooling
  • Интерфейс ServicedComponent
  • Использование активизации JIT со сборками .NET
  • Заключение
  • Главa 20

    Службы COM+ 

    Введение

    В этой главе рассматриваются два больших вопроса: 1) что такое службы COM+, как они разрабатываются и как работают; и 2) каким образом службы COM+ могут использоваться на платформе .NET в целом и в C#, в частности.

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

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

    RegSvcs.exe
    .

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

    Давайте начнем с рассмотрения того, как появились службы COM+.

    Службы COM+ в ретроспективе

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

    По мере развития технологий программирования поставщики упаковали полезную функциональность в повторно используемые серверные компоненты. (Теперь, например, если разработчику потребуется функциональность базы данных, то он может использовать Oracle или SQL Server). По мере дальнейшего развития все больше полезной функциональности извлекалось из прикладных программ и переносилось на уровень серверных программ и даже операционной системы.

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

    Состав служб COM+ 

    Службы COM+ начинали жизнь как дополнительный модуль Windows NT, называемый Сервер транзакций Microsoft (MTS). В настоящее время Windows 2000 определяет MTS как составную часть операционной системы, переименовывая ее в ходе процесса.

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

    Службы COM+, которые уже были представлены в MTS, включают в себя:

    □ Обеспечение транзакций

    □ Организацию пулов объектов

    □ Оперативную активизацию объектов (JIT, Just-in-Time)

    □ Безопасность

    Новые службы, введенные COM+:

    □ Поддержка событий

    □ Очереди сообщений компонентов

    □ Выравнивание нагрузки компонентов

    В этой главе мы рассмотрим каждую из служб COM+, как старые, известные из MTS, так и новые, которые могут быть еще незнакомы. Но сначала кратко рассмотрим лучшего помощника COM+: "snap-in" служб компонентов (Component Services). (Snap-in является специальным типом программы, таким как SQL Server или IIS, который выполняется внутри интерфейса консоли управления Microsoft — ММС.)

    Snap-in служб компонентов

    Опытные разработчики могут вспомнить, что администратор MTS был доступен в Windows NT из меню Start как пункт Option Pack. В соответствии со своим новым статусом составной части операционной системы службы COM+ перечислены в Windows 2000 более отчетливо в меню Administrative Tools под заголовком Component Services.

    Левая панель окна служб компонентов (Component Services) содержит иерархическое дерево с компьютером, приложением COM+ и узлами компонентов. (На языке служб компонентов приложение является группой компонентов COM+, которые рассматриваются (администрируются) как целое, это называлось в MTS пакетом). Класс каждого компонента в приложении представлен золотым шариком со знаком плюс в середине, который начинает вращаться, когда происходит обращение к компоненту.

    Существует два метода импорта сборок .NET в службы компонентов. Первый метод использует функциональность, предоставленную snap-in служб компонентов, а второй использует CLR.

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

    Транзакции COM+ 

    Назначение транзакций

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

    Рассмотрим web-сайт, который занимается в основном обработкой кредитных карт для заказов. Если пользователь заказывает на сайте какой-то продукт, то требуется, чтобы не только было выполнено списание с его счета, но чтобы запись заказа была помещена в базу данных заказов. Если возникает проблема с вводом записи о заказе в базу данных, то списание средств с кредитной карты должно быть отменено и заказ также должен быть отменен, иначе пользователь заплатит за товар, который он никогда не получит.

    public void PlaceOrder(OrderInfo, objOrderInfo, UserInfo objUserInfo) {

     CreditCard objCreditCard=new CreditCard();

     OrderTable objOrderTable=new OrderTable();

     // Шаг 1: Списание средств с кредитной карты

     objCreditCard.PlaceCharge(objOrderInfo, objUserInfo);

     // Если здесь возникает ошибка между шагами 1 и 2,

     // то заказчик не получит продукт, за который

     // он заплатил

     // Шаг 2: Записать заказ

     objOrderTablе.RecordOrder(objOrderInfo, objUserInfo);

    }

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

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

    Принципы транзакций

    Рассмотрим внутренний механизм работы транзакций

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

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

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

    Хотя и немного детальное, это объяснение внутренней работы транзакций будет полезно позже, при кодировании поддержки транзакций в сборке .NET.

    Транзакции в N-звенной архитектуре

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

    Обычно один метод бизнес-объекта вызывает несколько методов на различных объектах доступа к данным. Если один из методов доступа к данным не может выполниться правильно, метод бизнес-объекта использует механизм транзакций COM+, чтобы запросить DTC об отмене операции.

    Службы COM+ и время жизни объекта

    Название Сервер транзакций Microsoft было несколько неправильным, так как MTS предоставлял больше, чем просто поддержку транзакций. В этом разделе мы рассмотрим две службы COM+, которые были упомянуты первыми в MTS, — активацию JIT и создание пулов объектов. Обе эти службы являются технологиями эффективного использования серверными машинами своих ресурсов при манипуляциях серверными объектами.

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

    □ Библиотечное приложение является совокупностью классов компонентов, объекты которых создаются в процессе вызывающего клиента.

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

    Большинство приложений на основе ASP используют компоненты, хранимые в серверных приложениях. Так как компоненты находятся в выделенном замещающем процессе, отказ серверного компонента не приводит к аварийному отказу сервера Web. Для стандартных (не определенных в .NET) приложений COM задается свойство Activation Type на вкладке Activation окна приложения Properties:

    Как мы увидим позже, существует другая процедура для определения типа активации сборки .NET. Будет показано, как делать это программным путем с помощью атрибутов.

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

    Создание пулов объектов

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

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

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

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

    Второй: можно наделить компоненты интеллектом, необходимым для сериализации и восстановления ими своего состояния.

    Состояние объекта представляет интерес не только при использовании пулов объектов, но и при использовании утилит активации JIT. Это следующая рассматриваемая служба COM+.

    Оперативная активизация (JIT)

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

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

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

    Мы использовали серверные объекты, которые требуют некоторой инициализации перед тем, как можно будет вызвать их методы. Объект

    ADODB.Connection
    является одним из примеров необходимо инициализировать его свойство
    ConnectionString
    , прежде чем можно будет приказать ему выполнить запрос SOL. Так как объект в пуле не может сохранять состояние между вызовами, невозможно инициализировать его и вызывать его методы в отдельных шагах. Обращение к объекту в пуле должно передавать в этот объект все значения, которые нужны ему для выполнения работы.

    Безопасность

    Существует два аспекта в модели безопасности, которую предоставляют службы COM+

    Первым аспектом является аутентификация. Вкратце службы COM+ позволяют наложить ограничения на того, кто имеет доступ к служебным компонентам и методам, которые они предоставляют. Используя snap-in службы компонентов, можно задать уровень аутентификации приложения для определения того, когда выполняется аутентификация — при соединении клиента с серверным объектом, для каждого сетевого пакета коммуникации для объекта, при вызове каждого метода и т.д.

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

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

    Мы обсудим безопасность на основе ролей для сборки .NET позже в этой главе.

    Новые службы COM+

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

    События

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

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

    Чтобы реализовать эту схему, необходимо выполнить следующие шаги:

    1. Создать DLL класса события, которая реализует интерфейс события.

    2. Зарегистрировать DLL класса события в службах COM+.

    3. Создать серверный компонент, который внутренне создает экземпляр класса события и вызывает методы на этом экземпляре, чтобы инициировать события.

    4. Зарегистрировать серверный компонент как издателя в службах COM+.

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

    6. Зарегистрировать клиентские компоненты как подписчиков в службах COM+.

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

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

    □ Второй: по крайней мере во время написания книги службы COM+ не имели возможности инициировать события от объектов-издателей для объектов-подписчиков на других машинах.

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

    Очереди сообщений

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

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

    Как можно представить, очереди сообщений будут удобным средством при создании приложений, которые должны выполняться на машинах со связью и без связи. К тому же, очереди сообщений являются составной частью сервера BizTalk компании Microsoft — новой серверной программы, делающей возможным процесс перемещения данных внутри и между организациями. При установке Windows 2000 Server очереди сообщений являются одной из возможностей, которую можно установить или отбросить.

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

    Выравнивание нагрузки компонентов

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

    Основным объектом стратегии выравнивания нагрузки компонентов является сервер выравнивания нагрузки компонентов, или CLB (Component Load Balancing). CLB является машиной Windows Advance Server или Windows Data Server, которая служит менеджером для других серверов в ферме. CLB отвечает за распределение запросов объектов между доступными серверами.

    Алгоритм, который использует сервер CLB для выбора хоста объекта, является достаточно сложным. Он проходит в определённом порядке список доступных серверов, передавая запросы создания первому доступному серверу. Так как этот список упорядочен от наиболее надежного к наименее надежному серверу, скорее всего запрос будут обрабатывать более мощные серверы.

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

    Использование служб COM со сборками .NET

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

    Взаимодействие со службами COM+ из сборок .NET делается возможным в основном через атрибуты. Задавая префиксы для определений классов с помощью атрибутов, определенных в пространстве имен EnterpriseServices, можно определить, как службы COM+ используют эти классы. Компилятор C# знает, как транслировать атрибуты в "крючки" необходимого кода, которые службы COM+ ожидают от компонентов.

    Некоторыми из атрибутов, определенных в пространстве имен

    EnterpriseServices
    , являются:

    Transaction

    ObjectPooling

    JustInTimeActivation

    EventClass

    ApplicationActivation

    В дополнение к этим атрибутам пространство имен

    EnterpriseServices
    определяет различные классы и перечисления, некоторые из которых мы скоро подробно рассмотрим. Если надо увидеть содержимое пространства имен, воспользуйтесь утилитой
    WinCV
    . Чтобы увидеть классы в пространстве имен
    System.EnterpriseServices
    , добавьте строку:

    <assembly name = "System.EnterpriseServices" />

    в элемент

    <wincv>
    файла
    WinCV.exe.config
    .

    Подготовка сборок .NET для служб COM+

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

    Предоставление атрибутов сборок

    Первое. Компания Microsoft предлагает стандартизованное множество "атрибутов сборок", которые должны включаться в каждую сборку .NET, использующую службы COM+.

    Следующий пример кода перечисляет их:

    [assembly:ApplicationActivation(ActivationOption.Server)]

    [assembly:ApplicationID("448934a3-324f-34d3-2343-129ab3c43b2c")]

    [assembly:ApplicationName("SomeApplicationName")]

    [assembly:Description("Description of your assembly here.")]

    Рассмотрим каждый из этих атрибутов по очереди.

    Ранее упоминалось, что существуют два вида приложений COM+ — серверные приложения и библиотечные приложения. Первый атрибут в коде примера —

    ApplicationActivation
    — позволяет определить, каким из этих видов приложений является определенная сборка. (Допустимые значения для этого атрибута определяются в перечислении
    ActivationOption
    , которое можно заметить внутри скобок атрибута.) Определяя тип приложения программным путем с помощью этого атрибута, можно избежать необходимости открывать менеджер службы компонентов и делать это вручную. Это перечисление имеет два значения:
    ActivationOption.Library
    и
    ActivationOption.Server
    .

    Второй атрибут,

    ApplicationID
    , определяет присоединенный 128-битный уникальный идентификатор (GUID) сборки. (GUID являются идентификационными номерами, которые гарантируют уникальность в течение очень большого периода времени. Службы COM+ ожидают такой идентификатор от каждого приложения.) В коде примера случайно выбранный GUID не имеет ничего существенного, он присутствует только для целей демонстрации. Для каждой создаваемой сборки придется создавать свой собственный. Чтобы сделать это, можно использовать утилиту
    GuidGen.exe
    компании Microsoft, которая распространяется вместе с Visual Studio.

    Третий атрибут в коде примера,

    ApplicationName
    , позволяет определить имя приложения службы COM+, которое будет создано для размещения сборки .NET, когда она импортируется в службы COM+. В данном примере используется значение
    SomeAppliсationName
    .

    Четвертый и последний атрибут,

    ApplicationDescription
    , позволяет связать описание со сборкой, чтобы предоставить разработчикам некоторые идеи о том, что она делает.

    Документация компании Microsoft определяет, что любая сборка .NET, использующая в соединении со службами COM+, должна применять все эти четыре атрибута.

    Развертывание сборки для служб COM+

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

    Первое: необходимо предоставить сборке сильное имя. Это делается с помощью утилиты

    sn.exe
    из SDK .NET (см. главу 10).
    sn.exe
    будет выводить файл сильного имени, на который можно ссылаться из командной строки, когда сборка компилируется, чтобы встроить сильное имя в компилированную сборку.

    Второе: необходимо зарегистрировать сборку в глобальном кэше сборок (см. главу 10).

    Если сборку будут использовать только управляемые клиенты (то есть, клиенты .NET), никаких дополнительных усилий по развертыванию не требуется. Когда управляемый клиент создает в сборке экземпляр обслуживаемого класса, CLR использует атрибуты в сборке для автоматической регистрации компонента в службах COM+.

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

    RegSvcs.exe
    , предоставляется компанией Microsoft как часть SDK .NET. Когда
    RegSvcs
    выполняется на компоненте .NET, она создает приложение COM+ с именем, указанным атрибутом
    ApplicationName
    в сборке, и импортирует сборку в него.

    Для чего же требуется RegSvcs.exe? 

    Как можно помнить из предыдущей главы по взаимодействию COM, сборки .NET имеют архитектуру, отличную от архитектуры компонентов COM. Задача

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

    1. Загружает и регистрирует сборку .NET.

    2. Создает библиотеку типов для сборки .NET.

    3. Импортирует библиотеку типов в приложение служб COM+.

    4. Использует метаданные внутри DLL, чтобы правильно сконфигурировать библиотеку типов внутри приложения служб COM+.

    RegSvcs
    не только заботится обо всех деталях импортирования сборки в службы COM+, но предоставляет также достаточно хороший контроль за тем, как это происходит. Этот контроль обеспечивается в форме дополнительных параметров командной строки. Вот синтаксис команды:

    Regsvcs .NetComponentName [COM+AppName] [TypeLibrary.tlb]

    С помощью второго аргумента (

    COM+AppName
    ) можно определить другое имя для создаваемого приложения COM+, предоставляя второй аргумент командной строки при вызове
    RegSvcs
    . Для еще большей гибкости можно определить имя файла библиотеки типов, которая создается при предоставлении третьего аргумента (
    TypeLibrary.tlb
    ). Желательно всегда предоставлять эти аргументы при вызове
    RegSvcs
    , так как более ранние версии этой программы будут молчаливо перезаписывать любые существующие файлы, которые могут иметь такие же имена, как у вновь создаваемых файлов.

    Предварительные итоги

    Теперь мы знаем, как подготовить сборку .NET для применения вместе со службами COM+. Эта подготовка включает в себя:

    □ Снаряжение сборки рекомендованными атрибутами сборки

    □ Соединение классов прокси с внутренними "рабочими" классами посредством атрибута

    ComEmulate

    □ Развертывание сборок с помощью

    sn.exe
    ,
    al.exe
    и, возможно,
    RegSvcs.exe

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

    Использование транзакций со сборками .NET

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

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

    Определение транзакционной поддержки

    Ранее при использовании транзакций из служб COM+ можно было увидеть настройку уровня транзакций в окне свойств класса в Snap-In службы компонентов. Эта настройка позволяет задать уровень поддержки транзакций, который службы COM+ будут предоставлять стандартному компоненту COM.

    Иначе в .NET уровень поддержки транзакций в сборке можно определить не с помощью графического окна в snap-in службы компонентов, а программным путем с помощью атрибута

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

    [Transaction(TransactionOption.Supported)]

    public class ProxyClass:ServicedComponent {

    }

    Supported
    является только одним из нескольких значений, которые можно присвоить атрибуту
    Transaction
    компонента. Фактически, существует четыре значения, которые представлены в перечислении
    TransactionOption
    , являющемся частью пространства имен
    System.EnterpriseServices
    .

    □ Когда атрибут

    Transaction
    класса задан как
    Disabled
    , службы COM+ не предоставляют транзакционной поддержки для класса, даже если такая поддержка определена где-то в коде. (Другими словами, вызовы этого класса, сделанные для
    ContextUtil
    с целью фиксации или отмены транзакций, игнорируются. Мы познакомимся с
    ContextUtil
    в следующем разделе.)

    □ Когда атрибут

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

    □ Когда атрибут

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

    □ Когда атрибут

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

    □ Когда атрибут

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

    На практике большинство разработчиков используют только одну или две из этих настроек. Значение

    Supported
    подходит для классов типа класса
    Settings
    , которому нужно будет обслуживать классы с транзакциями и без транзакций. Для большинства других транзакционных классов можно справиться, задавая значение
    Required
    . Однако все-таки может возникнуть ситуация, где потребуются одно или несколько составных значений, дополнительную информацию можно найти в книге "Professinal Windows DNA Programming" (ISBN 1-861004-45-1) издательства Wrox Press.

    Кодирование транзакций с помощью ContextUtil

    Модификация класса с помощью атрибута

    Transaction
    является только частью того, что необходимо сделать, чтобы подготовить его для участия в транзакции. Надо также определить, как каждый метод в этом классе ведет себя, когда он вызывается как часть транзакции. Это осуществляется с помощью класса
    ContextUtil
    из пространства имен
    System.EnterpriseServices
    .

    Проще говоря, класс

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

    В качестве примера сделаем краткий обзор фрагмента кода, приведенного ниже.

    public bool PlaceOrder(bool CommitTrans) {

     // Попытка работы

     try {

      if (CommitTrans) {

       // Эта транзакция должна быть зафиксирована

       // шаг 1 — увеличить число единиц продукта ID=2 на 10

       IncreaseUnits(2, 10);

       // шаг 2 — сократить запас продукта ID=2 на 10 единиц

       ReduceStock(2, 10);

      } else {

       // Эта транзакция должна быть отменена

       // шаг 1 — увеличить число единиц продукта ID=5 на 5 единиц

       IncreaseUnits(5, 5);

       // шаг 2 - сократить запас продукта ID=5 на 5 единиц

       ReduceStock(5, 5);

      }

      // Если все прошло хорошо, завершить транзакцию.

      ContextUtil.SetComplete();

      return true;

     }

     // Этот код выполняется, если встречается ошибка.

     catch (Exception е) {

      // Отменить работу, которую выполнила эта функция.

      ContextUtil.SetAbort();

      return false;

     }

    }

    Что здесь происходит?

    Мы имеем две транзакции, которые обрабатываются в зависимости от значения

    CommitTrans
    . Для любой транзакции
    PlaceOrder()
    вызывает два метода, которые оба соединяющиеся с базой данных
    Northwind
    , чтобы сделать изменения в таблице
    Product
    . Метод
    ReduceStock()
    сокращает объем запасов в столбце
    UnitsInStock
    , метод
    IncreaseUnits()
    увеличивает значение столбца
    UnitsOnOrder()
    . Для обоих методов первым параметром является
    ProductID
    в строке, которую нужно изменить, второй параметр есть величина, на которую мы хотим изменить соответствующий столбец.

    Выполняющаяся транзакция контролируется булевой переменной

    CommitTrans
    , передаваемой в
    PlaceOrder()
    . Первая транзакция должна быть зафиксирована, так как уровень запаса для
    ProductID=2
    равен 17, следовательно, можно удалить десять элементов и все еще иметь оставшийся запас. Однако вторая транзакция обречена на отказ так как
    ProductID=5
    не имеет запаса элементов и существует ограничение на столбец
    UnitsInStock
    , которое не позволяет значению становиться меньше нуля. Это означает, что можно проверить, будет ли транзакция отменяться или нет. Не должно быть никаких проблем с вызовом
    IncreaseStock()
    , поэтому можно увидеть, что транзакция была отменена, проверяя значение столбца
    UnitsOnOrder
    для
    ProductID=5
    .

    В блоке

    try
    , если все идет хорошо, или, другими словами, если поток выполнения должен покинуть
    PlaceOrder()
    нормально, через
    return true
    , инструкция
    PlaceOrder()
    вызывает метод
    SetComplete()
    объекта
    ContextUtil
    , эффективно сообщая DTC через менеджер ресурсов, что в той части, которая касается его, транзакцию необходимо зафиксировать.

    С другой стороны, если где-то в

    PlaceOrder
    возникает ошибка и порождается исключение, управление программой будет передано предложению
    catch()
    . В этом предложении
    PlaceOrder()
    вызовет метод
    SetAbort()
    объекта
    ContextUtil
    . Этот метод посылает голос
    PlaceOrder()
    за отмену транзакции, в которую он вовлечен, и DTC после получения этого голоса от менеджера ресурсов прикажет каждому участнику транзакции отменить свою работу.

    Помните, что не требуется создавать экземпляр объекта

    ContextUtil
    , чтобы вызвать его методы
    SetComplete()
    и
    SetAbort()
    . Эти методы класса, поэтому их можно вызывать прямо на классе.

    Практически любой код, поддерживающий транзакции будет походить на код в примере. Он вызывает

    SetComplete()
    перед своей точкой выхода для фиксации всей работы, которая была успешно выполнена, или он вызывает метод
    SetAbort()
    класса
    ContextUtil
    в своем обработчике ошибок, чтобы все отменить в связи с ошибкой. Существует, правда, еще более простой способ.

    Компания Microsoft предоставляет атрибут .NET, называемый

    AutoComplete
    . Методы, модифицированные с помощью этого атрибута, автоматически применяют подход, описанный выше. И хотя такие методы никогда явно не ссылаются на класс
    ContextUtil
    , они неявно завершают свои транзакции, если те заканчиваются нормально, или отменяют всю работу если выход происходит в связи с ошибкой (когда порождается исключение). По прежнему необходимо вызывать
    SetAbort()
    , чтобы отменить работу транзакции если порождается исключение.

    [AutoComplete]

    public bool PlaceOrder(bool CommitTrans) {

     try {

      if (CommitTrans) {

       // Эта транзакция должна быть зафиксирована

       // шаг 1 — Увеличить число единиц продукта ID=2 на 10

       IncreaseUnits(2, 10);

       // шаг 2 - Сократить запас продукта ID=2 на 10 единиц

       ReduceStock(2, 10);

      } else {

       // Эта транзакция должна быть отменена

       // шаг 1 - Увеличить число единиц продукта ID=5 на 5

       IncreaseUnits(5, 5);

       // шаг 2 — Сократить запас продукта ID=5 на 5 единиц

       ReduceStock(5, 5);

      }

      return true;

     } catch (Exception e) {

      ContextUtil.SetAbort();

      return false;

     }

    }

    Это полный код примера всей транзакции, он показывает, как все части объединяются. Следующее просто встроено в библиотеку классов с заданным сильным именем и зарегистрировано в глобальном кэше сборок.

    using System;

    using System.EnterpriseServices;

    using System.Data.SqlClient;


    namespace OrderTransaction {

     [Transaction(TransactionOptiоn.Required)]

     public class Purchase : ServicedComponent {

      public Purchase() { }


      public bool PlaceOrder(bool CommitTrans) {

       // Попытка работы

       try {

        if (CommitTrans) {

         // Эта транзакция должна быть зафиксирована

         // шаг 1 - Увеличить число единиц продукта ID=2 на 10

         IncreaseUnits(2, 10);

         // шаг 2 - Сократить запас продукта ID=2 на 10 единиц

         ReduceStock(2, 10);

        } else {

         // Эта транзакция должна быть отменена

         // шаг 3 — Увеличить число единиц продукта ID=5 на 5

         IncreaseUnits(5, 5);

         // шаг 2 — Сократить запас продукта ID=5 на 5

         единиц ReduceStock(5, 5);

        }

        // Если все прошло хорошо, закончить транзакцию.

        ContextUtil.SetComplete();

        return true;

       }

       // Этот код выполняется, если встречается ошибка.

       catch (Exception e) {

        // Отменить работу, которую выполнила эта функция.

        ContextUtil.SetAbort();

        return false;

       }

      }


      public void ReduceStock(int ProductID, int amount) {

       string source = "server ephemeral;uid=sa;pwd=garysql;database Northwind";

       SqlConnection conn = new SqlConnection(source);

       string command =

        "UPDATE Products SET UnitsInStock = UnitsInStock - " +

        amount.ToString() + " WHERE ProductID = " + ProductID.ToString();

       conn.Open;

       sqlCommand cmd = new SqlCommand(command, conn);

       cmd.ExecuteNonQuery();

       conn.Close();

      }


      public void IncreaseUnits(int ProductID, int amount) {

       string source = "server=ephemeral;uid=sa;pwd=garysql;database=Northwind";

       SqlConnection conn = new SqlConnection(source);

       string command =

        "UPDATE Products SET UnitsOnOrder = UnitsOnOrder +

        " amount.ToString() + " WHERE ProductID = " + ProductID.ToString();

       conn.Open();

       SqlCommand cmd = new SqlCommand(command, conn);

       cmd.ExecuteNonQuery();

       conn.Close();

      }


      public void Restore() {

       // Восстановить запас продукта ID=2

       ReduceStock(2, -10);

       // Восстановить единицы продукта для ID=2

       IncreaseUnits(2, -10);

       // Не требуется восстанавливать запас или единицы продукта для ID=5,

       // так так транзакция должна быть отменена

      }

     }

    }

    Можно создать клиента для тестирования этой библиотеки классов. Здесь клиент создан так, что он ожидает, пока пользователь проверит содержимое базы данных, прежде чем восстановить базу данных в исходное состояние. Это позволяет увидеть результаты транзакций. 

    statiс void Main(string[] args) {

     Purchase order = new Purchase();

     Console.WriteLine("\nThis transaction should commit");

     Console.WriteLine("ProductID = 2, ordering 10 items");

     if (Order.PlaceOrder(true)) Console.WriteLine("Transaction Successful");

     else Console.WriteLine("Transaction Unsuccessful");

     Console.WriteLine("\nThis transaction should roll back");

     Console.WriteLine("ProductID = 5, ordering 5 items");

     if (Order.PlaceOrder(false)) Console.WriteLine("Transaction Successful");

     else Console.WriteLine("Transaction Unsuccessful");

     Console.WriteLine(

      "\nTake a look at the database then hit enter to

      + "return database to original state");

     Console.ReadLine();

     Order.Restore();

    }

    Другие полезные методы ContextUtil

    Рассмотрим еще пару методов класса

    ContextUtil
    которые могут оказаться полезны при программировании на C#.

    Первый метод

    IsCallerInRole()
    предназначен для безопасности на основе ролей. В качестве входной переменной этот метод получает строковую переменную, содержащую имя определенной роли системы безопасности Windows 2000. Он возвращает булево значение, указывающее, является или нет пользователь, который в данный момент вызывает объект, членом указанной роли.

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

    PlaceOrder()
    , является авторизованным членом роли
    Administrators
    . Если пользователь не является членом этой роли, то
    PlaceOrder()
    порождает исключение.

    [AutoComplete]

    public bool PlaceOrder(bool CommitTrans) {

     if (!ContextUtil.IsCallerInRole("Administrators") {

      throw new AccessViolationException("User is not authorized to place" + "orders.");

     }

     // Поместить код транзакции здесь

    }

    Вторым полезным методом класса

    ContextUtil
    является
    IsInTransaction()
    . Этот метод возвращает булево значение, указывающее, участвует ли объект в данный момент в транзакции.

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

    IsInTransaction
    класса
    ContextUtil
    и инициировать ошибку, если это свойство задано как
    false
    .

    В примере кода ниже свойство

    IsInTransaction
    используется для гарантии, что сборка правильно сконфигурирована, прежде чем ей будет разрешено ей начать какую-либо работу. Код порождает исключение, если
    IsInTransaction
    имеет значение
    false
    . Можно протестировать это, изменяя атрибут класса на
    TransactionalOptionDisabled
    .

    [AutoComplete]

    public bool PlaceOrder(bool CommitTrans) {

     if (!ContextUtil.IsInTransaction) {

      throw new

       ConfigurationException("This assembly needs to be configured for" + " transactions.");

     }

     // Выполнить транзакцию

    }

    Этим мы завершаем обсуждение транзакций COM+ и класса

    ContextUtil
    . Давайте перейдем к пулам объектов.

    Использование пудов объектов со сборками .NET

    Нетрудно сконфигурировать компонент .NET для пула объектов. Для этого необходимо изменить класс с помощью атрибута и реализовать интерфейс в этом классе.

    Атрибут ObjectPooling

    Атрибутом с помощью которого необходимо изменить класс, является

    ObjectPooling
    . Этот атрибут получает четыре аргумента.

    1. Аргумент

    Enabled
    является первым. Ему должно быть присвоено значение true.

    2. Аргумент

    MinPoolSize
    определяет минимальное число экземпляров объектов, которое должны поддерживать службы COM+ в пуле объектов класса.

    3. Аргумент

    MaxPoolSize
    определяет максимальное число экземпляров объектов, которое должны поддерживать службы COM+ в пуле объектов класса.

    4. Аргумент

    CreationTimeOut
    определяет период времени, в течение которого службы COM+ должны пытаться получить объект из пула, прежде чем вернуть отказ.

    Далее следует пример атрибута

    ObjectPooling
    со всеми четырьмя аргументами, примененными к классу. Мы расширим этот фрагмент кода в конце данного раздела.

    [ObjectPooling (Enabled=True, MinPoolSize=1, MaxPoolSize=100, CreationTimeout=30)]

    public class CreditCard:ServicedComponent {

    Интерфейс ServicedComponent

    Как можно было заметить, класс в примере выше наследует интерфейс

    ServicedComponent
    . Все классы .NET, которые используют пулы объектов, должны реализовывать этот интерфейс.
    ServicedComponent
    содержит три метода для переопределения.

    1. Метод

    CanBePooled()
    используется клиентами для определения, что может быть создан пул объектов класса.

    2. Метод

    Activate()
    вызывается службами COM+ на объекте в пуле перед тем, как этот объект передается новому клиенту.

    3. Метод

    Deactivate()
    , напарник метода
    Activate()
    , вызывается службами COM+, когда объект освобождается клиентом, чтобы вернуть его в пул доступных объектов.

    Следующий фрагмент кода показывает класс, сконфигурированный для пула объектов.

    [ObjectPooling (Enabled=true, MinPoolSize=1, MaxPoolSize=100, CreationTimeout=30)]

    public class CreditCard:ServicedComponent {

     // Этот метод будет вызываться службами COM+ для определения,

     // что объект находится в пуле.

     public override bool CanBePooled() {

      return true; // необходимо вернуть логическое "true"

     }


     // Этот метод должен вызываться службами COM+, когда объект

     // передается клиенту.

     public override void Activate() {

      // Код инициализации находится здесь.

     }


     // Этот метод будет вызываться службами COM+, когда

     // объект будет возвращаться в пул.

     public override void Deactivate() {

      // Код завершения находится здесь

     }


     // Этот метод будет вызываться клиентом.

     public void PlaceCharge(int OrderInfo, int UserInfo) {

      // код списания средств с кредитной карты находится здесь

     }

    }

    Как показывает пример, атрибут

    ObjectPooling
    и интерфейс
    ServicedComponent
    требуются для того, чтобы класс .NET реализовал пул объектов. Также можно заметить, что в отличие от атрибута
    Transaction
    атрибут
    ObjectPooling
    применяется непосредственно к "рабочей" сборке .NET, а не к классу прокси, созданному с атрибутом
    ComEmulate
    , который был рассмотрен ранее в этой главе.

    Использование активизации JIT со сборками .NET

    Чтобы сконфигурировать класс .NET для активизации JIT, нужно просто изменить класс с помощью атрибута

    JustInTimeActivation
    , задав булево значение true. В данном случае класс
    CreditCard
    из предыдущего примера модифицируется для активизации JIT.

    [JustInTimeActivation(true)]

    public class CreditCard: ServicedComponent {

     // Этот метод будет вызываться клиентом.

     public void PlaceCharge(OrderInfo objOrderInfo, UserInfo objUserInfo) {

      // Код для снятия средств с кредитной карты находится здесь

     }

    }

    Заключение

    Прежде чем начинать разработку следующего проекта развития предприятия, познакомьтесь со службами COM+. Упомянутая ранее книга "The Professional Windows DNA" издательства WroxPress является хорошим началом. При правильном использовании службы COM+ дают изобилие функциональности, что потребовало бы немало времени для воспроизведения и еще больше для полной отладки. Более того, подходы, которые службы COM+ используют для поддержки транзакций, сохранения ресурсов и межпроцессной коммуникации являются в некоторой степени базовыми, после изучения их можно будет применять к большому спектру разнообразных проблем. 







     

    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх