.NET Core Web App with multiple Background Services Periodic runs

Introduction

In this blog post, we'll explore how to build a .NET Core web application that includes background services. Background services are long-running tasks that run independently of the main application thread, making them ideal for tasks like monitoring, scheduling, and other asynchronous operations. We'll specifically focus on creating two background services: InstrumentationService and PeriodicService.

The InstrumentationService will run continuously, while the PeriodicService will run periodically based on a configurable interval.

Setting up the Project

Let's start by creating a new .NET Core web application project. Open your terminal or command prompt and run the following command:

dotnet new web -n BackgroundServicesDemo
cd BackgroundServicesDemo

This command creates a new .NET Core web application project named BackgroundServicesDemo. Navigate into the project directory.

Add appSettings.json modifications

"PeriodicServiceSettings": {
  "IntervalInMinutes": 5
}

Creating Background Services

Now, let's create the background services. We'll start by creating the InstrumentationService class, which will continuously log information.

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;

public class InstrumentationService : IHostedService, IDisposable
{
    private readonly ILogger<InstrumentationService> _logger;
    private Timer _timer;

    public bool IsRunning { get; private set; }

    public InstrumentationService(ILogger<InstrumentationService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Instrumentation Service is starting.- {0}", DateTime.Now);
        _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.Zero); // Adjust delay as needed
        IsRunning = true;
        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        _logger.LogInformation("Instrumentation Service is running.");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Instrumentation Service is stopping.- {0}", DateTime.Now);
        _timer?.Change(Timeout.Infinite, 0);
        IsRunning = false;
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

Next, let's create the PeriodicService class, which will run periodically based on a configurable interval.

public class PeriodicService : BackgroundService
{
    private readonly ILogger<PeriodicService> _logger;
    private readonly IConfiguration _configuration;
    private readonly InstrumentationService _instrumentationService;
    private bool _isRunning;
    public PeriodicService(
        ILogger<PeriodicService> logger,
        IConfiguration configuration,
        InstrumentationService instrumentationService)
    {
        _logger = logger;
        _configuration = configuration;
        _instrumentationService = instrumentationService;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var intervalInMinutes = _configuration.GetValue<int>("PeriodicServiceSettings:IntervalInMinutes");

        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Periodic Service is running. - {0}", DateTime.Now);

            // Stop Instrumentation Service
            _logger.LogInformation("Stopping Instrumentation Service... - {0}", DateTime.Now);
            await StopInstrumentationService();

            // Do Periodic Service Work
            await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); // Simulated work
            _logger.LogInformation("Periodic Service work is done. - {0}", DateTime.Now);

            // Restart Instrumentation Service
            _logger.LogInformation("Restarting Instrumentation Service... - {0}", DateTime.Now);
            await StartInstrumentationService();

            // Wait for next interval
            await Task.Delay(TimeSpan.FromMinutes(intervalInMinutes), stoppingToken);
        }
    }

    private async Task StartInstrumentationService()
    {
        if (InstrumentationServiceIsRunning())
            return;

        await _instrumentationService.StartAsync(CancellationToken.None);
    }

    private async Task StopInstrumentationService()
    {
        if (!InstrumentationServiceIsRunning())
            return;

        await _instrumentationService.StopAsync(CancellationToken.None);
    }

    private bool InstrumentationServiceIsRunning()
    {
        return _instrumentationService.IsRunning; // Placeholder, replace with actual logic
    }
}

Registering Background Services

Now, let's register the background services in the dependency injection container. Open the Program.cs file and update the Main method as follows:

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.
        builder.Services.AddHostedService<InstrumentationService>(); // Register InstrumentationService
        builder.Services.AddHostedService<PeriodicService>(); // Register PeriodicService
        builder.Services.AddLogging(); // Add logging
        // Register InstrumentationService as a singleton
        builder.Services.AddSingleton<InstrumentationService>();
        await using var app = builder.Build();

        // Configure the HTTP request pipeline.
        app.MapGet("/", () => "Hello World!");

        await app.RunAsync();
    }
}

With this setup, the InstrumentationService will run continuously, and the PeriodicService will run periodically based on the configured interval.

Conclusion

In this blog post, we explored how to build a .NET Core web application with background services. We created two background services: InstrumentationService and PeriodicService, and registered them in the dependency injection container. Background services are a powerful feature of .NET Core, enabling developers to perform asynchronous tasks efficiently.