教學課程:使用 .NET SDK 在 Azure 認知搜尋 中建立您的第一個搜尋應用程式

本教學課程說明如何建立 Web 應用程式,使用 Azure 認知搜尋和 Visual Studio 來查詢並傳回搜尋索引的結果。

在本教學課程中,您將了解如何:

  • 設定開發環境
  • 模型資料結構
  • 建立網頁來收集查詢輸入和顯示結果
  • 定義搜尋方法
  • 測試應用程式

您也將瞭解搜尋呼叫的簡單程度。 程式碼中的索引鍵語句會封裝在下列幾行中:

var options = new SearchOptions()
{
    // The Select option specifies fields for the result set
    options.Select.Add("HotelName");
    options.Select.Add("Description");
};

var searchResult = await _searchClient.SearchAsync<Hotel>(model.searchText, options).ConfigureAwait(false);
model.resultList = searchResult.Value.GetResults().ToList();

只有一個呼叫會查詢搜尋索引並傳回結果。

正在搜尋 *pool*

概觀

本教學課程使用 hotels-sample-index,您可以逐步執行匯入 資料快速入門,在自己的搜尋服務上快速建立。 索引包含虛構的旅館資料,可在每個搜尋服務中作為內建資料來源使用。

本教學課程的第一課會建立基本的查詢結構和搜尋頁面,您將在後續的課程中增強,以包含分頁、Facet 和預先輸入體驗。

您可以在下列專案中找到完成的程式碼版本:

必要條件

從 GitHub 安裝並執行專案

如果您想要跳到運作中的應用程式,請遵循下列步驟下載並執行已完成的程式碼。

  1. 在 Github 上尋找範例:建立第一個應用程式

  2. 根資料夾中,選取 [程式碼],接著選取 [複製] 或 [下載 ZIP],以建立專案的私人本機複本。

  3. 使用 Visual Studio 瀏覽至基本搜尋頁面 ("1-basic-search-page") 的解決方案並加以開啟,然後選取 [啟動但不偵錯] (或按下 F5 鍵) 以建置及執行程式。

  4. 這是旅館索引,因此,您可以用某些字輸入,以搜尋旅館 (例如「wifi」、「view」、「bar」、「parking」) 。 檢查結果。

    正在搜尋 *wifi*

此應用程式包含更複雜的搜尋基本元件。 如果您不熟悉搜尋開發,您可以逐步重新建立此應用程式,以瞭解工作流程。 下列各節將示範如何。

設定開發環境

若要從頭開始建立此專案,並因此強化Azure 認知搜尋的概念,請從 Visual Studio 專案開始。

  1. 在 Visual Studio 中,選取 [新增>專案],然後ASP.NET Core Web 應用程式 (Model-View-Controller)

    建立一個雲端專案

  2. 為專案命名,例如 "FirstSearchApp",並設定位置。 選取 [下一步]。

  3. 接受目標架構、驗證類型和 HTTPS 的預設值。 選取 [建立]。

  4. 安裝用戶端程式庫。 在[工具>NuGet 套件管理員>管理方案的 NuGet 套件...] 中,選取[流覽],然後搜尋 「azure.search.documents」。 安裝 Azure.Search.Documents (第 11 版或更新版本),接受授權合約和相依性。

    使用 NuGet 來新增 Azure 程式庫

