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

在本教學課程中,建立在localhost中執行的基本 ASP.NET Core (Model-View-Controller) 應用程式,並連線到搜尋服務上的 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 類別移除 [位置] 字段。 本教學課程中未使用該欄位。

新增服務資訊

針對連線,應用程式會將查詢 API 金鑰呈現給您的完整搜尋 URL。 這兩者都會在檔案中 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 搜尋 中的篩選和 OData $filter Azure AI 搜尋中的語法。

排序結果

在 hotels-sample-index 中,可排序的字段包括 Rating 和 LastRenovated。 本範例會將$OrderBy表達式新增至 [評等] 字段。

  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. 執行應用程式。 結果會以遞減順序依評等排序。

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

下一步

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

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