articles » current » blazor » wasm » timer-refresh-rest-api-data

Blazor: Timer Example - Refresh Data

An example using the Timer class to refresh data from a REST API or Web API

This code is compatible with .NET Core 3.1, .NET 5 and .NET 6. If using .NET 5 then follow the .NET Core 3.1 code.

Here is an example that uses the timer System.Threading.Timer to refresh the data on the user's screen. It should be run as client-side Blazor WASM code.

For an example of the Timer class running an analog clock, see this article.

.NET 6 Example

Here is a .NET 6 razor page that uses the Timer. It is an example that needs to call StateHasChanged(). The code for the RESTful API is just the default code for "WeatherForecast" controller that Microsoft adds when creating a new project.

@page "/fetchdata"
@using BlazorTimer.Shared
@inject HttpClient Http

<PageTitle>Weather forecast</PageTitle>

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server using a timer.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? forecasts;
    private System.Threading.Timer? timer; // NOTE: THIS LINE OF CODE ADDED

    protected override async Task OnInitializedAsync()
    {
		// NOTE: THE FOLLOWING CODE ADDED
		timer = new System.Threading.Timer(async (object? stateInfo) =>
		{
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
			StateHasChanged(); // NOTE: MUST CALL StateHasChanged() BECAUSE THIS IS TRIGGERED BY A TIMER INSTEAD OF A USER EVENT
		}, new System.Threading.AutoResetEvent(false), 2000, 2000); // fire every 2000 milliseconds
    }
}

Run this code and watch the "fetchdata" page update the weather forecast every couple seconds. It is just that simple!

Here is the standard WeatherForecastController code that is supplied by Microsoft. It is unmodified.

using BlazorTimer.Shared;
using Microsoft.AspNetCore.Mvc;

namespace BlazorTimer.Server.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

        private readonly ILogger<WeatherForecastController> _logger;

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

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

Here is the standard WeatherForecast class that is supplied by Microsoft. It is unmodified.

namespace BlazorTimer.Shared
{
    public class WeatherForecast
    {
        public DateTime Date { get; set; }

        public int TemperatureC { get; set; }

        public string? Summary { get; set; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}

.NET Core 3.1 Example

Here is a razor page that uses the Timer. It is an example that needs to call StateHasChanged(). Code follows that implements the RESTful API.

@page "/timerexample"
@using BlazorExample.Shared
@inject HttpClient Http

<h1>Customers</h1>

@if (custs == null)
{
	<p><em>Loading...</em></p>
}
else
{
<div class="table-responsive">
	<table class="table table-hover table-striped">
		<thead>
			<tr>
				<th>name</th>
				<th>address</th>
				<th>zip</th>
			</tr>
		</thead>
		<tbody>
		@foreach (var cust in custs)
		{
			<tr>
				<td>@cust.name</td>
				<td>@cust.address</td>
				<td>@cust.zip</td>
			</tr>
		}
		</tbody>
	</table>
</div>
}

<form class="mt-5" onsubmit="return false;">
	<div class="input-group input-group-md mb-2">
		<span class="input-group-text">Name</span>
		<input type="text" class="form-control" autocomplete="off" required @bind-value="customer.name" />
	</div>
	<div class="input-group input-group-md mb-2">
		<span class="input-group-text">Address</span>
		<input type="text" class="form-control" autocomplete="off" required @bind-value="customer.address" />
	</div>
	<div class="input-group input-group-md mb-2">
		<span class="input-group-text">Zip</span>
		<input type="text" class="form-control" autocomplete="off" required @bind-value="customer.zip" />
		<button class="btn btn-success" @onclick="Add" type="button">Add</button>
	</div>
</form>

@code {
	private List<Customer> custs;
	private Customer customer = new Customer();
	private System.Threading.Timer timer;

	protected override async Task OnInitializedAsync()
	{
		await base.OnInitializedAsync();
		custs = await Http.GetFromJsonAsync<List<Customer>>("/api/customers");

		timer = new System.Threading.Timer(async (object stateInfo) =>
		{
			custs = await Http.GetFromJsonAsync<List<Customer>>("/api/customers");
			StateHasChanged(); // NOTE: MUST CALL StateHasChanged() BECAUSE THIS IS TRIGGERED BY A TIMER INSTEAD OF A USER EVENT
		}, new System.Threading.AutoResetEvent(false), 1000, 1000); // fire every 1000 milliseconds
	}

	private async Task Add()
	{
		var msg = await Http.PostAsJsonAsync<Customer>("/api/customers", customer, System.Threading.CancellationToken.None);
		if (msg.IsSuccessStatusCode)
		{
			custs.Add(await msg.Content.ReadFromJsonAsync<Customer>());
		}
	}

	private async Task Delete(string id)
	{
		var msg = await Http.DeleteAsync("/api/customers/" + id, System.Threading.CancellationToken.None);
		if (msg.IsSuccessStatusCode)
		{
			int i;
			for (i = 0; i < custs.Count && custs[i].id != id; i++) ;
			custs.RemoveAt(i);
		}
	}
}

Here is the Customer definition.

using System;

namespace BlazorExample.Shared
{
	[Serializable]
	public class Customer
	{
		public string id { get; set; }
		public string name { get; set; }
		public string address { get; set; }
		public string zip { get; set; }
	}
}

Here is the RESTful API implementation used by the above page that does not rely upon a database.