在此步驟中,設定端點和存取金鑰,以連線到提供 旅館範例索引的搜尋服務。

  1. 開啟 appsettings.json ,並將預設行取代為搜尋服務 URL (格式 https://<service-name>.search.windows.net) ,以及搜尋服務的 系統管理員或查詢 API 金鑰 。 因為您不需要建立或更新索引,所以您可以使用本教學課程的查詢索引鍵。

    {
        "SearchServiceUri": "<YOUR-SEARCH-SERVICE-URI>",
        "SearchServiceQueryApiKey": "<YOUR-SEARCH-SERVICE-API-KEY>"
    }
    
  2. 在方案總管中選取檔案,然後在 [屬性] 中,將 [複製到輸出目錄] 設定變更為 [有更新時才複製]。

    將應用程式設定複製到輸出

模型資料結構

模型 (C#類別) 用來傳達用戶端 (檢視)、伺服器 (控制器),以及使用 MVC (模型、檢視、控制器) 架構之 Azure 雲端之間的資料。 一般而言,這些模型會反映所存取資料的結構。

在此步驟中,您將會建立搜尋索引的資料結構,以及在檢視/控制器通訊中使用的搜尋字串模型。 在旅館索引中,每個旅館都有許多房間,而且每個旅館都有多個部分的地址。 合併在一起,旅館完整表示是階層式和巢狀資料結構。 您將需要三個類別來建立每個元件。

HotelAddressRoom 這組類別已知為複雜類型,這是 Azure 認知搜尋的一項重要功能。 複雜類型可以是多層深的類別和子類別,而且會使用比簡單類型 (只包含基本成員的類別) 更複雜的資料結構表示。

  1. 在 [方案總管] 中,以滑鼠右鍵按一下 [模型] > [新增] > [新增項目]。

  2. 選取 [類別 ],並將專案命名為 Hotel.cs。 以下列程式碼取代 Hotel.cs 的所有內容。 請注意類別的 AddressRoom 成員,這些欄位本身是類別,因此您也需要這些欄位的模型。

    using Azure.Search.Documents.Indexes;
    using Azure.Search.Documents.Indexes.Models;
    using Microsoft.Spatial;
    using System;
    using System.Text.Json.Serialization;
    
    namespace FirstAzureSearchApp.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 Room[] Rooms { get; set; }
        }
    }
    
  3. 重複建立 Address 類別模型的相同程序,將檔案命名為 Address.cs。 將內容取代如下。

    using Azure.Search.Documents.Indexes;
    
    namespace FirstAzureSearchApp.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; }
        }
    }
    
  4. 同樣地,依照建立 Room 類別的相同程序進行,並將檔案命名為 Room.cs。

    using Azure.Search.Documents.Indexes;
    using Azure.Search.Documents.Indexes.Models;
    using System.Text.Json.Serialization;
    
    namespace FirstAzureSearchApp.Models
    {
        public partial class Room
        {
            [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; }
        }
    }
    
  5. 您在本教學課程中建立的最後一個模型是名為 SearchData 的類別,其代表使用者的輸入 (searchText) 和搜尋的輸出 (resultList)。 輸出的類型 (SearchResults<Hotel> ) 非常重要,因為這個類型完全符合搜尋的結果,以及您需要將此參考傳遞到檢視。 使用下列程式碼來取代預設範本。

    using Azure.Search.Documents.Models;
    
    namespace FirstAzureSearchApp.Models
    {
        public class SearchData
        {
            // The text to search for.
            public string searchText { get; set; }
    
            // The list of results.
            public SearchResults<Hotel> resultList;
        }
    }
    

建立網頁

專案範本隨附 Views 資料夾中的多個用戶端檢視。 確切的檢視取決於您使用 (3.1 的 Core .NET 版本,此範例) 。 在本教學課程中,您將修改 Index.cshtml 以包含搜尋頁面的元素。

