Blazor WebAssembly Timer Example (Refresh REST API Data)

Here is an example that uses the timer to refresh the data on the user's screen. It should be run as client-side (WebAssembly) Blazor code.
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(); // MUST CALL StateHasChanged() BECAUSE THIS IS TRIGGERED BY A TIMER INSTEAD OF A USER EVENT
}, new System.Threading.AutoResetEvent(false), 1000, 1000);
}
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.
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.Runtime.Serialization.Formatters.Binary;
using BlazorExample.Shared;
namespace BlazorExample.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class CustomersController : ControllerBase
{
private static byte[] ObjectToBytes(object obj)
{
BinaryFormatter bf = new BinaryFormatter();
using (var ms = new MemoryStream())
{
bf.Serialize(ms, obj);
return ms.ToArray();
}
}
private static object BytesToObject(byte[] bytes)
{
try
{
using (MemoryStream ms = new System.IO.MemoryStream(bytes))
{
BinaryFormatter bf = new BinaryFormatter();
ms.Position = 0;
return bf.Deserialize(ms);
}
}
catch { return null; }
}
private string DataFolder { get; }
private IWebHostEnvironment env { get; }
public CustomersController(IWebHostEnvironment env)
{
this.env = env;
DataFolder = env.ContentRootPath + "\\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.WriteAllBytes(DataFolder + '\\' + id + ".bin", ObjectToBytes(cust));
return cust;
}
catch { return null; }
});
}
[HttpPatch("{id}")]
public async Task<Customer> Patch(string id, [FromBody] Customer update)
{
return await Task.Run(() =>
{
var ip = HttpContext.Connection.RemoteIpAddress.ToString().Replace(':', '-');
try
{
var cust = (Customer)BytesToObject(System.IO.File.ReadAllBytes(DataFolder + '\\' + ip + '-' + id + ".bin"));
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.WriteAllBytes(DataFolder + '\\' + ip + '-' + id + ".bin", ObjectToBytes(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 + '\\' + id + ".bin";
var cust = (Customer)BytesToObject(System.IO.File.ReadAllBytes(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.WriteAllBytes(DataFolder + '\\' + id + ".bin", ObjectToBytes(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((Customer)BytesToObject(System.IO.File.ReadAllBytes(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 (Customer)BytesToObject(System.IO.File.ReadAllBytes(DataFolder + '\\' + id + ".bin"));
}
catch { return null; }
});
}
}
}