Кроме консольных и графических приложений на C# мы можем разрабатывать сервисы - некоторые приложения, которые работают в виде фонового процесса и выполняют некоторую работу. Такие сервисы могут использоваться, например, в фоновой обработке задач по очереди или через промежутки времени. Примером таких сервисов можно назвать, например, службы Windows. Рассмотрим, как мы можем создавать простейшие сервисы.
Для создания сервисов Visual Studio и .NET CLI предоставляют специальные шаблоны. В Visual Studio это шаблон Worker Service:
.NET CLI для создания сервисов предоставляет шаблон worker
dotnet new worker
Создадим проект данного типа. Пусть он будет называться WorkerServiceApp. По умолчанию созданный проект будет выглядеть следующим образом:
Рассмотрим его базовую структуру:
Папка Properties
содержит файл launchSettings.json, который определяет параметры запуска проекта
Файл appsettings.json определяет конфигурацию приложения
Файл appsettings.Development.json определяет конфигурацию приложения для стадии разработки
Файл Program.cs содержит определение класса Program (по умолчанию без явного определения этого класса), который является точкой входа в программу
Файл Worker.cs определяет собственно класс сервиса, который по умолчанию называется Worker
Файл workerserviceapp.cproj - стандартный файл с расширением csproj, который определяет конфигурацию проекта
Откроем файл конфигурации проекта с расширением csproj
он выглядит примерно следующим образом:
<Project Sdk="Microsoft.NET.Sdk.Worker"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <UserSecretsId>dotnet-WorkerServiceApp-21388fcc-d283-4fc4-ab25-5dfa3ffcbfb3</UserSecretsId> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /> </ItemGroup> </Project>
Здесь мы видим, что и в качестве фреймворка проекта используется подсистема Microsoft.NET.Sdk.Worker
, которая предназначена специально для создания сервисов.
Также в отличие от стандартного проекта консольного приложения здесь мы видим, что в проект добавлен по умолчанию Nuget-пакет "Microsoft.Extensions.Hosting", функциональность которого позволяет развертывать и запускать сервисы.
И также среди настроек имеет элемент <UserSecretsId>
который представляет секретный код для текущего проекта, который генерируется на основе значения Guid.
Рассмотрим ключевые компоненты программы - класс сервиса Worker и запускающий его хост.
Сервис должен представлять объект IHostedService. Класс Worker наследуется от BackgroundService, который является реализацией IHostedService:
namespace WorkerServiceApp { public class Worker : BackgroundService { private readonly ILogger<Worker> _logger; public Worker(ILogger<Worker> logger) { _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); await Task.Delay(1000, stoppingToken); } } } }
Через конструктор класс Worker получает объект ILogger для выполнения логгирования (ILogger по умолчанию передается в приложение через систему
внедрения зависимостей). А в методе ExecuteAsync()
применяется бесконечный цикл (пока не будет установлен токен отмены), в котором
раз в секунду с помощью ILogger выполняется некоторая запись в лог. По умолчанию логгирование идет на консоль.
Рассмотрим, как происходит запуск и выполнение сервиса Worker. Как и в других проектах на C# выполнение начинаяется с кода файла Program.cs, который неявно определяет класс Program:
using WorkerServiceApp; // пространство имен класса Worker IHost host = Host.CreateDefaultBuilder(args) .ConfigureServices(services => { services.AddHostedService<Worker>(); }) .Build(); host.Run();
Сервис развертывается и запускается в рамках некоторого хоста - объекта IHost. Для создания объекта IHost применяется объект IHostBuilder. А чтобы создать IHostBuilder, применяется метод Host.CreateDefaultBuilder().
IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args);
Далее нам надо добавить наш сервис Worker. И для этого у IHostBuilder вызывается метод ConfigureServices().
В ConfigureServices передается делегат Action<IServiceCollection>
, который регистрирует сервисы. Он принимает
параметр типа IServiceCollection
- коллекция сервисов, в которую необходимо добавить наш сервис.
Для добавления (или можно сказать регистрации) сервиса у коллекции сервисов вызывается метод AddHostedService()
:
hostBuilder = hostBuilder.ConfigureServices(services => services.AddHostedService<Worker>());
Метод AddHostedService()
типизируется типом IHostedService - то есть по сути типом сервиса, который должен предоставлять реализацию интерфейса IHostedService.
По умолчанию это автосгенерированный сервис Worker. Стоит отметить, что сервис добавляет как синглтон.
И в конце необходимо построить объект Host с помощью метода Build():
IHost host = hostBuilder.Build();
После создания хоста запускаем его методом Run(), вместе с этим запускаются и все сервисы хоста.
host.Run();
Возможно, это не самый показательный сервис, тем не менее его можно рассматривать как стартовую точку для работы с сервисами. Запустим проект и увидим логгирование на консоль, которое выполняет наш сервис:
Консоль сервиса сначала отображает некоторую базовую информацию и затем собственно идет вывод сообщений логгера.