Blazor: Using QuickGrid to Perform the CRUD Operations in Blazor WebAssembly Application

In this article, we will be the QuickGrid in Blazor WebAssembly Application. Blazor is a modern web framework that allows you to build interactive web applications using C# and.NET instead of JavaScript. Blazor offers a Component-Based Architecture that helps us to create UI using C# and HTML along with ready-to-use UI components. This helps developers to develop Cross-Platform Full-Stack applications without using JavaScript.

Traditionally, most of the .NET developers who have started working on Web Apps in ASP.NET WebForm days, have habit of using DataGrid (or GridView) a Tabular control, that offers features like inline-record update, deleting record as well as pagination. For all those developers when Blazor was introduced, the first question was that where is the DataGrid? Definitely, most of such experienced developers can use their JavaScript expertise to create DataGrid using HTML table or can choose to go for third-party UI elements.

In Blazor, we have QuickGrid to perform the Tabular operations. QuickGrid is a powerful and efficient data grid component for Blazor applications. It allows you to display data in a tabular format with features like sorting, filtering, pagination, and virtualization. In this article, we will perform CRUD operations using the QuickGrid. One of the useful features of the QuickGrid is that it provides TemplateColumns using which we can customize Columns and decide the UI element that we want to QuickGrid Columns. The Paginator component is used to show the pagination UI so that we can easily navigate across the QuickGrid pages. To use the QuickGrid in the Blazor Application, we need to install Microsoft.AspNetCore.Components.QuickGrid  package. 

The ASP.NET Core API is used for server-side Data Operations to the table of which script is provided in Listing 1.


