Tutoriel : Créer votre première application de recherche dans Recherche cognitive Azure à l’aide du Kit de développement logiciel (SDK) .NET

Ce tutoriel vous montre comment créer une application web qui interroge un index de recherche et retourne les résultats à partir de ce dernier à l’aide de Recherche cognitive Azure et de Visual Studio.

Dans ce tutoriel, vous allez découvrir comment :

  • Configurer un environnement de développement
  • Modéliser des structures de données
  • Créer une page web pour collecter les entrées de requête et afficher les résultats
  • Définir une méthode de recherche
  • Test de l'application

Vous allez également découvrir à quel point il est simple d’effectuer un appel de recherche. Les instructions essentielles du code sont encapsulées dans les quelques lignes suivantes :

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

Un seul appel interroge l’index de recherche et renvoie les résultats.

Recherche de *pool*

Vue d’ensemble

Ce didacticiel utilise l’index hotels-sample-index que vous pouvez créer rapidement sur votre propre service de recherche en exécutant les étapes du Démarrage rapide Importer des données. L’index contient des données d’hôtel fictives, disponibles sous la forme d’une source de données intégrée dans chaque service de recherche.

La première leçon de ce didacticiel crée une page de recherche et une structure de requête de base que vous allez améliorer dans les leçons suivantes pour inclure une pagination, des facettes et une expérience d’autocomplétion.

Une version terminée du code se trouve dans le projet suivant :

Prérequis

Installer et exécuter le projet à partir de GitHub

Si vous souhaitez profiter sans tarder d’une application opérationnelle, suivez les étapes ci-dessous pour télécharger et exécuter le code terminé.

  1. Recherchez l’exemple sur GitHub : Créer votre première application.

  2. Dans le dossier racine, sélectionnez Code, puis Clone ou Download ZIP (Télécharger le fichier ZIP) pour créer votre copie locale privée du projet.

  3. À l’aide de Visual Studio, accédez à la solution pour la page de recherche de base (« 1-basic-search-page ») et ouvrez-la, puis sélectionnez Démarrer sans débogage (ou appuyez sur F5) pour générer et exécuter le programme.

  4. Il s’agit d’un index d’hôtels. Par conséquent, tapez quelques mots que vous pourriez utiliser pour rechercher des hôtels (par exemple, « wifi », « view » (vue), « bar », « parking »). Examinez les résultats.

    Recherche de *wifi*

Les composants essentiels aux recherches plus sophistiquées sont inclus dans cette application. Si vous êtes novice en matière de développement de recherche, vous pouvez recréer cette application étape par étape pour vous familiariser avec le workflow. Les sections suivantes décrivent les différentes opérations.

Configurer un environnement de développement

Pour créer ce projet à partir de zéro et ainsi mieux connaître les concepts du service Recherche cognitive Azure, commencez avec un projet Visual Studio.

  1. Dans Visual Studio, sélectionnez Nouveau>Projet, puis Application web ASP.NET Core (Model-View-Controller).

    Création d’un projet cloud

  2. Donnez un nom au projet, tel que « FirstSearchApp », puis définissez l’emplacement. Sélectionnez Suivant.

  3. Acceptez les valeurs par défaut pour l’infrastructure cible, le type d’authentification et HTTPS. Sélectionnez Créer.

  4. Installez la bibliothèque de client. Dans Outils>Gestionnaire de package NuGet>Gérer les packages NuGet pour la solution..., sélectionnez Parcourir, puis recherchez « azure.search.documents ». Installez Azure.Search.Documents (version 11 ou ultérieure), en acceptant les contrats de licence et les dépendances.

    Utilisation de NuGet pour ajouter des bibliothèques Azure

