PROWAREtech
Blazor: DataGrid Component Example
Example usage of a DataGrid component for displaying data in Blazor WebAssembly (WASM).
This example uses .NET Core 3.1 but is compatible with .NET 5, .NET 6 and .NET 8.
As an example, here is a custom DataGridComponent
. It allows for its data to be sorted by clicking on the column heading. It should be run as client-side (WebAssembly) Blazor code.
See DataRepeaterComponent
(link) for an example that allows for more customization to allow for deleting data, for example.
@typeparam TItem
@using System.Reflection
@if (Items != null)
{
<div class="@ParentClassName">
@if (info != null && info.Length > 0)
{
<table class="@TableClassName">
<thead>
<tr>
@foreach (var member in info)
{
if (member.MemberType == MemberTypes.Property && !hideColumns.Contains(member.Name))
{
<th><a href="javascript:;" @onclick="@(()=>Sort(member.Name))">@member.Name</a></th>
}
}
</tr>
</thead>
<tbody>
@for (int index = 0; index < Items.Count; index++)
{
var item = Items[index];
var key = GetDataKey(item);
<tr data-index="@index">
@foreach (var member in info)
{
if (member.MemberType == MemberTypes.Property && !hideColumns.Contains(member.Name))
{
<td data-key="@key">@item.GetType().GetProperty(member.Name).GetValue(item)</td>
}
}
</tr>
}
</tbody>
</table>
}
</div>
}
@code {
[Parameter]
public List<TItem> Items { get; set; }
[Parameter]
public string ParentClassName { get; set; }
[Parameter]
public string TableClassName { get; set; }
private string[] hideColumns = { };
[Parameter]
public string HideColumns
{
get
{
return string.Join(',', hideColumns);
}
set
{
hideColumns = value.Split(',');
}
}
[Parameter]
public string DataKeyColumn { get; set; }
private MemberInfo[] info;
private string GetDataKey(TItem item)
{
if (!string.IsNullOrEmpty(DataKeyColumn))
{
foreach (var member in info)
{
if (member.MemberType == MemberTypes.Property && member.Name == DataKeyColumn)
return item.GetType().GetProperty(member.Name).GetValue(item).ToString();
}
}
return string.Empty;
}
protected override void OnParametersSet()
{
base.OnParametersSet();
if (Items != null && Items.Count > 0)
{
Type type = Items[0].GetType();
info = type.GetMembers();
}
}
private string sortedField;
private bool descending = true;
private void Sort(string field)
{
if (sortedField == field)
descending = !descending;
sortedField = field;
if (descending)
{
Items.Sort((a, b) =>
{
var obj = typeof(TItem).GetProperty(field).GetValue(a);
if (obj is DateTime)
{
DateTime dta = (DateTime)typeof(TItem).GetProperty(field).GetValue(a);
DateTime dtb = (DateTime)typeof(TItem).GetProperty(field).GetValue(b);
return dta.ToString("yyyyMMddHHmmssfff").CompareTo(dtb.ToString("yyyyMMddHHmmssfff"));
}
else if (obj is byte || obj is sbyte || obj is short || obj is ushort || obj is int)
{
int ia = (int)typeof(TItem).GetProperty(field).GetValue(a);
int ib = (int)typeof(TItem).GetProperty(field).GetValue(b);
return ia - ib;
}
else
{
var ta = typeof(TItem).GetProperty(field).GetValue(a);
var tb = typeof(TItem).GetProperty(field).GetValue(b);
return ta.ToString().CompareTo(tb.ToString());
}
});
}
else
{
Items.Sort((b, a) =>
{
var obj = typeof(TItem).GetProperty(field).GetValue(a);
if (obj is DateTime)
{
DateTime dta = (DateTime)typeof(TItem).GetProperty(field).GetValue(a);
DateTime dtb = (DateTime)typeof(TItem).GetProperty(field).GetValue(b);
return dta.ToString("yyyyMMddHHmmssfff").CompareTo(dtb.ToString("yyyyMMddHHmmssfff"));
}
else if (obj is byte || obj is sbyte || obj is short || obj is ushort || obj is int)
{
int ia = (int)typeof(TItem).GetProperty(field).GetValue(a);
int ib = (int)typeof(TItem).GetProperty(field).GetValue(b);
return ia - ib;
}
else
{
var ta = typeof(TItem).GetProperty(field).GetValue(a);
var tb = typeof(TItem).GetProperty(field).GetValue(b);
return ta.ToString().CompareTo(tb.ToString());
}
});
}
}
}
Here is a razor page that uses the DataGridComponent
. Contrary to the page for the DataRepeaterComponent
(link), the code is very simple.
@page "/datagrid"
@using BlazorExample.Shared
@inject HttpClient Http
@using BlazorExample.Client.Components
<h1>Customers</h1>
@if (custs == null)
{
<p><em>Loading...</em></p>
}
<DataGridComponent ParentClassName="table-responsive" TableClassName="table table-hover table-striped" HideColumns="id" DataKeyColumn="id" Items="custs"></DataGridComponent>
<form class="mt-5" onsubmit="return false;">
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="vipCheck" @bind-value="customer.vip">
<label class="form-check-label" for="vipCheck">VIP?</label>
</div>
<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">Add</button>
</div>
</form>
@code {
private List<Customer> custs;
private Customer customer = new Customer();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
custs = await Http.GetFromJsonAsync<List<Customer>>("/api/customers");
}
private async Task Add()
{
using(var msg = await Http.PostAsJsonAsync<Customer>("/api/customers", customer, System.Threading.CancellationToken.None))
{
if (msg.IsSuccessStatusCode)
{
custs.Add(await msg.Content.ReadFromJsonAsync<Customer>());
//StateHasChanged();
}
}
}
}
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; }
public bool vip { 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; }
});
}
}
}
Coding Video
Comment