在 ASP.NET Core 中创建搜索应用
在本教程中,创建在 localhost 中运行并连接到搜索服务上的 hotels-sample-index 的基本 ASP.NET Core(模型-视图-控制器)应用。 本教程介绍如何执行下列操作:
- 创建基本搜索页
- 筛选结果
- 对结果进行排序
本教程重点介绍通过搜索 API 调用的服务器端操作。 尽管在客户端脚本中进行排序和筛选很常见,但在设计搜索体验时,了解如何在服务器上调用这些操作可为你提供更多选项。
本教程的示例代码可在 GitHub 上的 azure-search-dotnet-samples 存储库中找到。
先决条件
- Visual Studio
- Azure.Search.Documents NuGet 包
- Azure AI 搜索,任何层,但必须具有公用网络访问权限。
- 酒店示例索引
逐步完成导入数据向导,以在搜索服务上创建 hotels-sample-index。 或者更改 HomeController.cs
文件中的索引名称。
创建项目
启动 Visual Studio 并选择“创建新项目”。
选择“ASP.NET Core Web 应用(模型-视图-控制器)”,然后选择“下一步”。
提供项目名称,然后选择“下一步”。
在下一页上,选择“.NET 6.0”、“.NET 7.0”或“.NET 8.0”。
验证是否未选中“不使用顶级语句”。
选择“创建”。
添加 NuGet 包
在工具页上,选择“NuGet 包管理器”>“管理解决方案的 NuGet 包”。
找到
Azure.Search.Documents
并安装最新稳定版本。找到并安装
Microsoft.Spatial
包。 示例索引包含 GeographyPoint 数据类型。 安装此包可避免运行时错误。 或者,如果不想安装包,请从 Hotels 类中删除“Location”字段。 本教程中未使用该字段。
添加服务信息
对于连接,应用会向完全限定的搜索 URL 提供查询 API 密钥。 两者都在 appsettings.json
文件中指定。
修改 appsettings.json
以指定搜索服务和查询 API 密钥。
{
"SearchServiceUri": "<YOUR-SEARCH-SERVICE-URL>",
"SearchServiceQueryApiKey": "<YOUR-SEARCH-SERVICE-QUERY-API-KEY>"
}
可以从门户获取服务 URL 和 API 密钥。 由于此代码可查询而不是创建索引,因此可以使用查询密钥而不是管理密钥。
请确保指定具有 hotels-sample-index 的搜索服务。
添加模型
在此步骤中,创建表示 hotels-sample-index 架构的模型。
在解决方案资源管理器中,右键单击“模型”,并为以下代码添加名为“Hotel”的新类:
using Azure.Search.Documents.Indexes.Models; using Azure.Search.Documents.Indexes; using Microsoft.Spatial; using System.Text.Json.Serialization; namespace HotelDemoApp.Models { public partial class Hotel { [SimpleField(IsFilterable = true, IsKey = true)] public string HotelId { get; set; } [SearchableField(IsSortable = true)] public string HotelName { get; set; } [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)] public string Description { get; set; } [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.FrLucene)] [JsonPropertyName("Description_fr")] public string DescriptionFr { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string Category { get; set; } [SearchableField(IsFilterable = true, IsFacetable = true)] public string[] Tags { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public bool? ParkingIncluded { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public DateTimeOffset? LastRenovationDate { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public double? Rating { get; set; } public Address Address { get; set; } [SimpleField(IsFilterable = true, IsSortable = true)] public GeographyPoint Location { get; set; } public Rooms[] Rooms { get; set; } } }
添加名为“Address”的类,并将其替换为以下代码:
using Azure.Search.Documents.Indexes; namespace HotelDemoApp.Models { public partial class Address { [SearchableField] public string StreetAddress { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string City { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string StateProvince { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string PostalCode { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string Country { get; set; } } }
添加名为“Rooms”的类,并将其替换为以下代码:
using Azure.Search.Documents.Indexes.Models; using Azure.Search.Documents.Indexes; using System.Text.Json.Serialization; namespace HotelDemoApp.Models { public partial class Rooms { [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnMicrosoft)] public string Description { get; set; } [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.FrMicrosoft)] [JsonPropertyName("Description_fr")] public string DescriptionFr { get; set; } [SearchableField(IsFilterable = true, IsFacetable = true)] public string Type { get; set; } [SimpleField(IsFilterable = true, IsFacetable = true)] public double? BaseRate { get; set; } [SearchableField(IsFilterable = true, IsFacetable = true)] public string BedOptions { get; set; } [SimpleField(IsFilterable = true, IsFacetable = true)] public int SleepsCount { get; set; } [SimpleField(IsFilterable = true, IsFacetable = true)] public bool? SmokingAllowed { get; set; } [SearchableField(IsFilterable = true, IsFacetable = true)] public string[] Tags { get; set; } } }
添加名为“SearchData”的类,并将其替换为以下代码:
using Azure.Search.Documents.Models; namespace HotelDemoApp.Models { public class SearchData { // The text to search for. public string searchText { get; set; } // The list of results. public SearchResults<Hotel> resultList; } }
修改控制器
在本教程中,请修改默认值 HomeController
以包含在搜索服务上执行的方法。
在“模型”下的“解决方案资源管理器”中,打开
HomeController
。将默认值替换为以下内容:
using Azure; using Azure.Search.Documents; using Azure.Search.Documents.Indexes; using HotelDemoApp.Models; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; namespace HotelDemoApp.Controllers { public class HomeController : Controller { public IActionResult Index() { return View(); } [HttpPost] public async Task<ActionResult> Index(SearchData model) { try { // Check for a search string if (model.searchText == null) { model.searchText = ""; } // Send the query to Search. await RunQueryAsync(model); } catch { return View("Error", new ErrorViewModel { RequestId = "1" }); } return View(model); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } private static SearchClient _searchClient; private static SearchIndexClient _indexClient; private static IConfigurationBuilder _builder; private static IConfigurationRoot _configuration; private void InitSearch() { // Create a configuration using appsettings.json _builder = new ConfigurationBuilder().AddJsonFile("appsettings.json"); _configuration = _builder.Build(); // Read the values from appsettings.json string searchServiceUri = _configuration["SearchServiceUri"]; string queryApiKey = _configuration["SearchServiceQueryApiKey"]; // Create a service and index client. _indexClient = new SearchIndexClient(new Uri(searchServiceUri), new AzureKeyCredential(queryApiKey)); _searchClient = _indexClient.GetSearchClient("hotels-sample-index"); } private async Task<ActionResult> RunQueryAsync(SearchData model) { InitSearch(); var options = new SearchOptions() { IncludeTotalCount = true }; // Enter Hotel property names to specify which fields are returned. // If Select is empty, all "retrievable" fields are returned. options.Select.Add("HotelName"); options.Select.Add("Category"); options.Select.Add("Rating"); options.Select.Add("Tags"); options.Select.Add("Address/City"); options.Select.Add("Address/StateProvince"); options.Select.Add("Description"); // For efficiency, the search call should be asynchronous, so use SearchAsync rather than Search. model.resultList = await _searchClient.SearchAsync<Hotel>(model.searchText, options).ConfigureAwait(false); // Display the results. return View("Index", model); } public IActionResult Privacy() { return View(); } } }
修改视图
在“视图”>“主页”下的“解决方案资源管理器”中,打开
index.cshtml
。将默认值替换为以下内容:
@model HotelDemoApp.Models.SearchData; @{ ViewData["Title"] = "Index"; } <div> <h2>Search for Hotels</h2> <p>Use this demo app to test server-side sorting and filtering. Modify the RunQueryAsync method to change the operation. The app uses the default search configuration (simple search syntax, with searchMode=Any).</p> <form asp-controller="Home" asp-action="Index"> <p> <input type="text" name="searchText" /> <input type="submit" value="Search" /> </p> </form> </div> <div> @using (Html.BeginForm("Index", "Home", FormMethod.Post)) { @if (Model != null) { // Show the result count. <p>@Model.resultList.TotalCount Results</p> // Get search results. var results = Model.resultList.GetResults().ToList(); { <table class="table"> <thead> <tr> <th>Name</th> <th>Category</th> <th>Rating</th> <th>Tags</th> <th>City</th> <th>State</th> <th>Description</th> </tr> </thead> <tbody> @foreach (var d in results) { <tr> <td>@d.Document.HotelName</td> <td>@d.Document.Category</td> <td>@d.Document.Rating</td> <td>@d.Document.Tags[0]</td> <td>@d.Document.Address.City</td> <td>@d.Document.Address.StateProvince</td> <td>@d.Document.Description</td> </tr> } </tbody> </table> } } } </div>
运行示例
按 F5 编译并运行项目。 应用会在本地主机上运行,并在默认浏览器中打开。
选择“搜索”以返回所有结果。
此代码使用默认搜索配置,支持简单语法和
searchMode=Any
。 可以输入关键字、使用布尔运算符进行增强,或运行前缀搜索 (pool*
)。
在接下来的几个部分中,修改 HomeController
中的 RunQueryAsync 方法以添加筛选器和排序。
筛选结果
索引字段属性确定哪些字段可搜索、可筛选、可排序、可查找和可检索。 在 hotels-sample-index 中,可筛选字段包括“Category”、“Address/City”和“Address/StateProvince”。 本示例在“Category”上添加了 $Filter 表达式。
筛选器始终先执行,后跟一个查询(假设已指定)。
打开
HomeController
并找到 RunQueryAsync 方法。 将筛选器添加到var options = new SearchOptions()
:private async Task<ActionResult> RunQueryAsync(SearchData model) { InitSearch(); var options = new SearchOptions() { IncludeTotalCount = true, Filter = "search.in(Category,'Budget,Suite')" }; options.Select.Add("HotelName"); options.Select.Add("Category"); options.Select.Add("Rating"); options.Select.Add("Tags"); options.Select.Add("Address/City"); options.Select.Add("Address/StateProvince"); options.Select.Add("Description"); model.resultList = await _searchClient.SearchAsync<Hotel>(model.searchText, options).ConfigureAwait(false); return View("Index", model); }
运行应用程序。
单击“搜索”以运行空查询。 此筛选器返回 18 个文档,而不是最初的 50 个文档。
有关筛选器表达式的详细信息,请参阅 Azure AI 搜索中的筛选器和 Azure AI 搜索中的 OData $filter 语法。
对结果进行排序
在 hotels-sample-index 中,可排序字段包括“Rating”和“LastRenovated”。 本示例将 $OrderBy 表达式添加到“Rating”字段。
打开
HomeController
并将 RunQueryAsync 方法替换为以下版本:private async Task<ActionResult> RunQueryAsync(SearchData model) { InitSearch(); var options = new SearchOptions() { IncludeTotalCount = true, }; options.OrderBy.Add("Rating desc"); options.Select.Add("HotelName"); options.Select.Add("Category"); options.Select.Add("Rating"); options.Select.Add("Tags"); options.Select.Add("Address/City"); options.Select.Add("Address/StateProvince"); options.Select.Add("Description"); model.resultList = await _searchClient.SearchAsync<Hotel>(model.searchText, options).ConfigureAwait(false); return View("Index", model); }
运行应用程序。 结果按“Rating”的降序排序。
有关排序的详细信息,请参阅 Azure AI 搜索中的 OData $orderby 语法。
后续步骤
在本教程中,你创建了一个 ASP.NET Core (MVC) 项目,该项目连接到搜索服务,并调用搜索 API 进行服务器端筛选和排序。
如果要浏览响应用户操作的客户端代码,请考虑向解决方案添加 React 模板: