Part 9: Real-Time Search Bar with Autocomplete Suggestions in ASP.NET Core MVC

 


🚀 Welcome to Part 9 of our Complete ASP.NET Core MVC eCommerce Project Series! In this tutorial, we are implementing a killer feature for our storefront: a Real-Time Search Bar with Instant Autocomplete Suggestions! You will learn how to build an optimized backend API endpoint using C# and Entity Framework Core that searches across product names, brands, and categories simultaneously. On the frontend, we'll write clean, lightweight JavaScript to capture user keystrokes, implement a "debounce" mechanism to shield our database from request spam, and render a beautiful, keyboard-friendly floating dropdown list.

Step 1: Implementing the Asynchronous Search API Endpoint

Add the following action method inside your ProductsController. This endpoint handles the incoming real-time requests from your search input element.

The Code: Controllers/ProductsController.cs


C# / Controllers/ProductsController.cs (Search Action)

[HttpGet]
public async Task<IActionResult> Search(string term)
{
    if (string.IsNullOrWhiteSpace(term) || term.Length < 2)
        return Json(new List<object>());

    var filter = new ProductListViewModel
    {
        SearchTerm = term,
        PageSize = 5
    };

    var result = await _productService.GetProductsAsync(filter);
    var suggestions = result.Products.Select(p => new
    {
        id = p.Id,
        name = p.Name,
        price = p.SalePrice,
        image = p.MainImageUrl,
        brand = p.Brand?.Name
    });

    return Json(suggestions);
}

🧠 Detailed Step-by-Step Code Breakdown for Your Video Script

1. The Guard Clause (Input Optimization & Security)

if (string.IsNullOrWhiteSpace(term) || term.Length < 2)
    return Json(new List<object>());
  • How it works: The code instantly evaluates if the input parameter term is empty, just spaces, or less than 2 characters long. If true, it exits immediately and returns a blank JSON list array [].

  • Why it matters to your viewers: It acts as a defensive shield. Searching a database with a single character like "a" or "s" can pull thousands of records, which slows down the application. Forcing a minimum threshold of 2 characters saves major server memory.

2. PageSize Boundary Control (PageSize = 5)

PageSize = 5
  • The Logic: An autocomplete dropdown list only has room to show a few items comfortably without cluttering the screen.

  • Why it matters to your viewers: By restricting the request filter to a max count of 5 items, the underlying database query processes an implicit SQL TOP(5) or LIMIT 5 operator. This ensures the database lookup stays incredibly quick, keeping the search "real-time."

3. Reusing Existing Service Architecture

var result = await _productService.GetProductsAsync(filter);
  • The Logic: Instead of writing a completely new, messy query routine, this endpoint intelligently leverages the existing product service layer logic by passing a populated ProductListViewModel. It perfectly matches the clean, maintainable, and dry software engineering design rules that professional teams expect.

4. Anonymous Data Projection (The Secret to Fast APIs)

var suggestions = result.Products.Select(p => new { ... });
  • The Logic: A raw database entity object (Product.cs) can be heavy. It might contain long text descriptions, background database relationships, full tracking tables, or structural properties that the search dropdown doesn't even need.

  • Why it matters to your viewers: This .Select() statement maps the database models directly into a highly compressed, custom anonymous C# object structure. It strips away all unneeded details, sending only the essential bits (id, name, price, image, brand) through the network cable. This slashes the JSON payload size, boosting page load speeds over mobile networks!

Step 2: Adding the Dynamic Asynchronous Client-Side Logic

Append the following jQuery script structure into your application's global script resource file.

The Code: wwwroot/js/site.js


JavaScript / wwwroot/js/site.js (Search Auto-suggestions)

// MobileShop Site JavaScript

$(document).ready(function () {
    // Search Auto-suggestions
    var searchTimeout;
    $('#searchInput').on('input', function () {
        clearTimeout(searchTimeout);
        var term = $(this).val();

        if (term.length < 2) {
            $('#searchSuggestions').hide();
            return;
        }

        searchTimeout = setTimeout(function () {
            $.get('/Products/Search', { term: term }, function (data) {
                if (data.length > 0) {
                    var html = '';
                    data.forEach(function (item) {
                        html += `<a href="/Products/Details/${item.id}" class="list-group-item list-group-item-action d-flex align-items-center">
                            <img src="${item.image || 'https://via.placeholder.com/40x40?text=No+Image'}" class="rounded me-3" style="width: 40px; height: 40px; object-fit: cover;" />
                            <div>
                                <h6 class="mb-0">${item.name}</h6>
                                <small class="text-muted">${item.brand} - RS ${item.price.toLocaleString()}</small>
                            </div>
                        </a>`;
                    });
                    $('#suggestionsList').html(html);
                    $('#searchSuggestions').show();
                } else {
                    $('#searchSuggestions').hide();
                }
            });
        }, 300);
    });

    // Hide suggestions when clicking outside
    $(document).on('click', function (e) {
        if (!$(e.target).closest('#searchInput').length) {
            $('#searchSuggestions').hide();
        }
    });

});

🧠 Detailed Step-by-Step Functional Breakdown for Your Video Script

1. The Core Debounce Mechanism (Performance Protection)

clearTimeout(searchTimeout);
searchTimeout = setTimeout(function () { ... }, 300);
  • How it works: Every single time the user presses a key inside the input box (#searchInput), the script instantly destroys the previous clock timer using clearTimeout() and starts a fresh 300-millisecond countdown.

  • Why it's vital for your viewers: This is debouncing. Without this, if someone types "iPhone", it would slam your server with 6 web requests back-to-back. With this code, the script waits for a brief 300ms pause in typing before sending a single request. This is a crucial trick for saving server resources!

2. Client-Side Guard Syncing

if (term.length < 2) { $('#searchSuggestions').hide(); return; }
  • The Logic: This mirrors the server-side guard clause from Step 1. If a user types two letters and then backspaces one, the app immediately drops out and closes the suggestions element (.hide()), keeping the user's view clean and responsive.

3. Dynamic HTML String Interpolation Loops

data.forEach(function (item) { html += `... ${item.name} ...`; });
  • The Logic: The app receives a flat JSON collection back from the controller, iterates over it via .forEach(), and compiles fresh HTML markup on the fly using JavaScript Template Literals (the backtick ` tags).

  • The UI Features: It dynamically handles empty images with a fallback placeholder string (|| '[https://via.placeholder.com/](https://via.placeholder.com/)...'). It also uses the native JavaScript .toLocaleString() method to format prices beautifully into local currency groupings with commas (e.g., displaying RS 150,000 automatically).

4. Event Delegation Outside-Click Listener

$(document).on('click', function (e) { ... })
  • The UX Quality: If a user clicks away to view another part of the webpage, leaving a floating search dropdown hanging would look unfinished. This global click interceptor checks if the click happened inside or outside the search box. If it's outside, it hides the results wrapper instantly.


Comments