Skip to main content Skip to footer

The Top Data Loading Techniques for Client-Side Blazor WASM Apps

Blazor as a framework has been around for some time now. For the uninitiated, it is a framework for building interactive client-side web UI with .NET and allows developers to create applications using HTML, CSS, and C#.

Blazor comes in 2 flavors:

  • Server App - Executed on the server from within an ASP.NET Core app
  • WebAssembly (WASM).- Useful for creating the SPA (Single Page Application) using C# and HTML, like other JS frameworks, e.g., Angular, React, and Vue.  WASM turns the client-side C# code directly on the browser

This blog will discuss setting up the WASM project with the ClientApp and RESTFul WebAPI project setup and different ways to feed data.  

Ready to Try It Out? Download ComponentOne Today!

Blazor WebAssembley Project Setup

Visual Studio must be used for the client-side Blazor and Blazor Assembly apps. 

Step 1: Open Visual Studio and click Create a New Project option.  

Step 2:  Select the “Blazor WebAssembly App” from the template. 

Blazor Data Loading

Step 3: Provide the project name and location after selecting a template. After providing the project name, click on the Next button. 

Step 4: Now, set the Framework to ".Net 6.0 (Long Term Support)" same as the below screenshot.

Step 5: Click on Checkboxes for "Configure for HTTPS" and "ASP.NET Core Hosted" 

 * "Configure for HTTPS" allows executing the application on HTTPS protocol 

*  "ASP.NET Core Hosted" option allows configuring the Shared and Server project to develop Model and RESTFul WebAPI to fetch data. 

Blazor Data Loading

Three projects will be created with {ProjectName}.Client, {ProjectName}.Server and {ProjectName}.Shared. In our example ProjectName is BlazorDataBinding.

Blazor Data Loading

The Blazor WebAssembly project with API setup is now complete. Now let's add ComponentOne Controls and use them inside this project.

Adding ComponentOne Blazor FlexGrid

ComponentOne Controls can be added via NuGet packages. Let's start with adding ComponentOne FlexGrid Control.

Step 1: Right-click on the reference and select ‘Manage NuGet Packages’, and install the following NuGet packages: 

Blazor Data Loading

Once the packages are installed, the required scripts and styles will be automatically included in the wwwroot/index.html file; if not, these can also be added manually.

<link rel="stylesheet" href="/_content/C1.Blazor.Core/styles.css" />
<link rel="stylesheet" href="/_content/C1.Blazor.Input/styles.css" />
<link rel="stylesheet" href="/_content/C1.Blazor.Grid/styles.css" />
<link rel="stylesheet" href="/_content/C1.Blazor.DataPager/styles.css" />  
 
<script src="/_content/C1.Blazor.Core/scripts.js"></script>
<script src="/_content/C1.Blazor.Input/scripts.js"></script>
<script src="/_content/C1.Blazor.Grid/scripts.js"></script>
<script src="/_content/C1.Blazor.DataPager/scripts.js"></script>

Step 2: Add the C1 namespaces to _Imports.razor file. This is to avoid namespace references for each page.  

@using C1.Blazor.Core
@using C1.Blazor.Input
@using C1.Blazor.Grid
@using C1.Blazor.DataPager

The project is ready to use the C1FlexGrid in the Blazor Client app, but we still need to add C1FlexGrid & connect it to a data source to display data.

Replace the HTML table with C1FlexGrid 

The default project template’s Pages/FetchData.razor file includes an HTML table to display the data from the JSON file. 

Let's remove this HTML Table first and replace it with C1FlexGrid.  

<FlexGrid  ItemsSource="@forecasts" Style="C1GridStyle.GridStyle" RowStyle="C1GridStyle.RowStyle" AlternatingRowStyle="C1GridStyle.AlternatingRowStyle" ColumnHeaderStyle = "C1GridStyle.ColumnHeaderStyle">
</FlexGrid>

C1FlexGrid has replaced the HTML table. The Datasource for C1FlexGrid is set using the ItemsSource property. In this case, it is bound to the @forecasts variable.

Let's discuss various ways to set this @forecasts variable to feed data from different sources. 

  1. JSON FIle
  2. REST API
  3. Virtual Data
  4. Paginated Data 

JSON File 

JSON is a widely used data format. It was introduced to provide data with less memory space than XML data types. The JSON data is written in the file with the .json extension name. 

The HTTPClient's GetFromJsonAsync() method allows the datacasting by providing the Data Model. The GetFromJsonAsync() method accepts the file path to fetch the JSON data. 

