Написание воркер-сервисов на .NET часто сопряжено с написанием большого количества повторяющегося boilerplate-кода. Однажды мне это надоело и я попытался упростить этот процесс, перенеся часть бойлерплейта в отдельную библиотеку, которой и посвящена эта статья. Библиотека называется dotWork, у нее есть репозиторий на GitHub, а сборки выкладываются на NuGet. По большей части библиотека представляет собой обертку над BackgroundService, встроенным в .NET решением для написания воркеров. Если вкратце, dotWork упрощает следующие аспекты разработки воркеров: регистрацию работ (works); регистрацию соответствующих работам настроек; написание кода для повторения итераций. В моей практике именно эти три момента требуют практически шаблонного кода и тем не менее не имеют возможности стандартизации "из коробки". Для установки достаточно поставить NuGet пакет: Для использования библиотеки нам нужно создать один или несколько классов-работ (works), описывающих, что должен делать работник (worker). В контексте .NET работником выступает само приложение, либо хост, если приложение содержит несколько хостов. Для создания работы добавим в приложение новый класс, унаследованный от WorkBase: Теперь добавим метод, описывающий, из чего состоит работа. Работник будет вызывать его с определенной периодичностью, поэтому этот метод можно назвать итерацией: Важно! Чтобы dotWork нашла метод итерации, необходимо, чтобы он соответствовал следующим правилам: имел название ExecuteIteration; был помечен модификатором public; возвращал void или Task. Наша работа уже готова к использованию, но пока не делает ничего полезного. Чтобы сделать ее более полезной, мы можем внедрить в нее сервисы, то есть зависимости.  dotWork позволяет внедрять зависимости двумя способами. Выбор конкретного способа зависит от того, как зависимость зарегистрирована в контейнере. Внедрение синглтонов происходит привычным способом, то есть через конструктор работы. Каждая итерация, таким образом, будет использовать один и тот же экземпляр: Часто возникает необходимость внедрить не-singleton зависимости в работу. Типичный пример - база данных. Поскольку работа живет до остановки хоста, то внедрение в нее коннектора базы данных может привести к тому, что соединение с БД будет открыто с самого начала и до самого конца работы программы. Это может быть нежелательно, например, если работа короткая и выполняется раз в длительный промежуток времени. В таком случае, зависимость можно внедрить напрямую в метод итерации: Сервисы, внедренные таким образом, будут удалены после окончания итерации. dotWork создает новый scope на каждую итерацию. Помимо сервисов, в метод итерации можно также внедрить CancellationToken. Он сработает, когда хост начнет остановку Наконец, наша работа может делать что-то полезное. Однако, запустив приложение, можно обнаружить, что работа не выполняется. Это нормально, ведь она еще не зарегистрирована в контейнере. Пора ее зарегистрировать. dotWork позволяет зарегистрировать все работы сразу, регистрировать каждую работу отдельно, либо комбинировать оба подхода. Зарегистрировать одну отдельную работу можно, добавив следующий вызов в метод ConfigureServices HostBuilder-a:  Здесь первым дженерик-параметром является тип работы, а вторым - тип настроек работы. Важно, чтобы тип настроек, указанный при регистрации, совпадал с тем, который указан при наследовании работы от класса WorkBase.  На данном этапе наша работа полностью готова к запуску. Однако, если запустить ее, обнаружится, что итерация выполняется только один раз. Это нормально - настройка работы по-умолчанию (DefaultWorkOptions) устанавливает бесконечную задержку между итерациями. Мы можем изменить это, переопределив стандартную настройку: Теперь следующая итерация работы начнется через 10 секунд после окончания предыдущей. Вместо того, чтобы регистрировать каждую работу по-отдельности, мы можем зарегистрировать все работы сразу. Очевидно, что при этом у нас нет возможности настроить каждую работу, поэтому этот способ регистрации требует обязательного указания раздела конфигурации с настроками работ. Этот раздел должен представлять из себя словарь, где ключами являются названия классов работ. Легче всего продемонстрировать это на примере. Предcтавим, что у нас есть приложение с двумя работами: ExampleWork1 ExampleWork2 Файл appSettings.json, в таком случае, может выглядеть следующим образом: И мы можем зарегистрировать все наши работы одной строчкой кода: Мы также можем переопределить регистрацию любой отдельно взятой работы, вызвав метод явной регистрации после автоматической: Важно! Автоматическая регистрация работает только для работ, определенных в той же сборке, что и код, вызвавший метод регистрации. Работы из других сборок придется регистрировать вручную. Работа необязательно должна использовать DefaultWorkOptions в качестве настроек. Можно создать свой класс, унаследовав его от DefaultWorkOptions или даже напрямую от интерфейса IWorkOptions: Надеюсь, dotWork поможет сэкономить время при написании приложений-воркеров. Если после начала использования библиотеки Вы обнаружите, что в ней отсутствует какая-то важная ожидаемая функциональность, пожалуйста, откройте issue в репозитории GitHub. .NET бэкенд разработчик