In this part of the series, we are going to explain how to create one version of Blazor WebAssembly Searching functionality.

In this example, we are going to implement a search by product name, but later on, you can modify it to your needs. As we did in a previous part with Paging, we are going to implement the Web API side first and then continue with the Blazor WebAssembly client-side application.

We won’t dive deep into the searching logic because we already have a great article on that topic, so if you want to learn more, feel free to read it.

To download the source code for this article, you can visit the Blazor WebAssembly Searching repository.

For the complete navigation for this series, you can visit the Blazor Series page.

We are going to divide this article into the following topics:

Searching Functionality Implementation – Web API Part

The first thing, we are going to do is to extend the ProductParameters class in the Entities project:

public class ProductParameters
{
    const int maxPageSize = 50; 
    public int PageNumber { get; set; } = 1; 
    private int _pageSize = 4; 
    public int PageSize 
    { 
        get 
        { 
            return _pageSize; 
        } 
        set 
        { 
            _pageSize = (value > maxPageSize) ? maxPageSize : value; 
        }
    }

    public string SearchTerm { get; set; }
}

The next thing we need to do is to implement searching functionality itself. To do that, inside the Repository folder, we are going to create a new RepositoryExtensions folder and inside it a new RepositoryProductExtensions class:

public static class RepositoryProductExtensions
{
    public static IQueryable<Product> Search(this IQueryable<Product> products, string searchTearm)
    {
        if (string.IsNullOrWhiteSpace(searchTearm))
            return products;

        var lowerCaseSearchTerm = searchTearm.Trim().ToLower();

        return products.Where(p => p.Name.ToLower().Contains(lowerCaseSearchTerm));
    }
}

So, it’s a static class with a single static method that extends the IQueryable<Product> type and accepts a single string parameter. The logic inside this class is pretty simple.

Now, let’s call this method in the ProductRepository class:

public async Task<PagedList<Product>> GetProducts(ProductParameters productParameters)
{
    var products = await _context.Products
       .Search(productParameters.SearchTerm)
       .ToListAsync();

    return PagedList<Product>
        .ToPagedList(products, productParameters.PageNumber, productParameters.PageSize);
}

Excellent. All we have to do is to test this.

Testing Web API Searching Implementation

To execute the test, we are going to use Postman with a single Get request:

Search Web API Works

There we go. We can see it works as expected.

Once we implement the client-side, we are going to test it together with paging.

Blazor WebAssembly Searching Implementation

In this section, the first thing we are going to do is to create a new Search.razor component and a partial class file in the Components folder:

Search Component Tree Structure

After this, let’s modify the Search class file:

public partial class Search
{
    public string SearchTerm { get; set; }

    [Parameter]
    public EventCallback<string> OnSearchChanged { get; set; }
}

We are going to use the SearchTerm property to bind the value from our input text field, and OnSearchChanged event callback to run the method from the Products parent component.

Now, let’s modify the razor file:

<section style="margin-bottom: 10px">
    <input type="text" class="form-control" placeholder="Search by product name" 
           @bind-value="@SearchTerm" @bind-value:event="oninput" @onkeyup="async () => await OnSearchChanged.InvokeAsync(SearchTerm)" />
</section>

In this file, we create a simple input text element with the bootstrap’s form-control class and a placeholder. Now, we have to pay attention to the @bind-value attribute. With this attribute, we provide a two way binding with the SearchTerm property. This means by populating the SearchTerm property, our input field will be populated and vice verse. But, if we leave only the @bind-value attribute, the SearchTerm property will receive value only when the user navigates away from the input field (in other words, when input loses focus). But we don’t want that.

What we want is to populate the SearchTerm property as soon as we type any character, and for that, we use the @bind-value:event attribute set to oninput value. With this in place, the SearchTerm property will receive value as we type.

Lastly, we have the @onkeyup event, where we use the lambda expression to execute the OnSearchChanged event callback and to pass the SearchTerm as a parameter to the method inside the Products component.