刪除 Index.cshtml 中完整的內容,並使用下列步驟重建檔案。

  1. 本教學課程在檢視中使用兩個小型影像:Azure 標誌和搜尋放大鏡圖示 (azure-logo.png 和 search.png)。 將來自 GitHub 專案的影像複製到您專案中的 wwwroot/images 資料夾。

  2. Index.cshtml 的第一行應該參考在用戶端 (檢視) 和伺服器 (控制站) 之間傳達資料所使用的模型,也就是先前建立的 SearchData 模型。 在 Index.cshtml 檔案中加入這一行。

    @model FirstAzureSearchApp.Models.SearchData
    
  3. 這是輸入檢視標題的標準做法,因此下一行應該是:

    @{
        ViewData["Title"] = "Home Page";
    }
    
  4. 在標題之後,輸入 HTML 樣式表單的參考,您很快就會建立。

    <head>
        <link rel="stylesheet" href="~/css/hotels.css" />
    </head>
    
  5. 此檢視的主體會處理兩個使用案例。 首先,在輸入任何搜尋文字之前,第一次使用時必須提供空白頁面。 其次,除了搜尋文字方塊之外,還必須針對重複的查詢處理結果。

    若要處理這兩種情況,您必須檢查提供給檢視的模型是否為 Null。 Null 模型表示第一個使用案例 (應用程式的初始執行)。 將下列內容加入至 Index.cshtml 檔案,並完整閱讀註解。

    <body>
    <h1 class="sampleTitle">
        <img src="~/images/azure-logo.png" width="80" />
        Hotels Search
    </h1>
    
    @using (Html.BeginForm("Index", "Home", FormMethod.Post))
    {
        // Display the search text box, with the search icon to the right of it.
        <div class="searchBoxForm">
            @Html.TextBoxFor(m => m.searchText, new { @class = "searchBox" }) <input class="searchBoxSubmit" type="submit" value="">
        </div>
    
        @if (Model != null)
        {
            // Show the result count.
            <p class="sampleText">
                @Model.resultList.TotalCount Results
            </p>
    
            var results = Model.resultList.GetResults().ToList();
    
            @for (var i = 0; i < results.Count; i++)
            {
                // Display the hotel name and description.
                @Html.TextAreaFor(m => results[i].Document.HotelName, new { @class = "box1" })
                @Html.TextArea($"desc{i}", results[i].Document.Description, new { @class = "box2" })
            }
        }
    }
    </body>
    
  6. 新增樣式表。 在 Visual Studio 的 [檔案] > [新增] > [檔案] 中,選取 [樣式表] (醒目提示 [一般])。

    使用下列程式碼來取代預設程式碼。 我們不會更詳細地進入此檔案,樣式是標準 HTML。

    textarea.box1 {
        width: 648px;
        height: 30px;
        border: none;
        background-color: azure;
        font-size: 14pt;
        color: blue;
        padding-left: 5px;
    }
    
    textarea.box2 {
        width: 648px;
        height: 100px;
        border: none;
        background-color: azure;
        font-size: 12pt;
        padding-left: 5px;
        margin-bottom: 24px;
    }
    
    .sampleTitle {
        font: 32px/normal 'Segoe UI Light',Arial,Helvetica,Sans-Serif;
        margin: 20px 0;
        font-size: 32px;
        text-align: left;
    }
    
    .sampleText {
        font: 16px/bold 'Segoe UI Light',Arial,Helvetica,Sans-Serif;
        margin: 20px 0;
        font-size: 14px;
        text-align: left;
        height: 30px;
    }
    
    .searchBoxForm {
        width: 648px;
        box-shadow: 0 0 0 1px rgba(0,0,0,.1), 0 2px 4px 0 rgba(0,0,0,.16);
        background-color: #fff;
        display: inline-block;
        border-collapse: collapse;
        border-spacing: 0;
        list-style: none;
        color: #666;
    }
    
    .searchBox {
        width: 568px;
        font-size: 16px;
        margin: 5px 0 1px 20px;
        padding: 0 10px 0 0;
        border: 0;
        max-height: 30px;
        outline: none;
        box-sizing: content-box;
        height: 35px;
        vertical-align: top;
    }
    
    .searchBoxSubmit {
        background-color: #fff;
        border-color: #fff;
        background-image: url(/images/search.png);
        background-repeat: no-repeat;
        height: 20px;
        width: 20px;
        text-indent: -99em;
        border-width: 0;
        border-style: solid;
        margin: 10px;
        outline: 0;
    }
    
  7. 將樣式表檔案儲存為 hotels.css,並與預設的 site.css 檔案一起儲存到 wwwroot/css 資料夾中。

如此便完成我們的檢視。 此時,模型和檢視都已完成。 只剩下用控制器將所有項目整合在一起。

定義方法

在此步驟中,請修改首頁控制器的內容。

  1. 開啟 HomeController.cs 檔案,然後將 using 陳述式取代如下。

    using Azure;
    using Azure.Search.Documents;
    using Azure.Search.Documents.Indexes;
    using FirstAzureSearchApp.Models;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Configuration;
    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.Threading.Tasks;
    

加入 Index 方法

在 MVC 應用程式中,Index() 方法是任何控制器的預設動作方法。 此方法會開啟索引 HTML 頁面。 本教學課程會針對應用程式啟動使用案例使用預設方法 (不採用任何參數):轉譯空白的搜尋頁面。

在本節中,我們會擴充方法以支援第二個使用案例:當使用者輸入搜尋文字時轉譯頁面。 為了支援這種情況,已擴充索引方法以採用模型作為參數。

  1. 在預設的 Index() 方法後面加上下列方法。

        [HttpPost]
        public async Task<ActionResult> Index(SearchData model)
        {
            try
            {
                // Ensure the search string is valid.
                if (model.searchText == null)
                {
                    model.searchText = "";
                }
    
                // Make the Azure Cognitive Search call.
                await RunQueryAsync(model);
            }
    
            catch
            {
                return View("Error", new ErrorViewModel { RequestId = "1" });
            }
            return View(model);
        }
    

    請注意方法的 async 宣告,以及對 RunQueryAsync 進行的 await 呼叫。 這些關鍵字負責讓呼叫變成非同步的,因此可避免阻絕伺服器上的執行緒。

    catch區塊會使用已建立的預設錯誤模型。

請注意錯誤處理以及其他預設檢視和方法