Au cours de cette étape, vous devez définir le point de terminaison et la clé d’accès pour la connexion au service de recherche qui fournit l’exemple d’index d’hôtels.

  1. Ouvrez le fichier appsettings.json et remplacez les lignes par défaut par l’URL du service de recherche (au format https://<service-name>.search.windows.net ) et une clé API d’administration ou de requête de votre service de recherche. Étant donné que vous n’avez pas besoin de créer ou de mettre à jour un index, vous pouvez utiliser la clé de requête pour ce didacticiel.

    {
        "SearchServiceUri": "<YOUR-SEARCH-SERVICE-URI>",
        "SearchServiceQueryApiKey": "<YOUR-SEARCH-SERVICE-API-KEY>"
    }
    
  2. Dans l’Explorateur de solutions, sélectionnez le fichier, puis dans Propriétés, définissez le paramètre Copier dans le répertoire de sortie sur Copier si plus récent.

    Copie des paramètres d’application dans la sortie

Modéliser des structures de données

Les modèles (classes C#) permettent de communiquer des données entre le client (vue), le serveur (contrôleur) et également le cloud Azure à l’aide de l’architecture MVC (modèle, vue, contrôleur). En règle générale, ces modèles reflètent la structure des données sollicitées.

Au cours de cette étape, vous allez modéliser les structures de données de l’index de recherche ainsi que la chaîne de recherche utilisée dans les communications entre la vue et le contrôleur. Dans l’index des hôtels, chaque hôtel a plusieurs chambres et une adresse en plusieurs parties. De même, la représentation complète d’un hôtel est une structure de données hiérarchique et imbriquée. Vous aurez besoin de trois classes pour créer chaque composant.

L’ensemble de classes Hotel, Address et Room est connu sous le terme de type complexe, une fonctionnalité importante de Recherche cognitive Azure. Les types complexes peuvent avoir de nombreux niveaux de classes et de sous-classes et permettent de représenter des structures de données beaucoup plus complexes qu’au moyen de types simples (classe contenant uniquement des membres primitifs).

  1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur Modèles>Ajouter>Nouvel élément.

  2. Sélectionnez Classe et nommez l’élément Hotel.cs. Remplacez tout le contenu de Hotel.cs par le code suivant. Notez les membres Address et Room de la classe ; ces champs étant eux-mêmes des classes, vous aurez également besoin de modèles pour eux.

    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. Répétez la même procédure de création de modèle pour la classe Address, en nommant le fichier Address.cs. Remplacez le contenu par ce qui suit.

    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. Là encore, suivez le même processus pour créer la classe Room, mais nommez le fichier 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. Le dernier modèle que vous allez créer dans ce tutoriel est une classe nommée SearchData et représente l’entrée de l’utilisateur (searchText) et la sortie de la recherche (resultList). Le type de la sortie, SearchResults<Hotel> , est critique ; en effet, il correspond exactement aux résultats de la recherche et vous devez passer cette référence à la vue. Remplacez le modèle par défaut par le code suivant.

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

Créer une page web

Les modèles de projet sont fournis avec un certain nombre de vues clientes situées dans le dossier Vues. Les vues exactes dépendent de la version de .NET Core que vous utilisez (dans cet exemple, la version 3.1 est utilisée). Dans le cadre de ce tutoriel, vous allez modifier Index.cshtml afin d’y inclure les éléments d’une page de recherche.

Supprimez le contenu d’Index.cshtml dans son intégralité et regénérez le fichier dans les étapes suivantes.

  1. Ce tutoriel utilise deux petites images dans la vue : un logo Azure et une icône en forme de loupe (azure-logo.png et search.png). Copiez les images depuis le projet GitHub dans le dossier wwwroot/images de votre projet.

  2. La première ligne d’Index.cshtml doit référencer le modèle utilisé pour communiquer des données entre le client (vue) et le serveur (contrôleur), qui correspond au modèle SearchData créé plus haut. Ajoutez cette ligne au fichier Index.cshtml.

    @model FirstAzureSearchApp.Models.SearchData
    
  3. Comme il est d’usage d’entrer un titre pour la vue, les lignes suivantes doivent être :

    @{
        ViewData["Title"] = "Home Page";
    }
    
  4. Après le titre, entrez une référence à une feuille de style HTML, que vous allez créer sous peu.

    <head>
        <link rel="stylesheet" href="~/css/hotels.css" />
    </head>
    
  5. Le corps de la vue gère deux cas d’usage. Tout d’abord, il doit fournir une page vide dès la première utilisation, avant qu’un texte de recherche ne soit entré. Deuxièmement, il doit gérer les résultats, en plus de la zone de texte de recherche, pour les requêtes répétées.

    Pour gérer les deux cas, vous devez vérifier si le modèle fourni à la vue est Null. Un modèle Null correspond au premier cas d’usage (l’exécution initiale de l’application). Ajoutez le code suivant au fichier Index.cshtml et lisez les commentaires.

    <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. Ajoutez la feuille de style. Dans Visual Studio, dans Fichier>Nouveau>Fichier, sélectionnez Feuille de style (avec Général sélectionné).

    Remplacez le code par défaut par ce qui suit. Nous n’allons pas nous attarder davantage sur ce fichier ; les styles correspondent à une syntaxe HTML standard.

    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. Enregistrez le fichier de feuille de style sous le nom hotels.css, dans le dossier wwwroot/css, avec le fichier site.css par défaut.

Notre vue est à présent complète. À ce stade, les modèles et les vues sont terminés. Seul reste à définir le contrôleur, qui relie tout cela.

Définir des méthodes

Au cours de cette étape, vous modifiez le contenu du contrôleur Home.

  1. Ouvrez le fichier HomeController.cs et remplacez les instructions using par ce qui suit.

    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;
    

Ajouter des méthodes Index

Dans une application MVC, la méthode index() est une méthode d’action par défaut pour tout contrôleur. Elle ouvre la page HTML d’index. La méthode par défaut, qui ne prend aucun paramètre, est utilisée dans ce tutoriel pour le cas d’usage de démarrage de l’application : afficher une page de recherche vide.

Dans cette section, nous étendons la méthode pour prendre en charge un deuxième cas d’usage : afficher la page quand un utilisateur a entré du texte de recherche. Pour prendre en charge ce cas, la méthode d’index est étendue afin de prendre un modèle comme paramètre.

  1. Ajoutez la méthode suivante, après la méthode Index() par défaut.

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

    Notez la déclaration async de la méthode et l’appel await à RunQueryAsync. Ces mots clés ont pour rôle d’effectuer des appels asynchrones et d’éviter, par là même, le blocage de threads sur le serveur.

    Le bloc catch utilise le modèle d’erreur par défaut qui a été créé.

Noter la gestion des erreurs et les autres méthodes et vues par défaut

Selon la version de .NET Core que vous utilisez, l’ensemble de vues par défaut qui est créé diffère légèrement. Pour .NET Core 3.1, les vues par défaut sont Index, Privacy et Error. Vous pouvez afficher ces pages par défaut lors de l’exécution de l’application et examiner la façon dont elles sont gérées dans le contrôleur.

Vous testerez la vue Error plus loin dans ce tutoriel.

Dans l’exemple GitHub, les vues non utilisées et leurs actions associées sont supprimées.

Ajouter la méthode RunQueryAsync

L’appel de Recherche cognitive Azure est encapsulé dans notre méthode RunQueryAsync.

  1. Tout d’abord, ajoutez des variables statiques pour configurer le service Azure et un appel pour les lancer.

        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. Maintenant, ajoutez la méthode RunQueryAsync proprement dite.

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

    Dans cette méthode, assurez-vous d’abord que notre configuration Azure est lancée, puis définissez certaines options de recherche. L’option Select spécifie les champs à retourner dans les résultats, correspondant aux noms des propriétés dans la classe hotel. Si vous omettez Select, tous les champs non masqués sont renvoyés, ce qui peut être inefficace si vous êtes uniquement intéressé par un sous-ensemble de tous les champs possibles.

    L’appel asynchrone de recherche formule la demande (modélisée en tant que searchText) et la réponse (modélisée en tant que searchResult). Lors du débogage de ce code, la classe SearchResult est un bon candidat à la définition d’un point d’arrêt si vous devez examiner le contenu de model.resultList. Vous devriez trouver cela intuitif, les résultats contenant pour l’essentiel les données demandées.

Test de l'application

À présent, nous allons vérifier si l’application s’exécute correctement.

  1. Sélectionnez Déboguer>Démarrer sans débogage ou appuyez sur F5. Si l’application s’exécute comme prévu, vous devez obtenir la vue d’index initiale.

    Ouverture de l’application

  2. Entrez une chaîne de requête telle que « beach » (ou n’importe quel texte qui vous vient à l’esprit), puis cliquez sur l’icône de recherche pour envoyer la demande.

    Recherche de *beach*

  3. Essayez d’entrer « five star ». Notez que cette requête ne retourne aucun résultat. Une recherche plus sophistiquée traiterait « five star » (cinq étoiles) comme un synonyme de « luxury » (luxe) et retournerait les résultats correspondants. La prise en charge des synonymes est disponible dans Recherche cognitive Azure, mais n’est pas traitée dans cette série de tutoriels.

  4. Essayez d’entrer « hot » comme texte de recherche. Vous n’obtenez aucune entrée contenant le mot « hotel ». Notre recherche porte uniquement sur les mots entiers, même si quelques résultats sont retournés.

  5. Essayez d’autres mots, tels que « pool », « sunshine » ou « view ». Vous verrez le service Recherche cognitive Azure fonctionner à son niveau le plus simple, mais néanmoins convaincant.

Tester des erreurs et conditions limites

Il est important de vérifier que nos fonctionnalités de gestion des erreurs fonctionnent comme prévu, même lorsque tout fonctionne parfaitement.

  1. Dans la méthode Index, après l’appel try { , entrez la ligne Throw new Exception() . Cette exception force une erreur quand vous effectuez une recherche sur du texte.

  2. Exécutez l’application, entrez « bar » comme texte de recherche, puis cliquez sur l’icône de recherche. L’exception doit aboutir à la vue d’erreur.

    Forcer une erreur

    Important

    Il est considéré comme risqué du point de vue de la sécurité de renvoyer des numéros d’erreur interne dans les pages d’erreur. Si votre application est destinée à un usage général, suivez les meilleures pratiques en matière de sécurité pour savoir ce qu’il faut renvoyer en cas d’erreur.

  3. Supprimez Throw new Exception() lorsque vous jugez que la gestion des erreurs fonctionne correctement.

Éléments importants à retenir

Retenez les points importants suivants de ce projet :

  • Un appel de Recherche cognitive Azure est concis et les résultats sont faciles à interpréter.
  • Les appels asynchrones ajoutent une petite dose de complexité au contrôleur, mais constituent une bonne pratique qui améliore les performances.
  • Cette application a effectué une recherche de texte simple, définie par ce qui est configuré dans searchOptions. Toutefois, cette classe peut être remplie avec de nombreux membres qui rendent une recherche plus sophistiquée. Avec un peu plus de travail, cette application peuvent devenir beaucoup plus puissante.

Étapes suivantes

Pour améliorer l’expérience utilisateur, ajoutez des fonctionnalités, notamment la pagination (au moyen de numéros de page ou d’un défilement infini) ainsi que l’autocomplétion et les suggestions. Vous pouvez également envisager d’autres options de recherche (par exemple, des recherches géographiques permettant de trouver des hôtels dans un rayon déterminé autour d’un point donné) et le tri des résultats de la recherche.

Ces prochaines étapes sont traitées dans les autres tutoriels. Commençons par la pagination.