分享方式:


在 ASP.NET Core 中建立搜尋應用程式

在本教學課程中,建立基本 ASP.NET Core (Model-View-Controller) 應用程式,其在 localhost 中執行並連線至搜尋服務上的 hotels-sample-index。 在本教學課程中,您將了解如何:

  • 建立基本搜尋分頁
  • 篩選結果
  • 排序結果

本教學課程會著重於透過搜尋 API 呼叫的伺服器端作業。 儘管在用戶端指令碼中進行排序和篩選相當常見,但了解如何在伺服器上叫用這些作業,可讓您在設計搜尋體驗時擁有更多選項。

本教學課程的範例程式碼可在 GitHub 上的 azure-search-dotnet-samples 存放庫中找到。

必要條件

逐步執行匯入資料精靈,以在搜尋服務上建立 hotels-sample-index。 或者,變更 HomeController.cs 檔案中的索引名稱。

建立專案

  1. 啟動 Visual Studio 並選取 [建立新專案]

  2. 選取 [ASP.NET Core Web 應用程式 (Model-View-Controller)],然後選取 [下一步]

  3. 提供專案名稱,然後選取 [下一步]

  4. 在下一頁中,選取 [.NET 6.0] 或 [.NET 7.0] 或 [.NET 8.0]

  5. 確認未勾選 [不要使用最上層陳述式]

  6. 選取 建立

新增 NuGet 套件

  1. 在 [工具],選取 [NuGet 套件管理員] > [管理解決方案的 NuGet 套件]

  2. 瀏覽 Azure.Search.Documents 並安裝最新的穩定版本。

  3. 瀏覽並安裝 Microsoft.Spatial 套件。 範例索引包含 GeographyPoint 資料類型。 安裝此套件可避免執行階段錯誤。 或者,如果您不想安裝此套件,請從 Hotels 類別中移除 [位置] 欄位。 本教學課程中未使用該欄位。

新增服務資訊

針對連線,應用程式會向您的完整搜尋 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 結構描述的模型。

  1. 在 [方案總管] 中,以滑鼠右鍵選取 [模型],然後為下列程式碼新增名為 "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; }
         }
     }
    
  2. 新增名為 "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; }
         }
     }
    
  3. 新增名為 "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; }
         }
     }
    
  4. 新增名為 "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,以包含在搜尋服務上執行的方法。

  1. 在 [方案總管] 中的 [模型] 底下,開啟 HomeController

  2. 使用下列內容取代預設內容:

    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();
             }
         }
     }
    

修改檢視

  1. 在 [方案總管] 中的 [檢視] > [首頁] 底下,開啟 index.cshtml

  2. 使用下列內容取代預設內容:

    @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>
    

執行範例

  1. F5 來編譯和執行專案。 應用程式會在本機主機上執行,並在預設瀏覽器中開啟。

  2. 選取 [搜尋] 傳回所有結果。

  3. 此程式碼會使用預設搜尋設定,支援簡單語法searchMode=Any。 您可以輸入關鍵字、使用布林運算子進行擴增,或執行前置詞搜尋 (pool*)。

在接下來幾節中,修改 HomeController 中的 RunQueryAsync 方法,以新增篩選和排序。

篩選結果

索引欄位屬性決定哪些欄位可搜尋、可篩選、可排序、可 Facet 和可擷取。 在 hotels-sample-index 中,可篩選的欄位包括 Category、Address/City 和 Address/StateProvince。 此範例會在 Category 上新增 $Filter 表達式。

篩選一律會先執行,接著執行查詢 (假設已指定查詢)。

  1. 開啟 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);
     }
    
  2. 執行應用程式。

  3. 選取 [搜尋] 以執行空的查詢。 篩選會傳回 18 份文件,而非原始的 50 份文件。

如需篩選條件運算式的詳細資訊,請參閱 Azure AI 搜尋服務中的篩選條件,以及 Azure AI 搜尋服務中的 OData $filter 語法

排序結果

在 hotels-sample-index 中,可排序的欄位包括 Rating 和 LastRenovated。 此範例會將 $OrderBy 表達式新增至 Rating 欄位。

  1. 開啟 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);
     }
    
  2. 執行應用程式。 結果會根據 Rating 遞減排序。

如需排序的詳細資訊,請參閱 Azure AI 搜尋服務中的 OData $orderby 語法

下一步

在本教學課程中,您已建立 ASP.NET Core (MVC) 專案,該專案連線至搜尋服務,並呼叫搜尋 API 進行伺服器端篩選和排序。

如果您要探索回應使用者動作的用戶端程式碼,請考慮將 React 範本新增至解決方案中: