четверг, 4 июля 2013 г.

Размещение WCF библиотеки в службе Windows.

В этой статье хочу рассказать как правильно разместить службу WCF (Windows Communication Foundation) в службе Windows 7.

Создадим новый проект WCF Service Library.

В Solution Explorer находим файл app.config, нажимаем на нем правой кнопкой и выбираем Edit WCF Configuration.

Переходим к пункту Host. Выбираем базовый адрес и нажимаем кнопку "Edit"

И вписываем адрес: net.tcp://localhost:8732/Service1/

Должно выглядеть вот так:

На пункте Binding нажимаем правую кнопку и выбираем New Binding Configuration

Далее выбираем netTcpBinding


Переходим к пункту serviceMetadata и меняем значение HttpGetEnabled на False.

Переходим к пункту Service Endpoint и меняет Binding на netTcpBinding


В BindingConfiguration ставим NewBinding0

Переходим ко второму пункту (Empty Name) и в Binding указываем mexTcpBinding

На этом настройка конфигурации закончена. Нажимаем File - Save.

В проект добавляем службу Windows.

На дизайнере службы нажимаем правую кнопку и добавляем установщик (Add Installer).
 

Выбираем появившийся serviceProcessInstaller1 и меняет Account с User на LocalSystem

Добавляем в проект WindowsService1 ссылку на проект WcfLibraryService (Add reference)
Выбираем serviceInstaller1, переключаемся на вкладку событий, и дважды щелкаем по событию AfterInstall.

Переходим к коду, добавляем using System.Management;
В процедуру serviceInstaller1_AfterInstall вписываем следующий код:

Code Snippet
  1. private void serviceInstaller1_AfterInstall(object sender, InstallEventArgs e)
  2. {
  3.     ConnectionOptions coOptions = new ConnectionOptions();
  4.     coOptions.Impersonation = ImpersonationLevel.Impersonate;
  5.  
  6.     ManagementScope mgmtScope = new System.Management.ManagementScope(@"root\CIMV2", coOptions);
  7.     mgmtScope.Connect();
  8.  
  9.     using (ManagementObject wmiService = new ManagementObject("Win32_Service.Name='" + serviceInstaller1.ServiceName + "'"))
  10.     {
  11.         ManagementBaseObject InParam = wmiService.GetMethodParameters("Change");
  12.         InParam["DesktopInteract"] = true;
  13.         wmiService.InvokeMethod("Change", InParam, null);
  14.     }
  15. }

Это действие позволит службе взаимодействовать с рабочим столом пользователя.
Хотя в Windows 7 это уже не актуально. И только добавляем больше проблем.

Весь код файла выглядит так:

Code Snippet
  1. using System.ComponentModel;
  2. using System.Configuration.Install;
  3. using System.Management;
  4.  
  5.  
  6. namespace WindowsService1
  7. {
  8.     [RunInstaller(true)]
  9.     public partial class ProjectInstaller : System.Configuration.Install.Installer
  10.     {
  11.         public ProjectInstaller()
  12.         {
  13.             InitializeComponent();
  14.         }
  15.  
  16.         private void serviceInstaller1_AfterInstall(object sender, InstallEventArgs e)
  17.         {
  18.             ConnectionOptions coOptions = new ConnectionOptions();
  19.             coOptions.Impersonation = ImpersonationLevel.Impersonate;
  20.  
  21.             ManagementScope mgmtScope = new System.Management.ManagementScope(@"root\CIMV2", coOptions);
  22.             mgmtScope.Connect();
  23.  
  24.             using (ManagementObject wmiService = new ManagementObject("Win32_Service.Name='" + serviceInstaller1.ServiceName + "'"))
  25.             {
  26.                 ManagementBaseObject InParam = wmiService.GetMethodParameters("Change");
  27.                 InParam["DesktopInteract"] = true;
  28.                 wmiService.InvokeMethod("Change", InParam, null);
  29.             }
  30.         }
  31.     }
  32. }

В свойствах проекта WcfServiceLibrary на последней вкладке снимаем галочку Start WCF Service Host.

Копируем файл app.config в проекте WcfServiceLibrary

Этот файл нужно вставить в проект WindowsService

Идея такая, что файл app.config должен быть одинаковый в этих двух проектах.
Потому что служба WCF будет вызываться из службы Windows, то в проекте службы Windows должны находиться параметры WCF службы. Эти параметры служба Windows (WindowsService1) не может достать из файла app.config проекта WcfServiceLibrary.

В проекте WindowsService1 на файле Service1 нажимаем правой кнопкой и выбираем View Code


Добавляем ссылку на сборку System.ServiceModel;
И в коде вписываем using System.ServiceModel;

В процедуре OnStart вписываем код:

Code Snippet
  1. protected override void OnStart(string[] args)
  2. {
  3.     if (myServiceHost != null)
  4.     {
  5.         if (myServiceHost.State == CommunicationState.Opened) myServiceHost.Close();
  6.     }
  7.  
  8.     try
  9.     {
  10.         myServiceHost = new ServiceHost(typeof(WcfServiceLibrary1.Service1));
  11.         myServiceHost.Open();
  12.     }
  13.     catch (Exception ex)
  14.     {
  15.         EventLog.WriteEntry(this.ServiceName, ex.Message);
  16.     }
  17. }

