четверг, 31 марта 2011 г.

Способы размещения служб WCF. Часть 2: размещение службы в контексте любого Windows-приложения


Вторым способом размещения службы WCF является размещение этой службы в контексте приложения Windows. Такой способ размещения называетсяавторазмещением (self-hosting). При авторазмещении служба может быть запущена в контексте любого приложения, будь то консольное приложение, приложение WinForms, приложение WPF или сервис Windows.
 Например, размещение службы как Windows-сервис дает возможность управлять запуском и остановкой этой службы, как через командную строку так и через оснастку Service Control Management, а так же настраивать возможные варианты действий при успешном или неудачном запуске. Более того, при нужной настройке служба будет автоматически запускаться при старте ОС. Если же служба будет размещена в Windows-приложении, то для ее старта необходимо будет так же запустить и само приложение (а так же нужно будет контролировать старт этого приложения при каждой перезагрузке системы). К тому же не всегда удобно, при входе на сервер видеть запущенные окна различных приложений, в контексте которых размещены службы WCF.
Но с другой стороны, в зависимости от функций службы WCF, размещение внутри Windows-приложения может быть единственным вариантом. Примером таких служб могут быть службы, работающие как узлы пиринговой сети, либо службы постоянно прослушивающие определенный адрес, на который клиенты отправляют сообщения.
В этой статье помимо самого способа размещения службы WCF я остановлюсь более детально и на самом процессе ее создания (чего я не сделал в первой статье). Для примера я создам WCF службу, которая может быть запущена как сервис ОС или как консольное приложение.
Создадим в Visual Studio новое консольное приложение (New->Project->Console Application). Я назвал его WcfHosting
Первым делом реализуем сам WCF-сервис внутри нашего консольного приложения:
1. Добавим в проект новый класс и назовем его WcfService.cs


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


Для того, чтобы начать работать с объектной моделью WCF нужно добавить в наш проект ссылку на сборку System.SeviceModel. Именно в пространстве имен этой сборки находятся методы создания и управления WCF-сервисами.


2. На втором шаге реализуем интерфейс методов (контракт) WCF-службы в файле WcfService.cs. В интерфейсе мы просто декларируем список всех методов, которыми будет обладать наша служба. Я назвал интерфейс IMyWcfService. В нем я определяю всего один метод GetDate(), который будет возвращать стоку текущей даты.
[ServiceContract]
public interface IMyWcfService
{
   [OperationContract]
   string GetDate();
}



Обратите внимание на атрибуты ServiceContract и OperationContract. WCFреализует свой функционал через контракты. Суть контракта WCF, как и суть обычного контракта из нашей повседневной жизни, заключается в определении, что будет делать наш сервис, какими возможностями он будет обладать. Для WCF контракт - это обычный интерфейс, в котором описаны все методы этой службы. Для того чтобы служба знала, что какой-либо интерфейс является контрактом, перед ним ставится атрибут ServiceContract (контракт о службе), а, соответственно, перед каждым его методом ставится атрибут OperationContract(контракт об операции).


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

public class MyWcfService : IMyWcfService
{

   #region IMyWcfService Members

   public string GetDate()
   {
      return DateTime.Now.ToString();
   }

   #endregion
}

3. Теперь все готово для реализации самой WCF-службы. В файле WcfService.cs создадим новый класс WcfService. Этот класс будет отвечать за создание, запуск и остановку службы WCF. Данный класс я реализовал на основе шаблона проектирования Singleton. Более подробно с шаблонами проектирования, можно ознакомиться по этой ссылке: http://msdn.microsoft.com/ru-ru/windows/gg543141.
Почему Singleton? Суть выбора в том, что чтобы не усложнять наше приложение я буду размещать только одну службу, доступную по определенному адресу. Используя этот шаблон, я на объектном уровне ограничиваю возможность создания нескольких экземпляров класса службы и тем самым гарантирую, что в рамках приложения будет работать только одна служба WCF. (На самом деле существует возможность разместить несколько служб WCF в контексте одного приложения, но тогда для каждй службы в отдельности нужно на программном уровне прописывать адреса доступа и их оконечные точки)
/*
     * Класс реализации запуска WCF-сервиса. 
     * Реализован с использованием шаблона Singleton
     */

