Совсем недавно мне пришлось работать над приложением, которое собирает данные из внешних систем, обрабатывает их и отправляет в центральную базу данных. Это приложение WinForms с несколькими экранами для настройки и ведения журнала. Я искал разные подходы к созданию новой версии парсера и углублялся в варианты использования службы Windows.
Я забыл, откуда взялась точная идея, но в какой-то момент я подумал: а что, если я смогу использовать Blazor в качестве пользовательского интерфейса для службы Windows. Затем я мог настраивать, запускать и останавливать, в основном делать все что угодно с этим сервисом с графическим интерфейсом. Я также читал кое-что о службах systemd с .NET, поэтому я мог бы даже создать кросс-платформенную версию (не то, чтобы Linux был нужен, но я сам использую Linux, я бы тоже хотел попробовать).
Что ж, через несколько часов у меня все заработало, и в этом посте я покажу вам, как создать кроссплатформенную службу, которую можно установить в Windows как службу и в Linux как компонент systemd и управлять ею с помощью Blazor.
Существует много информации о том, как запустить проект .NET как службу в Windows и Linux (насколько я знаю, Mac пока не поддерживается). Я предоставлю пример проекта на GitHub и покажу здесь только некоторые основы.
Наиболее интересной частью для меня является размещение приложения Blazor Server с Kestrel в качестве службы как в Windows, так и в Linux. Это дает безграничные возможности в управлении сервисом, причем не из самой системы, а удаленно.
Управление сервисом с помощью Blazor
Сначала мы создаем обычный проект Blazor Server. Я сохраняю проект как есть и просто добавляю несколько классов, чтобы продемонстрировать использование Blazor в службе.
Добавление фоновой службы
Создайте класс с именем CustomBackgroundService
. Я использую BackgroundService в качестве базового класса, но я также могу реализовать IHostedService. Более подробную информацию о различных типах можно найти здесь.
Эта служба просто регистрирует, а затем ждет 5 секунд, чтобы имитировать процесс, который выполняется некоторое время:
public class CustomBackgroundService : BackgroundService { public bool IsRunning { get; set; }
private readonly ILogger<CustomBackgroundService> _logger;
public CustomBackgroundService(ILogger<CustomBackgroundService> logger) => _logger = logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { try { _logger.LogInformation($"{nameof(CustomBackgroundService)} starting {nameof(ExecuteAsync)}"); IsRunning = true; while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation($"{nameof(CustomBackgroundService)} running {nameof(ExecuteAsync)}"); await Task.Delay(5000); } IsRunning = false; _logger.LogInformation($"{nameof(CustomBackgroundService)} ending {nameof(ExecuteAsync)}"); } catch (Exception exception) { _logger.LogError(exception.Message, exception); } finally { IsRunning = false; } } }
Регистрация и добавление размещенного сервиса:
services
.AddLogging(logging => logging.AddConsole())
.AddSingleton<WeatherForecastService>()
.AddSingleton<CustomBackgroundService>()
.AddHostedService(serviceCollection => serviceCollection.GetRequiredService<CustomBackgroundService>());
Я добавил новую страницу Razor и добавил ее в меню (Pages/Service.razor):
@page "/Service" @inject CustomBackgroundService _customBackgroundService
<h3>Service</h3>
<p><div hidden="@HideIsRunning">Running</div></p>
<button name="startButton" class="btn btn-primary" @onclick="Start">Start</button> <button class="btn btn-primary" @onclick="Stop">Stop</button>
@code {
private bool _isRunning { get; set; } public bool HideIsRunning => !_isRunning; protected override void OnInitialized() { _isRunning = _customBackgroundService.IsRunning; base.OnInitialized(); }
private async Task Start() { if(!_customBackgroundService.IsRunning) await _customBackgroundService.StartAsync(new System.Threading.CancellationToken()); _isRunning = _customBackgroundService.IsRunning;
} private async Task Stop() { if(_customBackgroundService.IsRunning) await _customBackgroundService.StopAsync(new System.Threading.CancellationToken()); _isRunning = _customBackgroundService.IsRunning; } }
Добавление нового пункта меню в приложение Blazor по умолчанию путем изменения «Shared/NavMenu.razor»:
<div class="nav-item px-3">
<NavLink class="nav-link" href="Service">
<span class="oi oi-pulse" aria-hidden="true"></span> Service
</NavLink>
</div>
При отладке проекта это должно быть видно:
вы можете запускать и останавливать службу и проверять консоль вашей IDE, чтобы увидеть вывод:
Запуск и установка сервиса
Чтобы запустить приложение как службу, необходимо добавить следующий код в Program.cs
:
public static void Main(string[] args) { var isService = !(Debugger.IsAttached || args.Contains("--console"));
if (isService) Directory.SetCurrentDirectory(Environment.ProcessPath!);
var builder = CreateHostBuilder(args.Where(arg => arg != "--console").ToArray());
if (isService) { if (OperatingSystem.IsWindows()) builder.UseWindowsService(); else if (OperatingSystem.IsLinux()) builder.UseSystemd(); else throw new InvalidOperationException( $"Can not run this application as a service on this Operating System"); }
builder.Build().Run(); }
Затем установите следующие пакеты NuGet:
Служба .NET в Windows
Сначала опубликуйте приложение в папке. Обязательно создайте правильный профиль публикации для Windows. Кроме того, подумайте, нужно ли вам публиковать его: Framework-Dependent (платформа .NET должна быть установлена на хост-компьютере) или Self-Contained (все необходимое включено, как здорово!):
После публикации откройте командную строку Powershell, перейдите в каталог, содержащий только что опубликованный сервис, и выполните следующие команды:
- New-Service -Name «Фоновая служба Blazor» -BinaryPath .\BlazorBackgroundservice.BlazorUI.exe
- Start-Service -Name «BlazorBackgroundService»
Я мог бы записывать логи в регистратор событий, но решил записывать простые логи в текстовый файл. Когда вы заглянете в каталог службы, вы должны увидеть файл журнала log****.txt
. Посмотрите в файл журнала, чтобы увидеть, работает ли служба. При переходе по URL-адресу, указанному в файле журнала, имейте в виду, что порт HTTPS может не работать, поскольку не установлены действительные сертификаты SSL.
Служба .NET в Linux
То же, что и для Windows: опубликуйте приложение в папке, используя правильную конфигурацию публикации. Я могу протестировать приложение, добавив --console
в командную строку:
Чтобы установить его как службу, я создал файл /etc/systemd/system/blazorbackgroundservice.service
:
[Unit] Description=Blazor Background Service
[Service] Type=Notify ExecStart=/home/jacob/blazorbackgroundservice/linux/BlazorBackgroundService.BlazorUI
[Install] WantedBy=multi-user.target
Выполните следующие команды:
- sudo systemctldaemon-reload
- статус sudo systemctlblazorbackgroundservice
- запуск sudo systemctl blazorbackgroundservice
- состояние sudo systemctl blazorbackgroundservice
Оно работает! Проверьте выходные данные состояния для URL-адреса веб-сайта Blazor и перейдите на сайт, чтобы проверить, работает ли он.
Вы даже можете автоматически запустить службу, выполнив следующую команду:
- sudo systemctl включает blazorbackgroundservice
При следующей перезагрузке служба запустится автоматически.
Заключение
Хотя это короткий пост, в котором нет ничего особенного, мне самому не терпится реализовать некоторые сервисы таким образом. Я все еще думаю о некоторых вариантах использования, но я могу представить сервис, который собирает данные, обрабатывает их и отправляет в онлайн-сервис. Есть мысли о других вариантах использования? Пожалуйста, дайте мне знать!