In this article, we are going to add a searching feature to the table. We will Implement server Side Search As we type. In my previous article, I walked through CRUD Using Blazor, Entity Framework Core And Dapper, Sorting Table In Blazor and Pagination In Blazor.  If you haven’t read yet please read both articles first to understand this article. You can download the CRUD Blazor code from here. If you are new to Blazor, I recommend reading Getting Started With Blazor.

Prerequisite

  1. CRUD Using Blazor, Entity Framework Core And Dapper
  2. Sorting Table In Blazor
  3. Pagination In Blazor

In the previous post, we have done with pagination logic. so we are continuing from there.

Let’s start!

First, we need to modify our “Articlemanager.cs” class to implement a search feature.

To do this modify the “Count” and “ListAll” method as below.

public Task<int> Count(string search)
{
    var totArticle = Task.FromResult(_dapperManager.Get<int>($"select COUNT(*) from [Article] WHERE Title like '%{search}%'", null,
            commandType: CommandType.Text));
    return totArticle;
}

public Task<List<Article>> ListAll(int skip, int take, string orderBy, string direction = "DESC", string search = "")
{
    var articles = Task.FromResult(_dapperManager.GetAll<Article>
        ($"SELECT * FROM [Article] WHERE Title like '%{search}%' ORDER BY {orderBy} {direction} OFFSET {skip} ROWS FETCH NEXT {take} ROWS ONLY; ", null, commandType: CommandType.Text));
    return articles;
}

In above method, we add a search parameter in both methods and update SQL query to filter records.

As we update the manager class we need to update interface too, Open “IArticleManager” interface and update methods as below.

Task<int> Count(string search);
Task<List<Article>> ListAll(int skip, int take, string orderBy, string direction, string search);

We have done with backend logic now we need to modify the razor component.

Open the “FetchArticle.razor” component and modify the following things.

Add the following HTML content above the table.

<div class="row col-md-3 pull-right">
    <input type="text" id="txtSearch" placeholder="Search Titles..." class="form-control" @bind="SearchTerm" @bind:event="oninput" />
</div>

Update the table’s tbody.

   <tbody>
    @if (articleModel == null || articleModel.Count == 0)
    {
        <tr>
            <td colspan="3">
                No Records to display
            </td>
        </tr>
    }
    else
    {
        foreach (var article in articleModel)
        {
            <tr>
                <td>@article.ID</td>
                <td>@article.Title</td>
                <td>
                    <a class="btn btn-primary" href='/editArticle/@article.ID'>Edit</a>  |
                    <a class="btn btn-danger" @onclick="() => DeleteArticle(article.ID)">Delete</a>
                </td>
            </tr>
        }
    }

</tbody>

Add the following things inside @code { }  blog.

  • Add properties.
private string searchTerm;
private string SearchTerm
{
    get { return searchTerm; }
    set { searchTerm = value; FilterRecords(); }
}
  • Update the following methods method.
protected override async Task OnInitializedAsync()
{
    //display total page buttons
    pagerSize = 3;
    pageSize = 2;
    curPage = 1;
    articleModel = await articleManager.ListAll((curPage - 1) * pageSize, pageSize, sortColumnName, sortDir, searchTerm);
    totalRecords = await articleManager.Count(searchTerm);
    totalPages = (int)Math.Ceiling(totalRecords / (decimal)pageSize);
    SetPagerSize("forward");
}
protected async Task DeleteArticle(int id)
{
    await articleManager.Delete(id);
    articleModel = await articleManager.ListAll((curPage - 1) * pageSize, pageSize, sortColumnName, sortDir, searchTerm);
}
private async Task<List<Article>> SortRecords(string columnName, string dir)
{
    return await articleManager.ListAll((curPage - 1) * pageSize, pageSize, columnName, dir, searchTerm);
}
public async Task refreshRecords(int currentPage)
{
    articleModel = await articleManager.ListAll((currentPage - 1) * pageSize, pageSize, sortColumnName, sortDir, searchTerm);
    curPage = currentPage;
    this.StateHasChanged();
}
public void SetPagerSize(string direction)
    {
        if (direction == "forward" && endPage < totalPages)
        {
            startPage = endPage + 1;
            if (endPage + pagerSize < totalPages)
            {
                endPage = startPage + pagerSize - 1;
            }
            else
            {
                endPage = totalPages;
            }
            this.StateHasChanged();
        }
        else if (direction == "back" && startPage > 1)
        {
            endPage = startPage - 1;
            startPage = startPage - pagerSize;
        }
        else
        {
            startPage = 1;
            endPage = totalPages;
        }
    }
  • Add the “FilterRecords” method.
public void FilterRecords()
{
    endPage = 0;
    this.OnInitializedAsync().Wait();
}

Your “FetchArticle.razor” will look like this.

@page "/articlelist"

@using BlazorCRUD.Entities
@using BlazorCRUD.Contracts
@inject IArticleManager articleManager

<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">

<style>
    .sort-th {
        cursor: pointer;
    }

    .fa {
        float: right;
    }

    .btn-custom {
        color: black;
        float: left;
        padding: 8px 16px;
        text-decoration: none;
        transition: background-color .3s;
        border: 2px solid #000;
        margin: 0px 5px 0px 5px;
    }
</style>

<div>
    <a class="btn btn-primary" href='/addArticle'>Add</a>
</div>
<br />