public sealed class WcfService
{
   private static WcfService _WcfService;
   private ServiceHost _SvcHost;

   public static WcfService Service
   {
      get
      {
         _WcfService = _WcfService ?? new WcfService();
         return _WcfService;
      }
   }

   // Конструктор по умолчанию определяется как private
   private WcfService()
   {
      // Регистрация сервиса и его метаданных
      _SvcHost = new ServiceHost(typeof(MyWcfService));
   }

   public void Start()
   {
      _SvcHost.Open();
   }

   public void Stop()
   {
      _SvcHost.Close();
   }
}


Класс содержит статический закрытый член экземпляра этого же класса, а так же свойство, при обращении к которому происходит проверка закрытого члена _WcfService и в случае, если он не определен - происходит вызов конструктора. Сам конструктор тоже является закрытым. Это гарантирует невозможность создания объекта нашего класса извне.
Вторым закрытым членом является переменная _SvcHost с типом ServiceHost. Это и есть наша служба WCF. В закрытом конструкторе происходит создание рабочего экземпляра службы. Обратите внимание, что в конструктор мы передаем контракт, тем самым сообщая службе, что она должна предоставлять для клиентов методы этого контракта.
Так же класс службы содержит два метода: Start() - запуск WCF службы и Stop() - ее остановка. Вот и все, класс службы реализован.
4. Последним шагом создания WCF-службы является определение базового адреса, по которому она будет доступна и оконечных точек (endpoints), то есть каналов, по которым мы можем доступиться к службе. Все эти настройки я вынес в конфигурационный файл. Использование настроек в конфигурационном файле делает сервис намного гибче и масштабируемей (не нужна перекомпиляция программы при каждом изменении настроек).
Добавим в наш проект файл конфигурации app.config. Следующий листинг показывает определение настроек службы WCF в конфигурационном файле:

  
    
      
        
          
            
          
        
        
        
      
    
    
      
        
          
        
      
    
  



При запуске служба просматривает конфигурационный файл приложенияapp.config (web.config) и ищет тег system.serviceModel. В этом теге определяются следующие настройки:
  • Тег service  определяет имя службы (пространство имен и имя контракта), а так же имя так называемой конфигурации поведения службы. Ниже в настройках присутствует тег behaviors. На самом деле, в этом теге имеется масса интересных настроек поведения служб WCF, о которых лучше подробно поговорить в отдельной статье. Здесь же я использую этот тег в конфигурации только для того, чтобы включить в нашу службу описание ее методов, что позволяет получать полную информацию о службе WCF клиентскому приложению (стандартное описание на языке WSDL). 
  • Тег host содержит список базовых адресов службы. Я определяю адрес на основе протокола http для доступа к методам службы
  • Далее определяются теги оконечных точек (endpoints). Обратите внимание - я определил две точки. Первая точка использует привязку basicHttpBinding и контракт WcfHosting.IMyWcfService. А вторая точка используется для получения описания службы (о чем говорилось выше). Такая точка называется точка обмена метаданными (mex). Она имеет жестко заданные в объектной модели WCF адрес mex (это относительный адрес от базового, определенного в теге host), привязку (mexHttpBinding) и контракт (IMetadataExchange). Наличие такой точки в службе позволяет клиенту получить подробное описание всех методов службы, их типов и возвращаемых значений. Среда Visual Studio позволяет очень легко получить описание службы через эту точку. Ниже я приведу пример как это можно сделать.
Все, наша служба полностью готова. Теперь остается только ее разместить внутри нашего консольного приложения. Как я ранее уже писал, консольное приложение будет иметь возможность простого запуска, а так же может быть установлено как сервис ОС Windows. Для того, чтобы наше приложение могло быть установлено и запущено как сервис ОС, в проект нужно добавить элемент WindwosService. Я назвал его WinService.cs.
Класс WinService наследуется от родительского класса ServiceBase и имеет дваoverride-метода OnStart и OnStop. Первый срабатывает, когда сервис стартует, второй - когда останавливается. Определим в классе закрытый член нашей службы WCF. В методе OnStart пропишем запуске WCF-службы, в OnStop - ее остановке:

