Managing a .NET Service with Blazor

Blazor Background 2

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:

Blazor with running service - Managing a .NET Service with Blazor
Blazor with running service

you can start and stop the service and check the console of your IDE to see the output:

Output from the service - Managing a .NET Service with Blazor
Output from the service

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!):

.NET Launch Settings Profile - Managing a .NET Service with Blazor
.NET Launch Settings Profile

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
PowerShell registers the Blazor service - Managing a .NET Service with Blazor
PowerShell registers the Blazor service
  • Start-Service -Name “BlazorBackgroundService”
PowerShell starts the new service - Managing a .NET Service with Blazor
PowerShell starts the new service

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.

Logging on Windows
Logging on Windows

.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:

Running on Arch Linux
Running on Arch Linux

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
Stopped service
Stopped service
sudo systemctl start blazorbackgroundservice
sudo systemctl status blazorbackgroundservice
Running service

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.

Resources

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.