在 ASP.NET Core 中建立搜尋應用程式
在本教學課程中,建立基本 ASP.NET Core (Model-View-Controller) 應用程式,其在 localhost 中執行並連線至搜尋服務上的 hotels-sample-index。 在本教學課程中,您將了解如何:
- 建立基本搜尋分頁
- 篩選結果
- 排序結果
本教學課程會著重於透過搜尋 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 應用程式 (Model-View-Controller)],然後選取 [下一步]。
提供專案名稱,然後選取 [下一步]。
在下一頁中,選取 [.NET 6.0] 或 [.NET 7.0] 或 [.NET 8.0]。
確認未勾選 [不要使用最上層陳述式]。
選取 建立。
新增 NuGet 套件
在 [工具],選取 [NuGet 套件管理員] > [管理解決方案的 NuGet 套件]。
瀏覽
Azure.Search.Documents
並安裝最新的穩定版本。瀏覽並安裝
Microsoft.Spatial
套件。 範例索引包含 GeographyPoint 資料類型。 安裝此套件可避免執行階段錯誤。 或者,如果您不想安裝此套件,請從 Hotels 類別中移除 [位置] 欄位。 本教學課程中未使用該欄位。
新增服務資訊
針對連線,應用程式會向您的完整搜尋 URL 提供查詢 API 金鑰。 這兩者皆在 appsettings.json
檔案中指定。
修改 appsettings.json
以指定您的搜尋服務和查詢 API 金鑰。
{
"SearchServiceUri": "<YOUR-SEARCH-SERVICE-URL>",
"SearchServiceQueryApiKey": "<YOUR-SEARCH-SERVICE-QUERY-API-KEY>"
}
您可以從 Azure 入口網站 取得服務 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 方法,以新增篩選和排序。
篩選結果
索引欄位屬性決定哪些欄位可搜尋、可篩選、可排序、可 Facet 和可擷取。 在 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 範本新增至解決方案中: