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.
For the complete navigation for this series, you can visit the Blazor Series page.
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 the 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 searchTerm) { if (string.IsNullOrWhiteSpace(searchTerm)) return products; var lowerCaseSearchTerm = searchTerm.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:
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:
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 versa. 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:
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:
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.