In Blazor wasm, I would like to periodically execute a job (code), even if the user is navigating through the pages (every x min for example).
Is that possible? What would be a practical way?
In Blazor wasm, I would like to periodically execute a job (code), even if the user is navigating through the pages (every x min for example).
Is that possible? What would be a practical way?
Create a Service to manager the timer
public class JobExecutedEventArgs : EventArgs {}
public class PeriodicExecutor : IDisposable
{
public event EventHandler<JobExecutedEventArgs> JobExecuted;
void OnJobExecuted()
{
JobExecuted?.Invoke(this, new JobExecutedEventArgs());
}
Timer _Timer;
bool _Running;
public void StartExecuting()
{
if (!_Running)
{
// Initiate a Timer
_Timer= new Timer();
_Timer.Interval = 300000; // every 5 mins
_Timer.Elapsed += HandleTimer;
_Timer.AutoReset = true;
_Timer.Enabled = true;
_Running = true;
}
}
void HandleTimer(object source, ElapsedEventArgs e)
{
// Execute required job
// Notify any subscribers to the event
OnJobExecuted();
}
public void Dispose()
{
if (_Running)
{
// Clear up the timer
}
}
}
Register it in Program.cs
builder.Services.AddSingleton<PeriodicExecutor>();
Request it and start it in home page initialization
@page "/home"
@inject PeriodicExecutor PeriodicExecutor
@code {
protected override void OnInitialized()
{
PeriodicExecutor.StartExecuting();
}
}
In any component if you want to do something when job executes
@inject PeriodicExecutor PeriodicExecutor
@implements IDisposable
<label>@JobNotification</label>
@code {
protected override void OnIntiialized()
{
PeriodicExecutor.JobExecuted += HandleJobExecuted;
}
public void Dispose()
{
PeriodicExecutor.JobExecuted -= HandleJobExecuted;
}
string JobNotification;
void HandleJobExecuted(object sender, JobExecutedEventArgs e)
{
JobNotification = $"Job Executed: {DateTime.Now}";
}
}
You can treat MainLayout.razor or App.razor as 'normal pages' for this.
Use MainLayout when you have something to show on screen:
....
<div>Time=@theTime</div>
@code
{
protected override void OnInitialized()
{
Ticker();
base.OnInitialized();
}
string theTime;
// use an async-void or a timer. An async-void needs no cleanup.
async void Ticker()
{
while(true)
{
await Task.Delay(2_000);
theTime = DateTime.Now.ToLongTimeString();
StateHasChanged(); // refresh everything
}
}
}
If you are using an ASP.NET Core Hosted flavor of Blazor WebAssembly, you can use a BackgroundService. For example:
MyBackgroundService.cs
public class MyBackgroundService : BackgroundService, IDisposable
{
private readonly ILogger<CollectionService> _logger;
private readonly IServiceScopeFactory _serviceScopeFactory;
public MyBackgroundService(ILogger<CollectionService> logger, IServiceScopeFactory serviceScopeFactory)
{
_logger = logger;
_serviceScopeFactory = serviceScopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("My Background Service is starting.");
//Do your work here...
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<MyBackgroundService>();
services.AddHostedService(provider => provider.GetService<MyBackgroundService>());
One benefit of this solution is the service will start running regardless of whether a user navigates to any particular page on the site. Or even if no user access the site.
The simplest way within a Blazor component would be to create a Timer, subscribe to Elapsed, and call InvokeAsync to update any state:
// In a @code block, or a Component.razor.cs file:
private readonly Timer _jobTimer = new()
{
AutoReset = true,
Enabled = true,
Interval = 60000, // 1 minute
};
protected override void OnInitialized() {
_jobTimer.Elapsed += JobMethod;
// ^ don't forget to unsubscribe and dispose in IDisposable.Dispose!
}
private async void JobMethod(object sender, ElapsedEventArgs e) {
// do extraneous stuff here
await InvokeAsync(() => {
// update state here
StateHasChanged();
}
}
protected virtual void Dispose(bool disposing) {
// however you implement this, but at the very least least:
_jobTimer.Elapsed -= JobMethod;
_jobTimer.Dispose();
}
Use this if the job is localized to a single component. For reusability, prefer Neil's answer.