In this post I will show you how creating a cross-platform and managing a .NET Service with Blazor that can be installed on Windows (Service) and Linux (systemd).
There is a lot of information on how to run a .NET project as a service, on Windows and on Linux (Mac is not supported yet). I will provide a sample project on GitHub and will only show some of the basics here.
The most interesting part for me is hosting a Blazor Server application with Kestrel as a service on both Windows and Linux. This gives endless possibilities in managing the service, not even from the system itself but also remotely.
Managing service with Blazor
First we create a normal Blazor Server project. I keep the project as-is and just add a few classes to demonstrate the use of Blazor in the service.
Adding the background service
Create a class called CustomBackgroundService
. I use the BackgroundService as a base class but I could also implement IHostedService. More information about the different types can be found here.
This service is just logging and then waiting for 5 seconds to simulate a process that runs for a while:
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;
}
}
}
Registering and adding the hosted service:
services
.AddLogging(logging => logging.AddConsole())
.AddSingleton<WeatherForecastService>()
.AddSingleton<CustomBackgroundService>()
.AddHostedService(serviceCollection => serviceCollection.GetRequiredService<CustomBackgroundService>());
I added a new Razor page and added it to the menu (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;
}
}
Adding a new menu item to the default Blazor application by changing `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>
When debugging the project this should be visible:
you can start and stop the service and check the console of your IDE to see the output:
Running and installing the service
To run the application as a Service the following code has to be added to the 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();
}
Next, install the following Nuget packages:
.NET Service on Windows
First, publish the application to a folder. Make sure to create the correct publish profile for Windows. Also, consider if you need to publish it Framework-Dependent (the .NET framework has to be installed on the host machine) or Self-Contained (everything needed is included, how cool is that!):
After publishing, open a Powershell command line, go to the directory conaining the newly published service and execute the following commands:
- New-Service -Name “Blazor Background Service” -BinaryPath .\BlazorBackgroundservice.BlazorUI.exe
- Start-Service -Name “BlazorBackgroundService”
I could write logs to the Event Logger but I decided to write simple logs to a text file. When you look into the directory of the service you should see a logfile log****.txt
. Look into the logfile to see if the service is running. When going to the url’s provided in the logfile be aware that the https port might not work because there are no valid SSL certificates installed.
.NET Service on Linux
Same as for Windows: publish the application to a folder, using the correct publish configuration. I can test the application by adding --console
to the command line:
To install it as a service I created the file in/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
Run the following commands:
sudo systemctl daemon-reload
sudo systemctl status blazorbackgroundservice
sudo systemctl start blazorbackgroundservice
sudo systemctl status blazorbackgroundservice
It works! Check the status output for the url of the Blazor website and browse to the site to check if it works.
You can even auto-start the service by running the following command:
sudo systemctl enable blazorbackgroundservice
Next time you reboot, the service will automatically start.