Add Search functionality

|

Search functionality MVC .NET project

So far we have created .NEt MVC project for Todo app with CRUD operations. For current workflow I created this release tag, so if you are new to this project go ahead and start working from that. But final code for this project is still located here.

So lets continue with adding Search to our Todos. Adding Search functionality for your app is definitely something that makes your program more attractive and helps with data management. With our Todo app, so far we have our CRUD methods ready for them to include some search as well.

Lets create 3 types of Search functionality: Search by keyword, filter by Status and Sort by either Status or by DueDate. To start, lets create a new ViewModel. Inside your WebApp/ViewModels folder, create a new file called TodoFilterViewModel.cs and populate this with following code:

// WebApp/ViewModels/TodoFilterViewModel.cs
using Domain;

namespace WebApp.ViewModels
{
    public class TodoFilterViewModel
    {
        public string? SearchKeyword { get; set; }
        public string? StatusFilter { get; set; }
        public string? SortBy { get; set; }
        public IEnumerable<Todo>? Todos { get; set; }
    }
}

Lets continue with creating partial view for our search functionality. Lets create a Partial View _SearchAndFilter.cshtml inside Views/Todos/ folder. Right Click on Todos folder -> Select Add -> and choose new View. On popup select Razor View – Empty. Name it _SearchAndFilter.cshtml. If you have completed creating new file, lets add the following content to it:

@model WebApp.ViewModels.TodoFilterViewModel

<form method="get" asp-action="Index">
    <div class="container row">

        <div class="form-group col-sm-3">
            <label asp-for="SearchKeyword">Search:</label>
            <input asp-for="SearchKeyword"
                   type="text"
                   name="SearchKeyword"
                   value="@Model.SearchKeyword"
                   class="form-control"
                   placeholder="Search by title or description" />
        </div>
        <div class="form-group col-sm-3">
            <label asp-for="StatusFilter">Status:</label><br />
            <select name="StatusFilter" class="Form-control">
                <option value="">All</option>
                @foreach (var status in Enum.GetValues(typeof(Domain.Status)))
                {
                    <option value="@status" selected="@(Model.StatusFilter == status.ToString())">@status</option>
                }
            </select>
        </div>
        <div class="form-group col-sm-3">
            <label asp-for="SortBy">Sort by:</label>
            <select asp-for="SortBy" name="SortBy" class="form-control">
                <option value="Title" selected="@(Model.SortBy != "DueDate" || Model.SortBy != "Status")">Title</option>
                <option value="DueDate" selected="@(Model.SortBy == "DueDate")">Due Date</option>
                <option value="Status" selected="@(Model.SortBy == "Status")">Status</option>
            </select>
        </div>
        <div class="col-sm-3">

            <button type="submit" class="btn btn-primary">Apply</button>
        </div>
    </div>
</form>

So this thing is for search view that will display different search options. Now lets include this to our Index.cshtml file to get search live. Modify your current index.cshtml with the following content:

 @model WebApp.ViewModels.TodoFilterViewModel
@{
    ViewData["Title"] = "Todo Tasks";
}

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>

@await Html.PartialAsync("_SearchAndFilter", Model)
<table class="table">
    <thead>
        <tr>
            <th>
                Title
            </th>
            <th>
                Description
            </th>
            <th>
                Due Date
            </th>
            <th>
                Status
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @if (Model.Todos == null)
        {
            <p>
                No tasks found
            </p>
        }
        else
        {
            @foreach (var item in Model.Todos)
            {
                <tr>
                    <td>
                        @Html.DisplayFor(modelItem => item.Title)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Description)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.DueDate)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Status)
                    </td>
                    <td>
                        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
                        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
                        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
                    </td>
                </tr>
            }
        }
    </tbody>
</table>

With this update we included our Search function partial view and got ViewModel to our Index page. Now we are almost done, lets change TodosController as well then it should work. In TodosController, lets put search functionality to GetAll method so that it would accept search parameters.

In TodosController, modify Index method like this:

// GET: Todos
public async Task<IActionResult> Index(string? searchKeyword, string? statusFilter, string? sortBy)
{
    var tasksQuery = _context.Todos.AsQueryable();
    if (!string.IsNullOrEmpty(searchKeyword))
    {
        var lowerSearch = searchKeyword.ToLower();
        tasksQuery = tasksQuery.Where(t => t.Title!.ToLower().Contains(lowerSearch)
        || t.Description!.ToLower().Contains(lowerSearch));
    }
    if (!string.IsNullOrEmpty(statusFilter))
    {
        if (Enum.TryParse(typeof(Status), statusFilter, out var status))
        {
            tasksQuery = tasksQuery.Where(t => t.Status == (Status)status);
        }
    }
    switch (sortBy)
    {
        case "DueDate":
            tasksQuery = tasksQuery.OrderBy(t => t.DueDate);
            break;
        case "Status":
            tasksQuery = tasksQuery.OrderBy(t => t.Status);
            break;
        default:
            tasksQuery = tasksQuery.OrderBy(t => t.Title); // Default sort by Title
            break;
    }
    var viewModel = new TodoFilterViewModel
    {
        SearchKeyword = searchKeyword,
        StatusFilter = statusFilter,
        SortBy = sortBy,
        Todos = await tasksQuery.ToListAsync()
    };


    return View(viewModel);
}

Thats it. We have search functionality. Now if you run the program you can see that there is search option available. You can create new todos and check that search is working as expected. A snapshot of the View is here:

Now we have completed the functionality of our application but that is not where we are going to stop. We still have some important code maintability stuff to do. First thing is Dependency Injection – Good code is where Business Logic is separated from Controllers. And that is what we are going to do next: We are going to introduce Services ->.

Leave a Reply

Your email address will not be published. Required fields are marked *