ASP=.\NET 核心的 EF Core 使用 HTMX @&EF 简单搜索
一. 导言导言 导言 导言 导言
这只是一篇简短的文章,因为它以全文搜索系列中的其他文章为基础。 字头下调 和 Postgres 全文搜索. 我将展示您如何使用 HTMX 和 EF Core 在 ASP @.NET 核心应用程序中执行一个简单的搜索页面@.}
Don'我是不是已经搜索过了?
嗯,是的,}(在站点信头中) 我有一个搜索功能 提供打字标题 @(}当您输入结果时 即刻到达@).}然而我用移动模式隐藏了这一点 我还想将搜索结果连接起来 {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}@)_@ to a specast page .}这让用户体验更好,
搜索服务处
为了做到这一点,我改变了我的搜索方法 . 我创建了一个 BlogSearchService@ , @% 这是基于我的两种完整文本查询方法@.}这些不幸需要分为两个方法, EF.Functions.WebSearchToTsQuery("english", processedQuery) 和 EF.Functions.ToTsQuery("english", query + ":*").
第一组采用适当的搜索条件,第二组使用通配符搜索@.
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
}
这些又用我的预数 SearchVector 栏,该列更新后创建并更新@ . @% 此创建于 DbContext 使用 OnModelCreating 方法@.
entity.Property(b => b.SearchVector)
.HasComputedColumnSql("to_tsvector('english', coalesce(\"Title\", '') || ' ' || coalesce(\"PlainTextContent\", ''))", stored: true);
entity.HasIndex(b => b.SearchVector)
.HasMethod("GIN");
这个方法的另一个缺点是,它只对英语有效 as @-iss.}我需要彻底重建数据库 让它为其他语言工作
然后我用这些方法 BlogSearchService 返回基于搜索查询的检查结果@. @%
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
};
}
我使用简单的检查来确定查询中是否有空格, 以确定调用“ .” 的方法@ title
搜索控制器
搜索控制器遵循我对大多数我的控制器所使用的模式, 它检测调用是否来自 HTMX 以便发送部分或完整的布局页面 @ (@ 意指它用于直接导航以及HTMX请求\ MS K1\
[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);
}
}
这是整个控制器@ ,} 您可以看到我有两个动作\ ,}一个返回页面 MS K2 可选包含结果的 page )}和一个只返回 HTMX 请求的结果\ MPK4}
if (Request.IsHtmx()) return PartialView("_SearchResultsPartial", searchModel.SearchResults);
搜索结果部分
您可以看到此可选返回 _SearchResultsPartial 查看请求是否为 HTMX 结果请求@. @%
这是一个非常简单的 Paritial 视图, 它有传呼比特和结果@. @
@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>
列表部分视图
我用同样的 _ListPost 偏移视图, 只要我需要列出位置@. @%
@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和搜索页面
我在此使用 HTMX 很简单@. @I just hook into the button {(} 这一次我决定不更改按键上输入的\ /}修改 URL)}, 当点击按钮时发送请求 *%.}
I 在请求中包含查询使用 hx-include 目标 #content div 替换结果@. @%
<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>
最新更新
因此,继一些评论之后 哈立德 我决定加强此功能, 使搜索成为#:}
- 在 Enter 键按键触发
- 输入两个以上的字符后触发“% ( @ @ oso” 变成一个松散的字头@ MS K1
未来我需要将页面大小功能重新添加到“;”中,
更新搜索表单
要做到这一点,我首先将输入以窗体包装,然后使用 Alpine.js 在用户键入@.}时提交表格。
你可以看到我用的是 x-data 来为查询创建一个活性变量,然后我检查查询的长度以确定是否提交窗体@.
<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>
为了重新使用同样的控制器动作,我还设定了 pagerequest 显示此请求的页眉@ . @ action
我也使用阿尔卑斯山 x-on:keydown.enter.prevent 在按下 Enter 键时单击按钮,并调出输入以阻止太多请求@. @%
主计长最新情况
在控制器中, 我删除了搜索Results 动作, 相反在主控件上添加更多的 @ ' intelligence@ MS K1 Search 用于处理初始搜索和被忽略请求的动作@. @%
在这里您可以看到我加了一个额外的参数 pagerequest 来确定此请求是否为附加请求, 并显示应该从信头收藏中填入该请求@. @ @ @ action
[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);
}
然后我就会得到结果 并检测到这个信头, 以确定哪个视图@ / @ partview 返回Q. {
我还添加了一个精细的退休者行动 来处理类似 /search/umami 以查询.重定向到主搜索页面
[HttpGet]
[Route("{query}")]
public IActionResult InitialSearch([FromRoute] string query)
{
return RedirectToAction("Search", new { query });
}
在结论中的结论
这是使用 HTMX 和 EF Core 在 ASP @. @NET 核心应用程序@.}您可以轻松扩展此功能以包括更多功能, 如过滤{,}排序},}(或甚至与其他搜索服务整合).}关键取用是如何利用HTMX 为用户提供顺利的经验,同时保持后端逻辑的清洁和效率>.}