This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Tämä on vain nopea artikkeli, koska se perustuu muihin tekstihakusarjoihin, kuten Typeahead dropdown sekä Postgres koko tekstin haku. Tässä viestissä näytän, miten voit toteuttaa yksinkertaisen hakusivun käyttämällä HTMX:ää ja EF Corea ASP.NET Core -sovelluksessa.
No kyllä, sivuston otsikossa Minulla on hakutoiminto, joka tarjoaa hakusanoja (jossa tulokset kirjoitetaan reaaliajassa). Piilotan sen kuitenkin mobiilitilassa ja halusin myös linkittää hakutulokset (kuten /search/umami) omalle hakusivulle. Tämä antaa paremman käyttökokemuksen sekä mobiililaitteiden parissa työskentelyn.
Tätä varten muokkasin, miten tein kotietsinnät. Minä loin BlogSearchService
Tämä perustuu kahteen Full Text -kyselymenetelmääni. Nämä on valitettavasti jaettava kahteen menetelmään, koska kyselyt on järjestetty Postgres Full Text Search -laajennuksilla. EF.Functions.WebSearchToTsQuery("english", processedQuery)
sekä EF.Functions.ToTsQuery("english", query + ":*")
.
Ensimmäinen vaatii oikeat hakuehdot ja toinen villikorttihaut.
private IQueryable<BlogPostEntity> QueryForSpaces(string processedQuery)
{
return context.BlogPosts
.Include(x => x.Categories)
.Include(x => x.LanguageEntity)
.AsNoTrackingWithIdentityResolution()
.Where(x =>
// Search using the precomputed SearchVector
(x.SearchVector.Matches(EF.Functions.WebSearchToTsQuery("english",
processedQuery)) // Use precomputed SearchVector for title and content
|| x.Categories.Any(c =>
EF.Functions.ToTsVector("english", c.Name)
.Matches(EF.Functions.WebSearchToTsQuery("english", processedQuery)))) // Search in categories
&& x.LanguageEntity.Name == "en") // Filter by language
.OrderByDescending(x =>
// Rank based on the precomputed SearchVector
x.SearchVector.Rank(EF.Functions.WebSearchToTsQuery("english",
processedQuery)));
}
private IQueryable<BlogPostEntity> QueryForWildCard(string query)
{
return context.BlogPosts
.Include(x => x.Categories)
.Include(x => x.LanguageEntity)
.AsNoTrackingWithIdentityResolution()
.Where(x =>
// Search using the precomputed SearchVector
(x.SearchVector.Matches(EF.Functions.ToTsQuery("english",
query + ":*")) // Use precomputed SearchVector for title and content
|| x.Categories.Any(c =>
EF.Functions.ToTsVector("english", c.Name)
.Matches(EF.Functions.ToTsQuery("english", query + ":*")))) // Search in categories
&& x.LanguageEntity.Name == "en") // Filter by language
.OrderByDescending(x =>
// Rank based on the precomputed SearchVector
x.SearchVector.Rank(EF.Functions.ToTsQuery("english",
query + ":*"))); // Use precomputed SearchVector for ranking
}
Taas nämä käyttävät esilaukaisuani SearchVector
kolumni, joka päivitetään jälkiluonnista ja päivityksestä. Tämä on luotu minun DbContext
Käyttäen darbepoetiini alfaa ja darbepoetiini alfaa (ks. kohta 5. 2). OnModelCreating
menetelmä.
entity.Property(b => b.SearchVector)
.HasComputedColumnSql("to_tsvector('english', coalesce(\"Title\", '') || ' ' || coalesce(\"PlainTextContent\", ''))", stored: true);
entity.HasIndex(b => b.SearchVector)
.HasMethod("GIN");
Tämän lähestymistavan haittapuoli on jälleen se, että se toimii vain englantilaisille. Tarvitsisin tietokannan radikaalin uudelleenrakennuksen, jotta se toimisi muilla kielillä (todennäköisesti taulukko jokaiselle kielelle).
Sitten käytän näitä menetelmiä minun BlogSearchService
palauttaa tulokset hakukyselyn perusteella.
public async Task<PostListViewModel> GetPosts(string? query, int page = 1, int pageSize = 10)
{
if(string.IsNullOrEmpty(query))
{
return new PostListViewModel();
}
IQueryable<BlogPostEntity> blogPostQuery = query.Contains(" ") ? QueryForSpaces(query) : QueryForWildCard(query);
var totalPosts = await blogPostQuery.CountAsync();
var results = await blogPostQuery
.Select(x => x.ToListModel())
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return new PostListViewModel()
{
Posts = results,
TotalItems = totalPosts,
Page = page,
PageSize = pageSize
};
}
Käytän yksinkertaista tarkistaa, onko välilyöntejä kyselyssä selvittää, mitä menetelmää soittaa.
Haku-ohjain noudattaa useimmille ohjaimilleni käyttämääni kaavaa, jossa se havaitsee, tuleeko puhelu HTMX:ltä vai ei, jotta se voi lähettää joko osittaisen tai täyden layout-sivun (mikä tarkoittaa, että se toimii suoran navigoinnin sekä HTMX-pyyntöjen vuoksi).
[Route("search")]
public class SearchController(
BaseControllerService baseControllerService,
BlogSearchService searchService,
ILogger<SearchController> logger)
: BaseController(baseControllerService, logger)
{
[HttpGet]
[Route("{query?}")]
public async Task<IActionResult> Search([FromRoute] string? query)
{
var searchResults = await searchService.GetPosts(query);
var searchModel = new SearchResultsModel
{
Query = query,
SearchResults = searchResults
};
searchModel = await PopulateBaseModel(searchModel);
searchModel.SearchResults.LinkUrl = Url.Action("SearchResults", "Search");
if (Request.IsHtmx()) return PartialView("SearchResults", searchModel);
return View("SearchResults", searchModel);
}
[HttpGet]
[Route("results")]
public async Task<IActionResult> SearchResults([Required] string query, int page = 1, int pageSize = 10)
{
var searchResults = await searchService.GetPosts(query, page, pageSize);
var searchModel = new SearchResultsModel
{
Query = query,
SearchResults = searchResults
};
searchModel = await PopulateBaseModel(searchModel);
searchModel.SearchResults.LinkUrl = Url.Action("SearchResults", "Search");
if (Request.IsHtmx()) return PartialView("_SearchResultsPartial", searchModel.SearchResults);
return View("SearchResults", searchModel);
}
}
Tämä on koko ohjain, näet, että minulla on kaksi toimintoa, joista toinen palauttaa sivun (valinnaisesti asutut tulokset) ja toinen palauttaa vain tulokset HTMX-pyyntöihin.
if (Request.IsHtmx()) return PartialView("_SearchResultsPartial", searchModel.SearchResults);
Huomaat, että tämä palauttaa valinnaisesti _SearchResultsPartial
katso, jos pyyntö on HTMX-tulospyyntö.
Tämä on aika yksinkertainen Paritial View, jossa on haku bittejä ja tuloksia.
@model Mostlylucid.Models.Blog.PostListViewModel
<div class="pt-2" id="content">
@if (Model.Posts?.Any() is true)
{
<div class="inline-flex w-full items-center justify-center print:!hidden">
@if (Model.TotalItems > Model.PageSize)
{
<pager
x-ref="pager"
link-url="@Model.LinkUrl"
hx-boost="true"
hx-target="#content"
hx-swap="show:none"
page="@Model.Page"
page-size="@Model.PageSize"
total-items="@Model.TotalItems"
hx-headers='{"pagerequest": "true"}'>
</pager>
}
<partial name="_Pager" model="Model"/>
</div>
@foreach (var post in Model.Posts)
{
<partial name="_ListPost" model="post"/>
}
}
</div>
Käytän samaa _ListPost
Osittainen näkymä aina, kun minun tarvitsee listata virkoja.
@model Mostlylucid.Models.Blog.PostListModel
<div class="border-b border-grey-lighter pb-8 mb-8">
<a asp-controller="Blog" asp-action="Show" hx-boost="true" hx-swap="show:window:top" hx-target="#contentcontainer" asp-route-slug="@Model.Slug"
class="block font-body text-lg font-semibold transition-colors hover:text-green text-blue-dark dark:text-white dark:hover:text-secondary">@Model.Title</a>
<div class="flex flex-wrap space-x-2 items-center py-4 print:!hidden">
@foreach (var category in Model.Categories)
{
<partial name="_Category" model="category"/>
}
@{ var languageModel = (Model.Slug, Model.Languages, Model.Language); }
<partial name="_LanguageList" model="languageModel"/>
</div>
<div class="block font-body text-black dark:text-white">@Model.Summary</div>
<div class="flex items-center pt-4">
<p class="pr-2 font-body font-light text-primary light:text-black dark:text-white">
@Model.PublishedDate.ToString("f")
</p>
<span class="font-body text-grey dark:text-white">//</span>
<p class="pl-2 font-body font-light text-primary light:text-black dark:text-white">
@Model.ReadingTime
</p>
</div>
</div>
HtMX:n käyttö täällä on jälleen aika yksinkertaista. Kiinnitän vain nappiin (tällä kertaa päätin, etten muuta näppäimistön syötettä tai muuta URL-osoitetta) ja lähetän pyynnön, kun painiketta napsautetaan.
Sisällytän kyselyn pyyntöön käyttäen hx-include
ja kohdistaa #content
Div korvaa tulokset.
<div class="flex items-center gap-2 bg-neutral-500 bg-opacity-10 p-2 rounded-lg">
<button
hx-get="@Url.Action("SearchResults", "Search")"
hx-target="#content"
hx-include="[name='query']"
hx-swap="outerHTML"
class="btn btn-outline btn-sm flex items-center gap-2 text-black dark:text-white">
Search
<i class="bx bx-search text-lg"></i>
</button>
<input
type="text"
placeholder="Search..."
value="@Model.Query"
name="query"
class="input input-sm border-0 grow text-black dark:text-white bg-transparent focus:outline-none"/>
</div>
Joten seuraavat joitakin kommentteja Khalid Päätin tehostaa tätä toiminnallisuutta, jotta haku olisi:
Tulevaisuudessa minun täytyy lisätä sivun koko toiminnallisuus takaisin; se on biisi hakata ja tarvitsee tukea lisävaatimuksia.
Tätä varten käärin syötteen ensin muotoon ja käytin Alpine.js:ia lähettääkseni lomakkeen käyttäjän kirjoittaessa.
Huomaat, että käytän x-data
Luon kyselylle reaktiivisen muuttujan ja tarkistan sitten kyselyn pituuden, jotta voin päättää, toimitetaanko lomake.
<form
x-data="{ query: '@Model.Query', checkSubmit() { if (this.query.length > 2) { $refs.searchButton.click(); } } }"
class="flex items-center gap-2 bg-neutral-500 bg-opacity-10 p-2 rounded-lg"
action="@Url.Action("Search", "Search")"
hx-push-url="true"
hx-boost="true"
hx-target="#content"
hx-swap="outerHTML show:window:top"
hx-headers='{"pagerequest": "true"}'>
<button
type="submit"
x-ref="searchButton"
class="btn btn-outline btn-sm flex items-center gap-2 text-black dark:text-white">
Search
<i class="bx bx-search text-lg"></i>
</button>
<input
type="text"
placeholder="Search..."
name="query"
value="@Model.Query"
x-model="query"
x-on:input.debounce.200ms="checkSubmit"
x-on:keydown.enter.prevent="$refs.searchButton.click()"
class="input input-sm border-0 grow text-black dark:text-white bg-transparent focus:outline-none"
/>
</form>
Jotta sama ohjaintoiminto voitaisiin käyttää uudelleen, asetin myös pagerequest
Otsikko osoittaa, että kyseessä on paginoitu pyyntö.
Käytän myös Alpine.js x-on:keydown.enter.prevent
Käynnistääksesi painikkeen painalluksen, kun Enter-näppäintä painetaan, ja poistaaksesi syötteestä liikaa pyyntöjä.
Ohjaimessa poistin SearchResults -toiminnon ja lisäsin sen sijaan pääosaan lisää "älykkyyttä". Search
toimia sekä alustavan haun että paginoitujen pyyntöjen käsittelemiseksi.
Tässä näet Lisään ylimääräisen parametrin nimeltä pagerequest
Määrittääkseen, onko kyseessä paginoitu pyyntö vai ei, ja ilmoittaakseen, että tämä pitäisi kirjata otsikkokokoelmasta.
[HttpGet]
[Route("")]
public async Task<IActionResult> Search(string? query, int page = 1, int pageSize = 10,[FromHeader] bool pagerequest=false)
{
var searchResults = await searchService.GetPosts(query, page, pageSize);
var searchModel = new SearchResultsModel
{
Query = query,
SearchResults = searchResults
};
searchModel = await PopulateBaseModel(searchModel);
var linkUrl = Url.Action("Search", "Search");
searchModel.SearchResults.LinkUrl = linkUrl;
if(pagerequest && Request.IsHtmx()) return PartialView("_SearchResultsPartial", searchModel.SearchResults);
if (Request.IsHtmx()) return PartialView("SearchResults", searchModel);
return View("SearchResults", searchModel);
}
Sitten saan tulokset ja havaitsen tämän otsikon selvittääkseni, mikä näkymä / osittainen näkymä palaa.
Lisäsin lisäksi itsenäisen eläkkeelle siirtymistoimen, jolla hoidellaan sellaisia asioita kuin /search/umami
Voit suunnata päähakusivulle kyselyn myötä.
[HttpGet]
[Route("{query}")]
public IActionResult InitialSearch([FromRoute] string query)
{
return RedirectToAction("Search", new { query });
}
Aika yksinkertaista, vai mitä? Tämä on suoraviivainen toteutus hakusivulle, jossa käytetään HTMX:ää ja EF Corea ASP.NET Core -sovelluksessa. Voit helposti laajentaa tämän koskemaan useampia ominaisuuksia, kuten suodatusta, lajittelua tai jopa integroitumista muihin hakupalveluihin. Keskeistä takea on se, miten HTMX:ää voidaan hyödyntää sujuvan käyttökokemuksen saavuttamiseksi ja samalla pitää backend-logiikka puhtaana ja tehokkaana.