共用方式為


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

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

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

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

您也將了解搜尋呼叫有多簡單。 程式碼中的關鍵語句包含在下列幾行中:

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 雲端之間傳達數據。 一般而言,這些模型會反映所存取數據的結構。

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

旅館位址會議室類別集合稱為複雜類型,這是 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 資料夾中的客戶端檢視。 確切檢視取決於您所使用的Core.NET版本(在此範例中使用3.1)。 在本教學課程中,您將修改 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. 將 stylesheet 檔案儲存為 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 宣告,以及 RunQueryAsyncawait 呼叫。 這些關鍵詞會負責進行異步呼叫,因此避免在伺服器上封鎖線程。

    catch 區塊會使用已建立的默認錯誤模型。

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

根據您使用的 .NET Core 版本,會建立一組稍微不同的默認檢視。 針對 .NET Core 3.1,默認檢視為 Index、Privacy 和 Error。 您可以在執行應用程式時檢視這些預設頁面,並檢查它們在控制器中的處理方式。

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

在 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 設定已起始,然後設定一些搜尋選項。 Select 選項會指定要在結果中傳回哪些欄位,因此符合旅館類別中的屬性名稱。 如果您省略 Select,則會傳回所有未隱藏的欄位,如果您只對所有可能欄位的子集感興趣,這可能會沒有效率。

    非同步呼叫的搜尋會制定要求(模擬為 searchText)及回應(模擬為 searchResult)。 如果您正在偵錯這段程式碼,並且需要檢查 model.resultList 的內容,SearchResult 類別是不錯的設定斷點的選擇。 您應該發現它是直覺式的,只提供您要求的數據,而不是太多。

測試應用程式

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

  1. > 選取 [不偵錯直接執行] 或按 F5。 如果應用程式如預期般執行,您應該會取得初始索引檢視。

    開啟應用程式

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

    搜尋 *海灘*

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

  4. 嘗試輸入「熱」作為搜索字詞。 它不會傳回包含「飯店」一字的條目。 我們的搜尋只會尋找整個單字,不過會傳回一些結果。

  5. 請嘗試其他字:「pool」、 「sunshine」、 「view」以及其他任何詞。 您會看到 Azure 認知搜尋在最簡單的水平上運作,但仍令人信服。

測試邊緣條件和錯誤

確認我們的錯誤處理功能能夠正常運作是很重要的,即使在一切正常的情況下也一樣。

  1. Index 方法中,try { 呼叫之後,輸入行 拋出新的 Exception()。 當您搜尋文字時,此例外狀況會導致錯誤。

  2. 執行應用程式,輸入 「bar」 作為搜尋文字,然後按下搜尋圖示。 例外狀況應該會導致錯誤畫面。

    強制錯誤

    這很重要

    將內部錯誤號碼顯示在錯誤頁面上被認為是一個安全風險。 如果您的應用程式適用於一般用途,請遵循發生錯誤時要傳回之內容的安全性最佳做法。

  3. 當您確認錯誤處理如預期運作時,請移除拋出新的例外()

外賣

請考慮這個專案的下列要點:

  • Azure 認知搜尋呼叫很簡潔,而且很容易解譯結果。
  • 異步呼叫會使控制器稍微複雜化,但這是改善效能的最佳做法。
  • 此應用程式會執行直接的文字搜尋,由 searchOptions 中設定的內容所定義。 不過,這個類別可以擁有許多成員,從而增加搜尋的複雜性。 稍微再多花點心思,您就可以讓這個應用程式變得更強大。

後續步驟

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

後續步驟會在其餘教學課程中加以解決。 讓我們從分頁開始。