#
Active Search
#
Implementing Active Search with htmx and ASP.NET Core
The Active Search pattern is one of the most popular use cases for htmx, providing a "search-as-you-type" experience without the complexity of a full-blown JavaScript framework. Here's a look at how it's implemented in our ActiveSearch demo.
#
The Frontend: Razor & htmx
In Index.cshtml, we define a search input that triggers a request to the server as the user types.
<form>
@Html.AntiForgeryToken()
<input class="form-control" type="search"
name="searchText" placeholder="Begin Typing To Search Users..."
hx-post="@Url.Page("Index", "Search")"
hx-trigger="keyup changed delay:500ms, search"
hx-target="#search-results"
hx-indicator=".htmx-indicator">
</form>
<table class="table">
<thead>
<tr><th>Country</th></tr>
</thead>
<tbody id="search-results">
<!-- Results will be injected here -->
</tbody>
</table>
Key htmx attributes used:
hx-post: Tells htmx to make a POST request to theSearchhandler on the current Page.hx-trigger: Specifies when to fire the request. We usekeyup changed delay:500msso it only fires 500ms after the user stops typing, avoiding excessive server hits.hx-target: Tells htmx where to put the returned HTML (in this case, our table body).hx-indicator: Shows a loading spinner while the request is in flight.
#
The Backend: C# PageModel
On the server side in Index.cshtml.cs, we handle the search request. The OnPostSearch method fetches data from an external API (REST Countries) based on the input and returns a partial view.
[BindProperty]
public string SearchText { get; set; }
public List<Country> Countries { get; set; }
public async Task<PartialViewResult> OnPostSearch()
{
Countries = new();
try
{
var response = await _httpClient.GetAsync($"https://restcountries.com/v3.1/name/{SearchText}");
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
var json = JsonArray.Parse(result);
if (json != null)
{
foreach (var country in json.AsArray())
{
var name = country?["name"]?["common"]?.ToString();
if (name != null) Countries.Add(new(name));
}
}
}
}
catch (Exception) { /* Handle errors */ }
return Partial("_searchResult", Countries);
}
#
The Result: Partial View
Finally, _searchResult.cshtml renders just the rows needed for the table:
@model List<Country>
@foreach (var c in Model)
{
<tr>
<td>@c.Name</td>
</tr>
}
This approach keeps the logic simple, the state on the server, and the UI fast and responsive.