根據您使用的 .NET Core 版本,會建立一組稍微不同的預設檢視。 針對 .NET Core 3.1,預設檢視是 [索引]、[隱私權] 和 [錯誤]。 您可以在執行應用程式時檢視這些預設頁面,並檢查它們在控制器中的處理方式。

您將在本教學課程稍後測試錯誤檢視。

在 GitHub 範例中,已刪除未使用的檢視及其相關聯的動作。

加入 RunQueryAsync 方法

Azure 認知搜尋呼叫會封裝在我們的 RunQueryAsync 方法中。

  1. 首先,加入一些靜態變數以設定 Azure 服務,然後加入一個起始這些變數的呼叫。

        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");
        }
    
  2. 現在,加入 RunQueryAsync 方法本身。

    private async Task<ActionResult> RunQueryAsync(SearchData model)
    {
        InitSearch();
    
        var options = new SearchOptions() 
        { 
            IncludeTotalCount = true
        };
    
        // Enter Hotel property names into this list so only these values will be returned.
        // If Select is empty, all values will be returned, which can be inefficient.
        options.Select.Add("HotelName");
        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);
    }
    

    在此方法中,先確定 Azure 設定已起始,然後設定一些搜尋選項。 [選取] 選項會指定要在結果中傳回哪些欄位,因而符合 hotel 類別中的屬性名稱。 如果您省略 Select,則會傳回所有未隱藏的欄位,如果您只對所有可能欄位的子集感興趣,這可能會沒有效率。

    搜尋的非同步呼叫會制定要求 (模型化為 searchText) 和回應 (模型化為 searchResult)。 如果您要偵錯此程式碼,如果您需要檢查model.resultList的內容,SearchResult類別是設定中斷點的好候選項目。 您應該發現它是直覺式的,只提供您要求的資料,而不是其他資料。

測試應用程式

現在,讓我們來看看應用程式是否正確執行。

  1. 選取 [偵錯] > [啟動但不偵錯],或按下 F5 鍵。 如果應用程式如預期般執行,您應該會看到初始索引檢視。

    開啟應用程式

  2. 輸入查詢字串,例如 "beach" (或想到的任何文字),然後按一下搜尋圖示以傳送要求。

    正在搜尋 *beach*

  3. 請嘗試輸入"five star"。 請注意,此查詢不會傳回任何結果。 更複雜的搜尋會將 "five star" 視為 "luxury" 的同義字,並傳回這些結果。 Azure 認知搜尋中提供同義字的支援,但是本教學課程系列並未涵蓋此功能。

  4. 請嘗試輸入 "hot" 作為搜尋文字。 它不會傳回具有 「hotel」 一字的專案。 我們的搜尋只會找出完整字詞,但是也會傳回幾個結果。

  5. 請嘗試其他文字;"pool"、"sunshine"、"view" 等等。 您會看到Azure 認知搜尋最簡單但仍然令人信服的層級。

測試邊緣條件和錯誤

請務必確認我們的錯誤處理功能是否如應般運作,即使專案正常運作也一樣。

  1. Index 方法中的 try { 呼叫後面,輸入 Throw new Exception() 這一行。 當您搜尋文字時,這個例外狀況會強制發生錯誤。

  2. 執行應用程式、輸入 "bar" 作為搜尋文字,然後按一下 [搜尋] 圖示。 例外狀況應該會導致錯誤檢視。

    強制發生錯誤

    重要

    在錯誤頁面中傳回內部錯誤號碼時,會被視為安全性風險。 如果您的應用程式適用于一般用途,請遵循發生錯誤時所傳回內容的安全性最佳做法。

  3. 當您滿足錯誤處理的運作方式時,請移除 擲回新的例外狀況 ()

重要心得

請考慮此專案的下列重點:

  • Azure 認知搜尋呼叫很簡潔,而且很容易解譯結果。
  • 非同步呼叫會將少量的複雜度新增至控制器,但是改善效能的最佳做法。
  • 此應用程式執行了簡單的文字搜尋,由 searchOptions中設定的內容所定義。 不過,此類別可以填入許多可對搜尋增加複雜度的成員。 有了更多工作,您就可以讓此應用程式更強大。

後續步驟

若要改善使用者體驗,請新增更多功能,尤其是分頁 (使用頁數或無限捲動),以及自動完成/建議。 您也可以考慮其他搜尋選項 (例如,在指定點的指定半徑內搜尋旅館的地理搜尋) 和搜尋結果排序。

在剩餘教學課程中,將會處理這些後續步驟。 讓我們從分頁著手。