Create Database EComm
GO
USE [EComm]
GO
CREATE TABLE [dbo].[ProductInfo](
	[ProductId] [varchar](20) NOT NULL,
	[ProductName] [varchar](100) NOT NULL,
	[CategoryName] [varchar](100) NOT NULL,
	[Description] [varchar](200) NOT NULL,
	[UnitPrice] [int] NOT NULL,
	[ProductRecordId] [int] IDENTITY(1,1) NOT NULL,
PRIMARY KEY CLUSTERED 
(
	[ProductId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

Listing 1: The ProductInfo Table 

I am skipping the API code, you care create APIs using ASP.NET Core, Noed.js, python. etc. (you can download the API code from the Git Link provided at the bottom of this article).             

Step 1: Open Visual Studio 2022 and create a Blazor WebAssembly project named Blazor-QuickGrid. In this project add the Microsoft.AspNetCore.Components.QuickGrid  package. 

Step 2: In this project, add a folder named Models. In this folder, add a new class file named ProductInfo.cs. In this file add the code for the ProductInfo class as shown in Listing 2. (Note that this class matches with columns of the Table.).


public partial class ProductInfo
{
    public string ProductId { get; set; } = null!;

    public string ProductName { get; set; } = null!;

    public string CategoryName { get; set; } = null!;

    public string Description { get; set; } = null!;

    public int UnitPrice { get; set; }

    public int ProductRecordId { get; set; }
}

Listing 2: The ProductInfo class

Step 3: In the Models folder, add a new class file named ProductService.cs. In this class file we will add code for the ProductService class. In this class we will inject the HttpClient class. This class provides methods to perform GET/POST/PUR/DELETE operations by accessing REST API. Listing 3 shows the code for ProductService class.


using System.Net.Http.Json;
namespace Blazor_QuickGrid.Models
{
    /// <summary>
    /// Class that contains method to perform HTTP Operations
    /// to Access API and Perform CRUD Operations
    /// </summary>
    public class ProductService(HttpClient client)
    {
        private string url = "https://localhost:7273/products";

        public async Task<IQueryable<ProductInfo>> GetAsync()
        {
            var result = await client.GetFromJsonAsync<List<ProductInfo>>(url);
            return result?.AsQueryable() ?? Enumerable.Empty<ProductInfo>().AsQueryable();
        }

        public async Task<ProductInfo> GetAsync(string id)
        {
            var result = await client.GetFromJsonAsync<ProductInfo>($"{url}/{id}");
            return result ?? new ProductInfo();
        }

        public async Task<ProductInfo> PostAsync(ProductInfo product)
        { 
            var response = await client.PostAsJsonAsync <ProductInfo>(url, product);
            if(response.IsSuccessStatusCode)
            { 
               var result = await response.Content.ReadFromJsonAsync<ProductInfo>();
                    return result ?? new ProductInfo();
            }
            return new ProductInfo();
        }

        public async Task<ProductInfo> PutAsync(string id,ProductInfo product)
        { 
            var response = await client.PutAsJsonAsync <ProductInfo>($"{url}/{id}", product);
            if(response.IsSuccessStatusCode)
            { 
               var result = await response.Content.ReadFromJsonAsync<ProductInfo>();
                    return result ?? new ProductInfo();
            }
            return new ProductInfo();
        }

        public async Task<ProductInfo> DeleteAsync(string id)
        {
          var result =  await client.DeleteFromJsonAsync<ProductInfo>($"{url}/{id}");
            return result ?? new ProductInfo();
        }
    }
}

Listing 3: The ProductService class

Register the ProductService class in dependency container in the Program.cs as shown in Listinn 4.


.......
builder.Services.AddScoped<ProductService>();
.......

Listing 4: Register the ProductService class in Dependency Container

Step 4: In the Pages folder, add a new Razor Component and name it as ProductInfoQuickGrid.cs. In this component set the @page directive as /quickcrud. Inject the ProductService in this component as shown in Listing 5.    


@page "/quickcrud"

@using Blazor_QuickGrid.Models
@using Microsoft.AspNetCore.Components.QuickGrid
@inject ProductService serv;

Listing 5: The Imports and the ProductService injected in the component

In the code part of the component, we need to define IQueryable type object so that we can show data in the QuickGrid. We need to add methods for Loading data, editing record in the QuickGrid, saving and updating the record also cancelling the edit effect. Listing 6 shows the code for component.


@code {
    private IQueryable<ProductInfo> products;
    private string editId = string.Empty;
    private bool canEdit = false;
    private bool isNew = false;
    private int NewRecordIndex = 0;
    private PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
    protected override async Task OnInitializedAsync()
    {
        await LoadData();
        // return base.OnInitializedAsync();
    }

    private async Task LoadData()
    { 
        products = await serv.GetAsync();
    }
    /// <summary>
    /// Make the Row (and Hence Record Editable)
    /// </summary>
    /// <param name="product"></param>
    private void EditRecord(ProductInfo product)
    {
        editId = product.ProductId;
        canEdit = true;
    }

    private void CancelEditRecord()
    {
        editId = string.Empty;
        canEdit = false;
    }

    /// <summary>
    /// Method to Add/Edit the Record
    /// </summary>
    /// <param name="product"></param>
    /// <returns></returns>
    private async Task Save(ProductInfo product)
    {
        // Add New Record
        if (isNew && NewRecordIndex >0)
        {
            await serv.PostAsync(product);
        }
        // Update an Existing Record
        if(canEdit && NewRecordIndex == 0)
        {
            await serv.PutAsync(editId, product);
        }
        editId = string.Empty;
        canEdit = false;
        isNew = false;
        NewRecordIndex = 0;
        await LoadData();
    }
    /// <summary>
    /// Delete the Record
    /// </summary>
    /// <param name="product"></param>
    /// <returns></returns>
    private async Task Delete(ProductInfo product)
    {
        await serv.DeleteAsync(product.ProductId);
        await LoadData();
    }

    /// <summary>
    /// Method to Generate teh ProductId from the Last Value of ProductId
    /// This method will also set the NewRecordIndex to specify the Index of the
    /// New reocrd to be added in the QuickGrid
    /// </summary>
    private void AddNewRecord() 
    {
        List<ProductInfo> prds = products.ToList();
        var prodId = (Convert.ToInt32(prds.Last().ProductId.Split('-')[1]) + 1).ToString();
        prodId = $"Prod-000{prodId}";
        prds.Add(new ProductInfo() {ProductId = prodId,ProductName=string.Empty,CategoryName=string.Empty,Description=string.Empty, UnitPrice = 0 });
        products = prds.AsQueryable();
        NewRecordIndex = products.Count();
        isNew = true;
    }

}

Listing 6:  The Component code

As shown in the Listing 6, we have defined the products of the type IQueryable so that we can bind it to the QuickGrid. The editId string will be sued to locate the record being edited so that the QuickGrid will show the edit UI to edit the record. The canEdit and isNew boolean variables will be used to set flag for editing record as well as adding new record respectively. The NewRecordIndex variable will be used to contains an index of the record being newly added in the QuickGrid. The pagination object of the type PaginationState will be used to define the pagination for the QuickGrid. Here we have set the page size to 10 records. Once there are more records in the QuickGrid than 10 a new page will be activated where we can navigate across pages. The code have following methods:

  • LoadData, the method that invokes the GetAsync() method of the ProductService to fetch data by accessing the API. This data is assigned to the products and hence will be shown in QuickGrid.
  • OnInitializedAsync(), this method will be invoked when the component is being loaded, this method invokes the LoadData() method to show data in the QuickGrid.         
  • EditRecord, this method is used to show the edit effect for the selected record from the QuickGrid for the selected ProductInfo record.
  • CancelEditRecord, this will remove the edit effect from the QuickGrid.
  • AddNewRecord,  this method will be used to add a new empty record to the QuickGrid using the products IQueryable. Since we cannot add record directly to IQueryable. we are storing it into the List of ProductInfo. Then we are reading the ProductId value of the last record in the List and increasing it by 1. New we are creating new empty ProductInfo record with the ProductId value in the List. Then we are assigning this list to the products IQueryable back. Finally, we are setting the value for the NewRecordIndex that is the index for the newly added record.
  • Save, this methos will either add a new record or edit an existing record based on the canEdit, isNew, and NewRecordIndex values. Once the new record is added the QuickGrid will be loaded. The Save method invokes PostAsync() or PutAsync() methods from the ProductService to either add new or edit existing record. 
  • Delete, method is used to delete the selected record by invoking DeleteAsync() method from the ProductService class.          
Listing 7 shows the UI code for the QuickGrid along with the Template Columns for Add, Edit, and Delete operations.


<h3>Using QuickGrid in Blazor With Inline Operations</h3>

<table class="table table-bordered table-striped">
    <thead>
        <tr>
            <th>
                <button class="btn btn-dark"
                @onclick="()=>AddNewRecord()">Add</button>
            </th> 
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <QuickGrid Items="products" class="table table-bordered table-striped table-warning table-hover" Pagination="pagination">
                    @if (!canEdit)
                    {
                        <PropertyColumn Property="@(p=>p.ProductRecordId)" Title="Product Record Id" />
                        <PropertyColumn Property="@(p=>p.ProductId)" Title="Product Id" />
                        <PropertyColumn Property="@(p=>p.ProductName)" Title="Product Name" />
                        <PropertyColumn Property="@(p=>p.CategoryName)" Title="Category Name" />
                        <PropertyColumn Property="@(p=>p.Description)" Title="Description" />
                        <PropertyColumn Property="@(p=>p.UnitPrice)" Title="Unit Price" />
                        <TemplateColumn Context="product">
                            <button @onclick="()=>EditRecord(product)">Edit</button>
                            <button @onclick="()=>Delete(product)">Delete</button>
                        </TemplateColumn>
                    }
                    else
                    {
                        <PropertyColumn Property="@(p=>p.ProductRecordId)" Title="Product Record Id" />
                        <PropertyColumn Property="@(p=>p.ProductId)" Title="Product  Id" />
                        <TemplateColumn Context="product">
                            @if (editId == product.ProductId)
                            {
                               <InputText @bind-Value="product.ProductName"/>
                            }
                            else
                            {
                                @product.ProductName
                            }
                        </TemplateColumn>
                        <TemplateColumn Context="product">
                            @if (editId == product.ProductId)
                            {
                                <InputText @bind-Value="product.CategoryName" />
                            }
                            else
                            {
                                @product.CategoryName
                            }
                        </TemplateColumn>
                        <TemplateColumn Context="product">
                            @if (editId == product.ProductId)
                            {
                                <InputText @bind-Value="product.Description" />
                            }
                            else
                            {
                                @product.Description
                            }
                        </TemplateColumn>
                        <TemplateColumn Context="product">
                            @if (editId == product.ProductId)
                            {
                                <InputNumber @bind-Value="product.UnitPrice" />
                            }
                            else
                            {
                                @product.UnitPrice
                            }
                        </TemplateColumn>
                        <TemplateColumn Context="product">
                            @if (editId == product.ProductId)
                            {
                                <button class="btn btn-success" @onclick="()=>Save(product)">Save</button>
                                <button class="btn btn-warning" @onclick="()=>CancelEditRecord()">Cancel</button>
                            }

                        </TemplateColumn>

                    }
                </QuickGrid>
                <Paginator State="pagination" />
            </td>
        </tr>
    </tbody>
</table>

Listing 7: The UI for the component

The component shows that the QuickGrid is bound with the products to its Items property so that product data can be shown in it. The Pagination property of the QuickGrid shows the pagination UI for the QuickGrid. If the value of the canEdit is false, then read-only rows will be shows in the QuickGrid using the PropertyColumn child of the QuickGrid that is bound with the properties of the ProductInfo class. When the rows are read-only it also shows the Edit and Delete buttons. These buttons are bound with the EditRecord() and Delete() methods respectively. When the Edit button is clicked the EditRecord() method will set the canEdit value to true and hence that row will show the edit UI using the InputText components in the TemplateColumns. This row will also show the Save and Cancel button those are bound to the Save() and CancleEditRecord() methods respectively. The Add button above the QuickGrid will add a new empty row which clicked. When the Edit button on this empty record is clicked, the row will be editable, and we can add values for the new record. This is how we can perform CRUD operations using the QuickGrid. The Paginator component below the QuickGrid will show the UI for the Pagination.

Step 5: Modify the NavMenu.razor to show the navigation link for the component which we have created as shown in Listing 8.


<div class="nav-item px-3">
    <NavLink class="nav-link" href="quickcrud">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Product Info Quick Grid
    </NavLink>
</div>

Listing 8: The Navigation Link

Run the Application, the Product Info Quick Grid link will be shown in the browser, click on it, the QuickGrid will be loaded with the data as shown in Figure 1



Figure 1: The Products in QuickGrid

Click on the Edit button on any row, that row will show TextBoxes for editing values as shown in Figure 2.



Figure 2: Edit Row

Edit Values and Click on the Save button, the values will be saved. Similarly Add, Delete, and Pagination can be tested.

The code for this article can be downloaded from this link.


Conclusion: The QuickGrid in Blazor is one of the superb components that provides and easy mechanism of tabular operations.  


   

    

       
 

   

       

Popular posts from this blog

Uploading Excel File to ASP.NET Core 6 application to save data from Excel to SQL Server Database

ASP.NET Core 6: Downloading Files from the Server

ASP.NET Core 6: Using Entity Framework Core with Oracle Database with Code-First Approach