次の方法で共有


チュートリアル: .NET SDK を使用して Azure Cognitive Search で最初の検索アプリを作成する

このチュートリアルでは、Azure Cognitive Search と Visual Studio を使用して検索インデックスからクエリを実行して結果を返す Web アプリを作成する方法について説明します。

このチュートリアルで学習する内容は次のとおりです。

  • 開発環境を設定する
  • モデル データ構造
  • クエリ入力を収集して結果を表示する 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();

1 回の呼び出しで検索インデックスが照会され、結果が返されます。

*pool* の検索

概要

このチュートリアルでは、hotels-sample-index を使用します。このインデックスは、 データのインポートのクイックスタートをステップ実行することで、独自の検索サービスですばやく作成できます。 インデックスには架空のホテル データが含まれており、すべての検索サービスで組み込みのデータ ソースとして使用できます。

このチュートリアルの最初のレッスンでは、基本的なクエリ構造と検索ページを作成します。以降のレッスンでは、ページング、ファセット、先行入力エクスペリエンスを含むように拡張します。

完成したバージョンのコードは、次のプロジェクトにあります。

[前提条件]

GitHub からプロジェクトをインストールして実行する

動作しているアプリに進む場合は、次の手順に従って、完成したコードをダウンロードして実行します。

  1. GitHub でサンプルを見つけます。 最初のアプリを作成します

  2. ルート フォルダーで [コード] を選択し、[複製] または [ZIP のダウンロード] を選択して、プロジェクトのプライベート ローカル コピーを作成します。

  3. Visual Studio を使用して、基本的な検索ページ ("1-basic-search-page") のソリューションに移動して開き、[ デバッグなしで開始] を選択して (または F5 キーを押して) プログラムをビルドして実行します。

  4. これはホテルインデックスなので、ホテルの検索に使用する可能性のある単語を入力します (例: "wifi"、"view"、"bar"、"parking")。 結果を確認します。

    *wifi* の検索

この 1 つのアプリには、より高度な検索に不可欠なコンポーネントが含まれています。 検索開発を初めて使用する場合は、このアプリを段階的に再作成してワークフローを学習できます。 次のセクションでは、その方法について説明します。

開発環境を設定する

このプロジェクトをゼロから作成し、Azure Cognitive Search の概念を強化するには、Visual Studio プロジェクトから始めます。

  1. Visual Studio で[新規>プロジェクト] を選択し、ASP.NET Core Web アプリ (モデル-View-Controller) を選択します。

    クラウド プロジェクトの作成

  2. プロジェクトに "FirstSearchApp" などの名前を付け、場所を設定します。 [ 次へ] を選択します。

  3. ターゲット フレームワーク、認証の種類、および HTTPS の既定値をそのまま使用します。 作成を選択します。

  4. クライアント ライブラリをインストールします。 ツール>NuGet パッケージ マネージャー>Manage NuGet Packages for Solution...で、[参照] を選択し、"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 クラウド間でデータを通信するために使用されます。 通常、これらのモデルには、アクセスされるデータの構造が反映されます。

この手順では、検索インデックスのデータ構造と、ビュー/コントローラー通信で使用される検索文字列をモデル化します。 ホテルインデックスでは、各ホテルには多くの部屋があり、各ホテルには複数の部分がある住所があります。 全体として、ホテルの完全な表現は、階層的で入れ子になったデータ構造です。 各コンポーネントを作成するには、3 つのクラスが必要です。

HotelAddressRoom クラスのセットは、Azure Cognitive Search の重要な機能である複合型と呼ばれます。 複合型は、クラスとサブクラスの深い多くのレベルにすることができ、 単純型 (プリミティブ メンバーのみを含むクラス) を使用するよりもはるかに複雑なデータ構造を表すことができます。

  1. ソリューション エクスプローラーで、 モデル>追加>新しい項目を右クリックします。

  2. [ クラス] を選択し、項目にHotel.cs名前を付けます。 Hotel.csのすべての内容を次のコードに置き換えます。 クラスの Address メンバーと Room メンバーに注目してください。これらのフィールドはクラス自体であるため、モデルも必要になります。

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

Web ページの作成

プロジェクト テンプレートには、 Views フォルダーに多数のクライアント ビューがあります。 正確なビューは、使用している Core .NET のバージョンによって異なります (このサンプルでは 3.1 が使用されています)。 このチュートリアルでは、検索ページの要素を含むように Index.cshtml を変更します。