Here is the code snippet for accessing the data from the JSON file.  

protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
    }

Blazor Data Loading

REST APIs 

We need to supply the API URLs only to HTTPClient’s GetFromJsonAsync<T>() method to get the data from REST API.  

Please refer to the following code snippet for reference:

protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<Customers[]>("https://demodata.grapecity.com/wwi/api/v1/customers");
    }

 Blazor Data Loading

Virtual Data 

When working with millions of records, it may only be feasible to load some of the data simultaneously, making it a good case for Virtualization. Hence it can be fetched on-demand when you scroll down the grid.

ComponentOne Blazor FlexGrid supports the virtual data to load data on demand from Server. This feature is supported via the C1VirtualDataCollection class from the C1DataCollection namespace.  

We need to create a custom class by supplying the HTTPClient instance to fetch the data from the APIs. We also need to implement the GetPageAsync()method to return the required user data accessed from the API for the specific page.  

Before implementing the C1VirtualDataCollection class, let's have a look at Customer API, which would be used to fetch the data from the Server.  

[Route("api/[controller]")]
[ApiController]
public class CustomerController : ControllerBase
{
    
    private static IEnumerable<Customers> _customers = Customers.GetCustomers(200);
    private readonly ILogger<CustomerController> logger;
    public CustomerController(ILogger<CustomerController> logger)
    {
        this.logger = logger;
    }
    // GET method for API
    [HttpGet]
    public async Task<CustomerResponse> Get()
    {  
        var skip = 0;
        var take = 0;
        //parse the startIndex and itemCount from the Request
        int.TryParse(Request.Query?["skip"].FirstOrDefault(), out skip);
        int.TryParse(Request.Query?["take"].FirstOrDefault(), out take);
 
        var customers = _customers.ToList();
 
        // Parse Filter Information from the Request
        #region filter
        var filter = Request.Query?["filter"].FirstOrDefault();
         
        if (!string.IsNullOrWhiteSpace(filter))
        {
            var options = new JsonSerializerOptions { Converters = { new FilterExpressionJsonConverter() } };
            var filterExpression = JsonSerializer.Deserialize<FilterExpression>(filter, options);
            var filterCollection = new C1FilterDataCollection<Customers>(customers);
            // Filter the Data based on Filter Expression
            await filterCollection.FilterAsync(filterExpression);
            customers = filterCollection.ToList();
        }
        #endregion
 
        #region sorting
        // Parse SortDescription
        var sort = Request.Query?["sort"].FirstOrDefault();
 
        if (!string.IsNullOrWhiteSpace(sort))
        {
            var options = new JsonSerializerOptions { Converters = { new SortDescriptionJsonConverter() } };
            var sortDescriptions = JsonSerializer.Deserialize<SortDescription[]>(sort, options);
            var sortCollection = new C1SortDataCollection<Customers>(customers);
            // Sort the Data
            await sortCollection.SortAsync(sortDescriptions);
            customers = sortCollection.ToList();
        }
        #endregion
        // return Sorted/Filtered data for specified range
        return new CustomerResponse { TotalCount = customers.Count, Customers = customers.Skip(skip).Take(take) };
    }
}

Here is the code snippet to implement the class inherited from the C1VirtualDataCollection class.  

public class CustomerVirtualDataCollection : C1VirtualDataCollection<Customers>
    {
        public HttpClient Http { get; set; }
        private static readonly string URL = "api/customer";
        // to determine if the Sorting operation performs on not
        public override bool CanSort(params SortDescription[] sortDescriptions)
        {
            return true;
        }
        // determine if the Filter operation should be perform or not
        public override bool CanFilter(FilterExpression filterExpression)
        {
            return !(filterExpression is FilterPredicateExpression);
        }
        // data fetching for the specified page, sorted and Filtered data
        protected override async Task<Tuple<int, IReadOnlyList<Customers>>> GetPageAsync(int pageIndex, int startingIndex, int count, IReadOnlyList<SortDescription> sortDescriptions = null, FilterExpression filterExpression = null, CancellationToken cancellationToken = default)
        {
            // append Page information to fetch data
            string url = $"{URL}?skip={startingIndex}&take={count}";
            // append params to sort the data
            if (sortDescriptions?.Count > 0)
            {
               url += $"&sort={Uri.EscapeUriString(JsonSerializer.Serialize<IReadOnlyList<SortDescription>>(sortDescriptions))}";
            }
            // append params to fiter the data 
            if (filterExpression != null)
            {
                var options = new JsonSerializerOptions { Converters = { new FilterExpressionJsonConverter() } };
                url += $"&filter={Uri.EscapeUriString(JsonSerializer.Serialize(filterExpression, options))}";
            }
            // fetch the data from the API
            var response = await Http.GetFromJsonAsync<CustomerResponse>(new Uri(url, UriKind.Relative), cancellationToken);
            return new Tuple<int, IReadOnlyList<Customers>>(response.TotalCount, response.Customers.ToList());
        }
    }