@if (articleModel == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <div class="row col-md-3 pull-right">
        <input type="text" id="txtSearch" placeholder="Search Titles..." class="form-control" @bind="SearchTerm" @bind:event="oninput" />
    </div>
    <table class="table table-bordered table-hover">
        <thead>
            <tr>
                <th class="sort-th" @onclick="@(() => SortTable("ID"))">
                    ID
                    <span class="fa @(SetSortIcon("ID"))"></span>
                </th>
                <th class="sort-th" @onclick="@(() => SortTable("Title"))">
                    Title
                    <span class="fa @(SetSortIcon("Title"))"></span>
                </th>
                <th>Action</th>
            </tr>
        </thead>
        <tbody>
            @if (articleModel == null || articleModel.Count == 0)
            {
                <tr>
                    <td colspan="3">
                        No Records to display
                    </td>
                </tr>
            }
            else
            {
                foreach (var article in articleModel)
                {
                    <tr>
                        <td>@article.ID</td>
                        <td>@article.Title</td>
                        <td>
                            <a class="btn btn-primary" href='/editArticle/@article.ID'>Edit</a>  |
                            <a class="btn btn-danger" @onclick="() => DeleteArticle(article.ID)">Delete</a>
                        </td>
                    </tr>
                }
            }

        </tbody>
    </table>
    <div class="pagination">
        <button class="btn btn-custom" @onclick=@(async ()=>await NavigateToPage("previous"))>Prev</button>

        @for (int i = startPage; i <= endPage; i++)
        {
            var currentPage = i;
            <button class="btn btn-custom pagebutton @(currentPage==curPage?"btn-danger":"")" @onclick=@(async () =>await refreshRecords(currentPage))>
                @currentPage
            </button>
        }

        <button class="btn btn-custom" @onclick=@(async ()=>await NavigateToPage("next"))>Next</button>

    </div>
}


@code {
    private string searchTerm;
    private string SearchTerm
    {
        get { return searchTerm; }
        set { searchTerm = value; FilterRecords(); }
    }

    List<Article> articleModel;
    Article articleEntity = new Article();


    #region Pagination

    int totalPages;
    int totalRecords;
    int curPage;
    int pagerSize;
    int pageSize;
    int startPage;
    int endPage;
    string sortColumnName = "ID";
    string sortDir = "DESC";

    #endregion

    protected override async Task OnInitializedAsync()
    {
        //display total page buttons
        pagerSize = 3;
        pageSize = 2;
        curPage = 1;
        articleModel = await articleManager.ListAll((curPage - 1) * pageSize, pageSize, sortColumnName, sortDir, searchTerm);
        totalRecords = await articleManager.Count(searchTerm);
        totalPages = (int)Math.Ceiling(totalRecords / (decimal)pageSize);
        SetPagerSize("forward");
    }


    protected async Task DeleteArticle(int id)
    {
        await articleManager.Delete(id);
        articleModel = await articleManager.ListAll((curPage - 1) * pageSize, pageSize, sortColumnName, sortDir, searchTerm);
    }

    private bool isSortedAscending;
    private string activeSortColumn;

    private async Task<List<Article>> SortRecords(string columnName, string dir)
    {
        return await articleManager.ListAll((curPage - 1) * pageSize, pageSize, columnName, dir, searchTerm);
    }

    private async Task SortTable(string columnName)
    {
        if (columnName != activeSortColumn)
        {
            articleModel = await SortRecords(columnName, "ASC");
            isSortedAscending = true;
            activeSortColumn = columnName;
        }
        else
        {
            if (isSortedAscending)
            {
                articleModel = await SortRecords(columnName, "DESC");
            }
            else
            {
                articleModel = await SortRecords(columnName, "ASC");
            }

            isSortedAscending = !isSortedAscending;
        }
        sortColumnName = columnName;
        sortDir = isSortedAscending ? "ASC" : "DESC";
    }

    private string SetSortIcon(string columnName)
    {
        if (activeSortColumn != columnName)
        {
            return string.Empty;
        }
        if (isSortedAscending)
        {
            return "fa-sort-up";
        }
        else
        {
            return "fa-sort-down";
        }
    }

    public async Task refreshRecords(int currentPage)
    {
        articleModel = await articleManager.ListAll((currentPage - 1) * pageSize, pageSize, sortColumnName, sortDir, searchTerm);
        curPage = currentPage;
        this.StateHasChanged();
    }

    public void SetPagerSize(string direction)
    {
        if (direction == "forward" && endPage < totalPages)
        {
            startPage = endPage + 1;
            if (endPage + pagerSize < totalPages)
            {
                endPage = startPage + pagerSize - 1;
            }
            else
            {
                endPage = totalPages;
            }
            this.StateHasChanged();
        }
        else if (direction == "back" && startPage > 1)
        {
            endPage = startPage - 1;
            startPage = startPage - pagerSize;
        }
        else
        {
            startPage = 1;
            endPage = totalPages;
        }
    }

    public async Task NavigateToPage(string direction)
    {
        if (direction == "next")
        {
            if (curPage < totalPages)
            {
                if (curPage == endPage)
                {
                    SetPagerSize("forward");
                }
                curPage += 1;
            }
        }
        else if (direction == "previous")
        {
            if (curPage > 1)
            {
                if (curPage == startPage)
                {
                    SetPagerSize("back");
                }
                curPage -= 1;
            }
        }
        await refreshRecords(curPage);
    }

    public void FilterRecords()
    {
        endPage = 0;
        this.OnInitializedAsync().Wait();
    }

}

Output:

You can download source code from here.

If you want to learn File Upload in Blazor please visit here.

I hope you guys found something useful. Please give your valuable feedback/comments/questions about this article. Please let me know how you like and understand this article and how I could improve it.

Subscribe

Select Categories