Index.cshtml の内容全体を削除し、次の手順でファイルを再構築します。

  1. このチュートリアルでは、ビューで 2 つの小さな画像を使用します。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. ビューの本文は、2 つのユース ケースを処理します。 最初に、検索テキストを入力する前に、最初の使用時に空のページを提供する必要があります。 次に、クエリを繰り返す場合は、検索テキスト ボックスに加えて結果を処理する必要があります。

    どちらの場合も処理するには、ビューに提供されるモデルが 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 の File>New>File で、[ スタイル シート ] を選択します ( [全般] が強調表示されています)。

    既定のコードを次のコードに置き換えます。 このファイルの詳細については説明しません。スタイルは標準の 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. 既定のsite.css ファイルと共に、スタイルシート ファイルをhotels.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 ページが開きます。 パラメーターを受け取りませんが、このチュートリアルでは、アプリケーションのスタートアップ ユース ケースである空の検索ページをレンダリングする既定のメソッドを使用します。

このセクションでは、2 番目のユース ケース (ユーザーが検索テキストを入力したときにページをレンダリングする) をサポートするようにメソッドを拡張します。 このケースをサポートするために、index メソッドはモデルをパラメーターとして受け取るように拡張されます。

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

    メソッドの非同期宣言と、RunQueryAsyncawait 呼び出しに注目してください。 これらのキーワードは非同期呼び出しの処理を行い、サーバー上のスレッドをブロックしないようにします。

    catch ブロックでは、作成された既定のエラー モデルが使用されます。

エラー処理とその他の既定のビューとメソッドに注意してください

使用している .NET Core のバージョンに応じて、少し異なる既定のビューのセットが作成されます。 .NET Core 3.1 の場合、既定のビューはインデックス、プライバシー、エラーです。 これらの既定のページは、アプリの実行時に表示し、コントローラーで処理される方法を確認できます。

このチュートリアルの後半でエラー ビューをテストします。

GitHub サンプルでは、未使用のビューとそれに関連付けられているアクションが削除されます。

RunQueryAsync メソッドを追加する

Azure Cognitive Search 呼び出しは、 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" などのクエリ文字列 (または気になるテキスト) を入力し、検索アイコンをクリックして要求を送信します。

    *beach* の検索

  3. 「5 つ星」と入力してみてください。 このクエリでは結果が返されないことに注意してください。 より高度な検索では、"five star" を "luxury" のシノニムとして扱い、それらの結果を返します。 シノニムのサポートは Azure Cognitive Search で利用できますが、このチュートリアル シリーズでは取り上げません。

  4. 検索テキストとして「ホット」と入力してみてください。 "hotel" という単語が含まれるエントリは返されません。 検索では単語全体のみが検索されますが、いくつかの結果が返されます。

  5. "pool"、"sunshine"、"view"など、他の単語を試してみてください。 Azure Cognitive Search は最も単純ですが、まだ説得力のあるレベルで動作していることがわかります。

エッジの条件とエラーをテストする

すべてが完璧に動作している場合でも、エラー処理機能が適切に動作することを確認することが重要です。

  1. Index メソッドで、try { 呼び出しの後に、新しい Exception() をスローする行を入力します。 この例外は、テキストを検索するときにエラーを強制します。

  2. アプリを実行し、検索テキストとして「バー」と入力し、検索アイコンをクリックします。 例外が発生すると、エラー ビューが表示されます。

    強制的にエラーを発生させる

    重要

    エラー ページで内部エラー番号を返すセキュリティ 上のリスクと見なされます。 アプリが一般的な使用を目的としている場合は、エラーが発生したときに返される内容のセキュリティのベスト プラクティスに従ってください。

  3. エラー処理が正常に動作したら、throw new Exception() を削除してください。

学んだこと

このプロジェクトの次の点について考えてみましょう。

  • Azure Cognitive Search の呼び出しは簡潔で、結果を簡単に解釈できます。
  • 非同期呼び出しはコントローラーに少量の複雑さを追加しますが、パフォーマンスを向上させるベスト プラクティスです。
  • このアプリは、 searchOptions で設定された内容によって定義された単純なテキスト検索を実行しました。 ただし、この 1 つのクラスには、検索に高度な機能を追加する多くのメンバーを設定できます。 もう少し作業すれば、このアプリをかなり強力にすることができます。

次のステップ

ユーザー エクスペリエンスを向上させるには、機能を追加します。特にページング (ページ番号を使用するか、無限スクロールを使用)、オートコンプリート/候補を追加します。 また、他の検索オプション (たとえば、特定のポイントの指定された半径内のホテルでの地理的な検索) と検索結果の順序を検討することもできます。

以降の手順については、残りのチュートリアルで説明します。 それでは、ページングを始めましょう。