Кому приходилось писать сервисы windows следуя заветам Microsoft осознаёт тяжесть (“корпоративность”) подхода. Долго, муторно, неудобно для тестирования, для отладки требуется ultimate версия студии. Возникает ощущение бега с препятствиями. Видимо схожие мысли посетили и авторов Mass Transit и Fubu Project. Они уже решили проблему создания сервисов, убрав все препятствия с трассы. Спешите освоить - Topshelf.
Раздел документации на сайте не блещет полнотой, что поначалу несколько расстраивает. Хорошая новость: ее достаточно. Topshelf может работать в двух режимах:
- installing - создание полноценной службы windows.
- shelving – хостинг сервисов в отдельном домене приложения.
Installing
Этот режим используется для создания полноценной службы windows, которой можно управлять через Service Manager. Идеологически Topshelf выполняет две операции:
- Берёт на себя сложности связанные инфраструктурными проблемами хостинга windows-сервиса.
- Хостинг бизнес-сервиса, выполняющего работу самого сервиса.
Эти две функции нам и надо прописать в коде:
static class Program
{
static void Main()
{
HostFactory.Run(x =>
{
//Настройка windows-сервиса.
x.UseNLog();
x.BeforeStartingServices(() => Console.WriteLine("[FooHost] Preparing to start host services"));
x.AfterStartingServices(() => Console.WriteLine("[FooHost] All services have been started"));
x.AfterStoppingServices(() => Console.WriteLine("[FooHost] All services have been stopped"));
x.SetServiceName("FooHost");
x.SetDisplayName("FooHost");
x.SetDescription("FooHost ");
//Сейчас запускаем как Local System, но можно запустить под любым пользователем.
x.RunAsLocalSystem();
x.EnableDashboard();
//Настройка нашего бизнес-сервиса.
x.Service<BarService>(y =>
{
y.SetServiceName("FooService");
y.ConstructUsing(() => new FooService());
y.WhenStarted(foo => foo.Start());
y.WhenStopped(foo => foo.Stop());
});
});
Logger.Shutdown();
}
}
Не буду подробно останавливаться на коде, он говорит сам за себя. Добавлю: Topshelf поддерживает работу с двумя наиболее популярными логгерами – log4net и NLog2. Расширенное описание командной строки есть в исходнихах.
Преимущество режима по сравнению с обычным сервисом:
- Нет нужды наследовать и вручную писать ServiceInstaller и ServiceBase, что повышает тестопригодность.
- Возможность запускать сервис в режиме консоли “из коробки”, что облегчает его отладку.
- Внятное (даже без документации) API.
Создание нового windows-сервиса легко, но есть лучший способ хостить сервисы:
Shelving
В большинстве случаев нам не нужен полноценный windows-сервис, но мы вынуждены его писать, чтобы удовлетворить требования Windows. Отсюда вытекает логичная мысль: сделать инфраструктурный windows-сервис, который бы мог хостить бизнес-сервисы. Эту идею и реализует Topshelf.
Для начала нам понадобиться Topshelf.Host.exe. Его нет в nuget, но можно найти в исходниках проекта (я использую несколько модифицированную версию). Topshelf.Host.exe является таким же windows-сервисом, как и описанный в предыдущем разделе, и следовательно его можно запускать и как сервис, и как консоль. После запуска сервис автоматически создать подпапку Services. Topshelf мониторит эту папку, и при обнаружении изменений по необходимости подгружает
Для дальнейшей работы необходимо реализовать Bootstrapper для своего сервиса, и сконфигурировать параметры запуска. У меня это выглядит так:
public class MazayBootstrapper : Bootstrapper<MazayService>
{
public void InitializeHostedService(IServiceConfigurator<MazayService> cfg)
{
cfg.SetServiceName("Mazay");
cfg.Named("Mazay");
cfg.HowToBuildService(x => new MazayService(SectionHandler.Instance.Folders));
cfg.SetServiceName("Mazay");
cfg.WhenStarted(x => x.Start());
cfg.WhenStopped(x => x.Stop());
}
}
После этого надо создать подпапку с названием сервиса в папке Service\FooService, и скопировать в нее наш сервис. Согласно описанию, сервис должен был заработать. Но заработал только после продолжительных мытарств. Что еще раз напомнило мне о древней мудрости: если ничего не помогает – прочитайте наконец документацию. Если и это не помогает – внимательно прочитайте документацию
Дело оказалось в двух проблемах:
Добавить конфигурационный файл сервиса. Внимание: название файла должно соответствовать шаблону ‘имя_папки_сервиса.dll’, в которую мы положили сервис. Для моего случая это FooService.config, а не FooService.dll.config. Topshelf запускает сервис в отдельном домене приложения, и принудительно назначает ему этот конфигурационный файл.
В конфигурационном файле необходимо указать ссылку на Bootstrapper сервиса:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section
name="ShelfConfiguration"
type="Topshelf.Shelving.ShelfConfiguration, TopShelf" />
</configSections>
<ShelfConfiguration
Bootstrapper="Mazay.MazayBootstrapper, Mazay" />
</configuration>
После этого сервис заработал в штатном режиме.
Dashboard
Маленький бонус от создателей. Topshelf содержит небольшой проект Dashboard, который позволяет из браузера посмотреть, и управлять рабой сервисов. Для его подключения понадобиться nuget пакет Topshelf.DashBoard, и вписать строчку и вызвать метода расширения x.EnableDashboad() при конфигурации сервиса. Dashboard не поддаётся конфигурации, и найти его можно по адресу http://localhost:8483/TopShelf/Topshelf.Host

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