Building a Periodic Data Update Service in ASP.NET Core

In this blog post, I’ll guide you through building a periodic data update service in ASP.NET Core that updates shared objects in memory. This can be incredibly useful for tasks such as refreshing cached data, updating statistics, or syncing information from an external service at regular intervals. We’ll cover creating a hosted service that runs on startup and every six hours, managing shared data with a singleton service, and accessing this data from your controllers.

Thank me by sharing on Twitter 🙏

Introduction

Recently, I faced a challenge where I needed to ensure that a background job ran periodically to update shared data accessible to all requests in an ASP.NET Core application. This post will share my approach and the steps I took to achieve this. By the end of this guide, you’ll have a robust solution for periodic data updates in your own ASP.NET Core projects.

Setting Up the Shared Data Service

First, let’s create a singleton service that will hold our shared data. This service will be accessible throughout our application, allowing us to read and write shared data safely.

Creating the Shared Data Service

We’ll start by creating a class called SharedDataService in a folder named Services. This class will use a reader-writer lock to ensure thread-safe access to our shared data.

C#
using System;
using System.Collections.Generic;
using System.Threading;

namespace MyProject.Services
{
    public class SharedDataService
    {
        private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
        private List<string> _sharedData;

        public SharedDataService()
        {
            _sharedData = new List<string>();
        }

        public void UpdateData(List<string> newData)
        {
            _lock.EnterWriteLock();
            try
            {
                _sharedData = newData;
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }

        public List<string> GetData()
        {
            _lock.EnterReadLock();
            try
            {
                return new List<string>(_sharedData);
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }
    }
}

In this class, UpdateData is used to modify the shared data, and GetData is used to read the current data.

Implementing the Hosted Service

Next, we’ll create a hosted service that updates the shared data periodically. This service will run on application startup and every six hours thereafter.

Creating the Data Update Service

We’ll create another class in the Services folder, named DataUpdateService. This class will implement IHostedService and manage a timer to schedule periodic updates.

C#
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace MyProject.Services
{
    public class DataUpdateService : IHostedService, IDisposable
    {
        private readonly ILogger<DataUpdateService> _logger;
        private readonly SharedDataService _sharedDataService;
        private Timer? _timer;

        public DataUpdateService(ILogger<DataUpdateService> logger, SharedDataService sharedDataService)
        {
            _logger = logger;
            _sharedDataService = sharedDataService;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Data Update Service is starting.");

            // Run the job on startup
            UpdateData(null);

            // Schedule the job to run every 6 hours
            _timer = new Timer(UpdateData, null, TimeSpan.FromHours(6), TimeSpan.FromHours(6));

            return Task.CompletedTask;
        }

        private void UpdateData(object? state)
        {
            _logger.LogInformation("Data Update Service is working.");

            // Add your data update logic here
            var newData = new List<string> { "Data1", "Data2", "Data3" }; // Example data
            _sharedDataService.UpdateData(newData);

            _logger.LogInformation("Data Update Service has completed the update.");
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Data Update Service is stopping.");

            // Stop the timer
            _timer?.Change(Timeout.Infinite, 0);

            return Task.CompletedTask;
        }

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

In this class, the StartAsync method initializes the timer and runs the update job on startup. The UpdateData method contains the logic to update the shared data, which you can customize to fit your needs.

Registering Services in the Application

Now that we’ve created our services, we need to register them in the application so they can be used.

Modifying Startup.cs or Program.cs

Depending on the version of ASP.NET Core you’re using, you’ll register the services differently.

In Startup.cs (ASP.NET Core 3.1 or earlier):

C#
public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<SharedDataService>();
    services.AddHostedService<DataUpdateService>();
    // Other service registrations
}

In Program.cs (ASP.NET Core 5.0 or later):

C#
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddSingleton<SharedDataService>();
builder.Services.AddHostedService<DataUpdateService>();
// Other service registrations

var app = builder.Build();

// Configure the HTTP request pipeline.
// ...

app.Run();

With these registrations, our shared data service and hosted service will be available for dependency injection throughout the application.

Accessing Shared Data from Controllers

Finally, we’ll create a controller to access the shared data. This controller will provide an endpoint to fetch the latest data.

Creating the Data Controller

Add a new controller named DataController in the Controllers folder:

C#
using Microsoft.AspNetCore.Mvc;
using MyProject.Services;

namespace MyProject.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class DataController : ControllerBase
    {
        private readonly SharedDataService _sharedDataService;

        public DataController(SharedDataService sharedDataService)
        {
            _sharedDataService = sharedDataService;
        }

        [HttpGet]
        public IActionResult GetSharedData()
        {
            var data = _sharedDataService.GetData();
            return Ok(data);
        }
    }
}

This simple controller uses dependency injection to access the SharedDataService and provides an endpoint to retrieve the current shared data.

Conclusion

In this post, we’ve built a robust solution for periodically updating shared data in an ASP.NET Core application. By creating a singleton service to manage shared data and a hosted service to update it periodically, we’ve ensured that our application can efficiently handle regular data updates. Additionally, by making this data accessible through a controller, we provide a straightforward way for other parts of our application to access the latest data.

This approach can be adapted to a variety of scenarios where periodic data updates are necessary, making your ASP.NET Core applications more dynamic and responsive to changing data.

Share this:

Leave a Reply