The CanSort and CanFilter methods are required to override by returning the true/false to determine whether data should be sorted/filtered.  

Once the class is implemented, it can be used to bind with the FlexGrid.   

Here is the code to bind the virtualized data with FlexGrid. 

@inject HttpClient Http
<C1TextBox Class="filled-text-box" @bind-Text="filterText" Style="@("width:100%;margin-bottom:5px;")" Placeholder="Type to search..."  />
<FlexGrid  ItemsSource="@collection" Style="C1GridStyle.GridStyle" RowStyle="C1GridStyle.RowStyle" AlternatingRowStyle="C1GridStyle.AlternatingRowStyle" ColumnHeaderStyle = "C1GridStyle.ColumnHeaderStyle">
    <FlexGridBehaviors>
        <FullTextFilterBehavior FilterString="@filterText" />
    </FlexGridBehaviors>
</FlexGrid>
 
@code {
    string filterText="";
    string loadingMessage;
    CustomerVirtualDataCollection collection;
    int VirtualPageSize = 15;
    protected override Task OnInitializedAsync()
    {
        collection = new CustomerVirtualDataCollection() {Http = Http, PageSize = VirtualPageSize };
        return base.OnInitializedAsync();
    }
}

Blazor Data Loading

Paginated Data 

Once our data is virtualized, the virtualized data can be paginated by just creating the instance from C1PagedDataCollection class. This class needs the C1VirtualDataCollection class instance.  

Here is the code to paginate the virtualized data: 

C1PagedDataCollection<Customers> collection;
    int VirtualPageSize = 15;
protected override void OnInitialized()
    {   
        collection = new C1PagedDataCollection<Customers>(new CustomerVirtualDataCollection(){Http = Http, PageSize = VirtualPageSize }){PageSize = VirtualPageSize};
    }

The FlexGrid supports paging but does not have a built-in pager. The pager for the application can be added using the C1.Blazor.DataPager namespace that provides the feature for paged navigation.

Here is the code for adding the pager for the application: 

<C1DataPager Source="collection">
</C1DataPager>

Blazor Data Loading

This would add the simple Page navigator to the application. The C1DataPager comes with default styles that can be customized using CSS.

Here is the code to show the Page navigator with some style:

<style>
    .btn-circle {
        width: 30px;
        height: 30px;
        padding: 6px 0px;
        border-radius: 15px;
        text-align: center;
        font-size: 12px;
        line-height: 1.42857;
    }
 
    .btn-circle:hover{
        outline: 1px solid #007bff;
    }
 
    .c1DataPagerBtn:focus{
        border:none !important;
        outline:none !important;
    }
</style>
  
public class C1ButtonStyle
    {
        public static String PagerButtonStyle { get; set; } = "border:none;background:#fff;";
        public static String ActiveButtonStyle { get; set; } = "background:#007bff;border:none;";
        public static String ButtonStyle { get; set; } = "padding:10px 20px;font-weight:normal;";
    }
  
<C1DataPager Source="collection" ButtonStyle="C1ButtonStyle.PagerButtonStyle" >
    <PrevTemplate>
        <span class="badge badge-pill badge-primary" style="@C1ButtonStyle.ButtonStyle">←   Prev</span>
    </PrevTemplate>
    <NextTemplate>
        <span class="badge badge-pill badge-primary" style="@C1ButtonStyle.ButtonStyle">  Next   →</span>
    </NextTemplate>
    <PageNumberTemplate Context="pageNo">
        @if(pageNo == collection.CurrentPage+1)
        {
            <span class="btn btn-primary btn-circle" style="@C1ButtonStyle.ActiveButtonStyle">@pageNo</span>
        }else{
            <span class="btn btn-default btn-circle" >@pageNo</span>
        }
    </PageNumberTemplate>
</C1DataPager>

 This code would style the Data pager, and the styled pager would show like this: 

Blazor Data Loading

ComponentOne Blazor Sample Offline | Download Blazor WASM APP

Ready to Try It Out? Download ComponentOne Today!

Happy Coding!

comments powered by Disqus