partial class WinService : ServiceBase
{
   private WcfService _Wcf;

   public WinService()
   {
      InitializeComponent();
   }

   protected override void OnStart(string[] args)
   {
      try
      {
          _Wcf = WcfService.Service;
          _Wcf.Start();
      }
      catch (Exception ex)
      {
          EventLog.WriteEntry(ex.Message,   System.Diagnostics.EventLogEntryType.Information);
      }
    }

    protected override void OnStop()
    {
       if (_Wcf != null)
       { _Wcf.Stop(); }
    }
}

Код сервиса готов! Теперь определим установщик для сервиса Windows. Самый простой способ это следать - это кликнуть по элементу WinService и в окне пустого дизайнера открыть правой кнопкой мыши контекстное меню и выбрать пункт Add Installer.
Это автоматически добавит в проект файл ProjectInstaller.cs. В дизайнере этого файла будут определены два элемента: Service (содержит имя сервиса, его описание, а так же режим запуска) и Process (процесс, в контексте которого будет запущен сервис). Переименуйте их как вам будет угодно и в свойствах Service укажите имя, описание и тип запуска сервиса.
Последний этап - реализуем запуск WCF-службы в режиме консоли и в режиме сервиса Windows. Откроем в проекте файл Program.cs и напишем следующий код:
static void Main(string[] args)
{
   // Если запускает пользователь сам
   if (Environment.UserInteractive)
   {
      WcfService s = WcfService.Service;
      s.Start();

      while (Console.ReadLine().ToLower() != "exit") { }
      s.Stop();
      return;
   }
   else
   {
     ServiceBase[] ServicesToRun;
     ServicesToRun = new ServiceBase[] { new WinService() };
     ServiceBase.Run(ServicesToRun);
   }
}


При запуске программы пользователем (проверяем переменную Environment.UserInteractive) мы вызываем наш Singleton-класс, где реализована служба WCF и вызываем метод Start(). В случае же запуска приложения в контексте сервиса Windows  мы вызываем метод Run класса ServiceBase. 
Все! Чтобы сервис появился в оснастке Service Control Management воспользуйтесь утилитой InstallUtil, входящей в пакет .NET SDK utility. С помощью этой утилиты можно установить/удалить любой сервис в оснастку Service Control Management.
В итоге мы получили следующее: WCF-служба размещена в контексте консольного приложения и сервиса ОС, доступна по адресу http://localhost:9000/WcfHosting, а ее описание доступно по адресу http://localhost:9000/WcfHosting/mex. Служба работает через протокол http.
Обратиться к службе очень просто. Для этого в любом проекте Visual Studio кликните правой кнопкой мыши по элементу Service Reference и выберите пунктAdd Service Reference.

В открывшемся окне введите http://localhost:9000/WcfHosting/mex и нажмите Go:
Итак, среда Visual Studio видит службу WCF, а так же все ее методы. Это все доступно, благодаря оконечной точке mex, о которой я писал выше. Укажите название пространство имен (я указал WcfServiceReference) и нажмите на кнопку Ок. Visual Studio создаст класс-обертку для методов службы, и вызвать метод станет проще простого:
WcfServiceReference.MyWcfServiceClient wcf = 
  new WcfHosting.WcfServiceReference.MyWcfServiceClient();
wcf.Open();
string date = wcf.GetDate();
wcf.Close();

3 комментария:

  1. После всего вышеперечисленного при запуске консольного приложения падает ошибка :
    The type initializer for 'System.ServiceModel.Diagnostics.TraceUtility' threw an exception.

    на строке _SvcHost = new ServiceHost(typeof(MyWcfService));

    С конфигом надоколдовать?!

    ОтветитьУдалить
  2. Этот комментарий был удален автором.

    ОтветитьУдалить
  3. Сделал все по инструкции, служба запустилась, но в журнале событий Windows увидел сообщение Инициализатор типа "System.ServiceModel.Diagnostics.TraceUtility" выдал исключение. Прошу помочь. ОС: Windows7

    ОтветитьУдалить