Это позволит службе Windows при запуске запустить хостинг WCF службы
В процедуре OnStop вписываем код:

Code Snippet
  1. protected override void OnStop()
  2. {
  3.     if (myServiceHost != null)
  4.     {
  5.         myServiceHost.Close();
  6.         myServiceHost = null;
  7.     }
  8. }

В итоге общий код файла выглядит так:

Code Snippet
  1. using System;
  2. using System.Diagnostics;
  3. using System.ServiceProcess;
  4. using System.ServiceModel;
  5.  
  6. namespace WindowsService1
  7. {
  8.     public partial class Service1 : ServiceBase
  9.     {
  10.         public Service1()
  11.         {
  12.             InitializeComponent();
  13.         }
  14.         private static ServiceHost myServiceHost = null;
  15.  
  16.         protected override void OnStart(string[] args)
  17.         {
  18.             if (myServiceHost != null)
  19.             {
  20.                 if (myServiceHost.State == CommunicationState.Opened) myServiceHost.Close();
  21.             }
  22.  
  23.             try
  24.             {
  25.                 myServiceHost = new ServiceHost(typeof(WcfServiceLibrary1.Service1));
  26.                 myServiceHost.Open();
  27.             }
  28.             catch (Exception ex)
  29.             {
  30.                 EventLog.WriteEntry(this.ServiceName, ex.Message);
  31.             }
  32.         }
  33.  
  34.         protected override void OnStop()
  35.         {
  36.             if (myServiceHost != null)
  37.             {
  38.                 myServiceHost.Close();
  39.                 myServiceHost = null;
  40.             }
  41.         }
  42.     }
  43. }

Добавляем новый проект Windows Forms. И делаем его запускаемым по-умолчанию (Set as Startup project).
Кидаем на форму кнопку и добавляем ссылку на службу (Add Service Reference)
Нажимаем кнопку Discover и выбираем службу Service1


В обработчик нажатия на кнопку вписываем код:

Code Snippet
  1. private void button1_Click(object sender, EventArgs e)
  2. {
  3.     ServiceReference1.Service1Client myService = new ServiceReference1.Service1Client();
  4.     MessageBox.Show(myService.GetData(123), "My Service");
  5.     myService.Close();
  6. }

Весь файл должен выглядеть так:

Code Snippet
  1. using System;
  2. using System.Windows.Forms;
  3.  
  4. namespace WindowsFormsApplication1
  5. {
  6.     public partial class Form1 : Form
  7.     {
  8.         public Form1()
  9.         {
  10.             InitializeComponent();
  11.         }
  12.  
  13.         private void button1_Click(object sender, EventArgs e)
  14.         {
  15.             ServiceReference1.Service1Client myService = new ServiceReference1.Service1Client();
  16.             MessageBox.Show(myService.GetData(123), "My Service");
  17.             myService.Close();
  18.         }
  19.     }
  20. }

Нажимаем F6 - для компиляции проекта. Ошибок быть не должно.
Открываем папку проекта WindowsService1


В эту папку нужно скопировать файл InstallUtil.exe
Чтобы его найти на вашем компьютере воспользуйтесь поиском.

Создадим файл InstallServiceAndStart.bat со следующим текстом:
InstallUtil.exe WindowsService1.exe
net start Service1

Создадим файл UninstallServiceAndStop.bat со следующим текстом:
net stop Service1
InstallUtil.exe /u WindowsService1.exe

В итоге папка должна выглядеть так:

Если вы используете Windows XP то для запуска/удаления службы можно пользоваться этими BAT файлами. Если же Windows 7 - то нужно сделать так:
В меню найдите пункт Microsoft Visual Studio 2010\Visual Studio tools

В этой папке есть "Open Visual Studio Command Prompt (2010)", нажмите на нем правой кнопкой мыши и выберите "Запуск от имени Администратора". Отобразится та же консоль.
Поменяйте текущую папку.

Для установки и запуска службы скопируйте содержимое файла InstallServiceAndStart.bat и вставьте в это окно и нажмите Enter (для Windows 7). Если в Windows 7 запускать файлы, то после каждого удаления службы Windows придется перезагружать компьютер.
Если у вас Windows XP то можно просто запускать батники.


Служба установлена и запущена.
Теперь нужно запустить приложение WindowsFormsApplication1. Возможно что Visual Studio не позволит запустить отладку, т.к.он не сможет заменить файл WindowsService1.exe потому, что он будет выполняться ОС. Я просто открыл папку и запустил WindowsFormsApplication1.exe


Нажимаем кнопку и через несколько секунд получаем сообщение


Это говорит о том, что наша служба Windows работает.
Если в службу вносите изменения то нужно произвести удаление службы.
Для этого в Windows XP просто запустите UninstallServiceAndStop.bat в Windows 7 нужно найти пункт Microsoft Visual Studio 2010\Visual Studio tools

В этой папке есть "Open Visual Studio Command Prompt (2010)", нажмите на нем правой кнопкой мыши и выберите "Запуск от имени Администратора". Поменяйте текущую папку на папку с файлом WindowsService1.exe. Скопируйте текст из файла