 // CustomersController.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
using BlazorExample.Shared;

namespace BlazorExample.Server.Controllers
{
	[ApiController]
	[Route("api/[controller]")]
	public class CustomersController : ControllerBase
	{
		private string DataFolder { get; }
		private IWebHostEnvironment env { get; }
		public CustomersController(IWebHostEnvironment env)
		{
			this.env = env;
			DataFolder = env.ContentRootPath + Path.DirectorySeparatorChar + "CustomersData";
			try
			{
				if (!System.IO.Directory.Exists(DataFolder))
					System.IO.Directory.CreateDirectory(DataFolder);
			}
			catch { }
		}

		[HttpPut("{id}")]
		public async Task<Customer> Put(string id, [FromBody] Customer cust)
		{
			return await Task.Run(() =>
			{
				try
				{
					cust.id = id;
					System.IO.File.WriteAllText(DataFolder + Path.DirectorySeparatorChar + id + ".json", JsonSerializer.Serialize(cust));
					return cust;
				}
				catch { return null; }
			});
		}

		[HttpPatch("{id}")]
		public async Task<Customer> Patch(string id, [FromBody] Customer update)
		{
			return await Task.Run(() =>
			{
				try
				{
					var cust = JsonSerializer.Deserialize<Customer>(System.IO.File.ReadAllText(DataFolder + Path.DirectorySeparatorChar + id + ".json"));
					cust.name = (update.name == null) ? cust.name : update.name;
					cust.address = (update.address == null) ? cust.address : update.address;
					cust.zip = (update.zip == null) ? cust.zip : update.zip;
					System.IO.File.WriteAllText(DataFolder + Path.DirectorySeparatorChar + id + ".json", JsonSerializer.Serialize(cust));
					return cust;
				}
				catch { return null; }
			});
		}

		[HttpDelete("{id}")]
		public async Task<Customer> Delete(string id)
		{
			return await Task.Run(() =>
			{
				var di = new DirectoryInfo(DataFolder);
				try
				{
					string file = DataFolder + Path.DirectorySeparatorChar + id + ".json";
					var cust = JsonSerializer.Deserialize<Customer>(System.IO.File.ReadAllText(file));
					System.IO.File.Delete(file);
					return cust;
				}
				catch { return null; }
			});
		}

		[HttpPost]
		public async Task<Customer> Post([FromBody] Customer cust)
		{
			return await Task.Run(() =>
			{
				var di = new DirectoryInfo(DataFolder);
				string id = Guid.NewGuid().ToString("N");
				cust.id = id;
				if (cust.name == null)
					cust.name = string.Empty;
				else
					cust.name = cust.name.Substring(0, cust.name.Length > 100 ? 100 : cust.name.Length);
				if (cust.address == null)
					cust.address = string.Empty;
				else
					cust.address = cust.address.Substring(0, cust.address.Length > 100 ? 100 : cust.address.Length);
				if (cust.zip == null)
					cust.zip = string.Empty;
				else
					cust.zip = cust.zip.Substring(0, cust.zip.Length > 10 ? 10 : cust.zip.Length);
				try
				{
					System.IO.File.WriteAllText(DataFolder + Path.DirectorySeparatorChar + id + ".json", JsonSerializer.Serialize(cust));
					return cust;
				}
				catch { return null; }
			});
		}

		[HttpGet]
		public async Task<IEnumerable<Customer>> Get()
		{
			return await Task.Run(() =>
			{
				var di = new DirectoryInfo(DataFolder);
				FileInfo[] fi = di.GetFiles("*", SearchOption.TopDirectoryOnly);
				var q = from f in fi orderby f.CreationTime descending select f;
				var custs = new List<Customer>();
				for (int i = 0; i < fi.Length; i++)
					custs.Add(JsonSerializer.Deserialize<Customer>(System.IO.File.ReadAllText(fi[i].FullName)));
				return custs;
			});
		}

		[HttpGet("{id}")]
		public async Task<Customer> Get(string id)
		{
			return await Task.Run(() =>
			{
				var di = new DirectoryInfo(DataFolder);
				try
				{
					return JsonSerializer.Deserialize<Customer>(System.IO.File.ReadAllText(DataFolder + Path.DirectorySeparatorChar + id + ".json"));
				}
				catch { return null; }
			});
		}
	}
}

This site uses cookies. Cookies are simple text files stored on the user's computer. They are used for adding features and security to this site. Read the privacy policy.
CLOSE