Modifying the Products Component Files

Now, we have to modify the Products.razor.cs file by adding a new SearchChanged method:

private async Task SearchChanged(string searchTerm)
{
    Console.WriteLine(searchTerm);
    _productParameters.PageNumber = 1;
    _productParameters.SearchTerm = searchTerm;
    await GetProducts();
}

This method will accept the searchTerm parameter from the event callback from the Search component, assign that value to the _productParameters object, set the PageNumber to 1, and call the GetProducts method. We have to set the PageNumber to 1 because we rerender the list of products with our searching functionality and the paging should start from the first page. Additionally, we just log the searchTerm parameter to inspect its value – just for the testing’s sake.

Of course, we have to modify the Products.razor file:

<div class="row">
    <div class="col-md-5">
        <Search OnSearchChanged="SearchChanged"/>
    </div>
    <div class="col-md-5">
        @*Place for sort*@
    </div>
    <div class="col-md-2">
        <a href="/createProduct">Create Product</a>
    </div>
</div>
<div class="row">
    <div class="col">
        <ProductTable Products="ProductList" />
    </div>
</div>
<div class="row">
    <div class="col">
        <Pagination MetaData="MetaData" Spread="2" SelectedPage="SelectedPage" />
    </div>
</div>

Finally, let’s modify the GetProducts method in the ProductHttpRepository file, to include this additional query string parameter:

public async Task<PagingResponse<Product>> GetProducts(ProductParameters productParameters)
{
    var queryStringParam = new Dictionary<string, string>
    {
        ["pageNumber"] = productParameters.PageNumber.ToString(),
        ["searchTerm"] = productParameters.SearchTerm == null ? "" : productParameters.SearchTerm
    };

    //the rest of the code is the same
            
    return pagingResponse;
}

We have to do a null check for the productParameters.SearchTerm property because the AddQueryString method will complain if it is null.

Excellent.

Now we can test this.

Testing and Improving Search Functionality

Let’s start the server and the client applications, navigate to the Products page and type the Wal term in the search field:

Blazor WebAssembly Searcihg Functionality

We can see the Blazor WebAssembly Searching functionality is working and we have only one product on the list. But, aside from the working part, this functionality is not optimized in terms of sending the HTTP Get request to the server. If you look at the console logs, you can see the SearchedChanged method is logging our search term three times, for each character we type in the search field. Additionally, this means that our server is called every time we enter a character in the search field, and that’s not the best way. What we want is to send only one request with the complete search term.

To add the optimization part, let’s modify the Search.razor.cs file:

public partial class Search
{
    private Timer _timer;

    public string SearchTerm { get; set; }

    [Parameter]
    public EventCallback<string> OnSearchChanged { get; set; }

    private void SearchChanged()
    {
        if (_timer != null)
            _timer.Dispose();

        _timer = new Timer(OnTimerElapsed, null, 500, 0);
    }

    private void OnTimerElapsed(object sender)
    {
        OnSearchChanged.InvokeAsync(SearchTerm);
        _timer.Dispose();
    }
}

So now, we create a timer and the SearchChanged method where we dispose of the _timer object if it is already created and create a new timer object. This object will execute the OnTimerElapsed method after 500 milliseconds, and in this method, we run the method from the Products component.

By doing this, we prevent sending the GET request for each character in the input field but instead, the request will be sent after half a second after the user finishes typing.

We have to modify one more thing in the Search.razor file:

<section style="margin-bottom: 10px">
    <input type="text" class="form-control" placeholder="Search by product name" 
           @bind-value="@SearchTerm" @bind-value:event="oninput" @onkeyup="SearchChanged" />
</section>

Now, let’s start the Blazor WebAssembly application and inspect the result:

Blazor WebAssembly Searching Optimized

As we can see, the user sees the same result, but its implementation is more optimized than before.

Conclusion

There we go. We have fully functional Blazor WebAssembly Searching Functionality implemented in our application.

In the next article, we are going to learn about Sorting in Blazor WebAssembly.