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.
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.
Amazon Basics Ergonomic 2.4 GHz Wireless Optical Mouse, DPI adjustable, Compatible with PC, Mac, Laptop - Black
$8.09 (as of November 20, 2024 06:18 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Excel Formulas QuickStudy Laminated Study Guide (QuickStudy Computer)
$5.53 (as of November 21, 2024 15:46 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Genesis: Artificial Intelligence, Hope, and the Human Spirit
$25.21 (as of November 21, 2024 15:46 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)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.
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):
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<SharedDataService>();
services.AddHostedService<DataUpdateService>();
// Other service registrations
}
In Program.cs
(ASP.NET Core 5.0 or later):
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:
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.