Créer une application de recherche dans ASP.NET Core
Dans ce tutoriel, vous allez créer une application ASP.NET Core de base (modèle-vue-contrôleur) qui s’exécute dans localhost et se connecte à hotels-sample-index sur votre service de recherche. Dans ce tutoriel, vous allez apprendre à effectuer les actions suivantes :
- Créer une page de recherche de base
- Filtrer les résultats
- Trier les résultats
Ce tutoriel insiste sur les opérations appelées côté serveur au travers des API de recherche. Bien qu’il soit courant de trier et filtrer côté client dans un script, savoir comment appeler ces opérations sur le serveur vous offre plus d’options lors de la conception de l’expérience de recherche.
Vous trouverez un exemple de code pour ce tutoriel dans le référentiel azure-search-dotnet-samples disponible sur GitHub.
Prérequis
- Visual Studio
- Package NuGet Azure.Search.Documents
- Recherche Azure AI, n’importe quel niveau, mais elle doit disposer d’un accès réseau public.
- Index des exemples d’hôtels
Parcourez l’Assistant Importation de données pour créer l’index hotels-sample-index sur votre service de recherche. Ou modifiez le nom de l’index dans le fichier HomeController.cs
.
Créer le projet
Démarrez Visual Studio et sélectionnez Créer un projet.
Sélectionnez Application web ASP.NET Core (modèle-vue-contrôleur), puis Suivant.
Attribuez un nom au projet, puis sélectionnez Suivant.
Sur la page suivante, choisissez .NET 6.0, .NET 7.0 ou .NET 8.0.
Vérifiez que l’option ne pas utiliser les instructions de niveau supérieur soit décochée.
Sélectionnez Create (Créer).
Ajouter des packages NuGet
Dans Outils, sélectionnez Gestionnaire de package NuGet>Gérer les packages NuGet pour la solution.
Parcourez
Azure.Search.Documents
et installez la dernière version stable.Parcourez vers et installez le package
Microsoft.Spatial
. L’exemple d’index compte un type de données GeographyPoint. L’installation du package évite les erreurs de temps d’exécution. Vous pouvez aussi supprimer le champ « Emplacement » de la classe Hôtels si vous ne souhaitez pas installer le package. Ce champ n’est pas utilisé dans ce didacticiel.
Ajouter une information sur le service
Pour la connexion, l’application présente une requête de clé API à votre URL complète de recherche. Les deux sont spécifiés dans le fichier appsettings.json
.
Modifiez appsettings.json
pour spécifier votre service de recherche et votre requête de clé API.
{
"SearchServiceUri": "<YOUR-SEARCH-SERVICE-URL>",
"SearchServiceQueryApiKey": "<YOUR-SEARCH-SERVICE-QUERY-API-KEY>"
}
Vous pouvez obtenir l’URL du service et la clé API à partir du Portail Azure. Ce code ne faisant qu’interroger un index, sans en créer un, vous pouvez utiliser une clé de requête plutôt qu’une clé d’administration.
Assurez-vous de spécifier le service de recherche qui contient hotels-sample-index.
Ajouter des modèles
À cette étape, créez des modèles représentant le schéma de l’index hotels-sample-index.
Dans l’Explorateur de solutions, effectuez un clic droit sur Modèles et ajoutez une nouvelle classe nommée « Hotel » pour le code suivant :
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; } } }
Ajoutez une classe nommée « Address » et remplacez-la par le code suivant :
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; } } }
Ajoutez une classe nommée « Rooms » et remplacez-la par le code suivant :
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; } } }
Ajoutez une classe nommée « SearchData » et remplacez-la par le code suivant :
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; } }
Modifier le contrôleur
Pour ce didacticiel, vous allez modifier la valeur par défaut HomeController
pour contenir les méthodes s’exécutant sur votre service de recherche.
Sous Modèles, ouvrez
HomeController
dans l’Explorateur de solutions.Remplacez la valeur par défaut par le contenu suivant :
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(); } } }
Modifier l’affichage
Sous Affichages>Accueil, ouvrez
index.cshtml
dans l’Explorateur de solutions.Remplacez la valeur par défaut par le contenu suivant :
@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>
Exécution de l'exemple
Appuyez sur F5 pour compiler et exécuter le projet. L’application s’exécute dans localhost et s’ouvre dans votre navigateur par défaut.
Sélectionnez Rechercher pour renvoyer tous les résultats.
Ce code utilise la configuration par défaut de recherche, prenant en charge la syntaxe simple et
searchMode=Any
. Vous pouvez renseigner des mots clés, compléter par des opérateurs booléens ou exécuter une recherche de préfixe (pool*
).
Dans les sections suivantes, modifiez la méthode RunQueryAsync dans HomeController
pour ajouter des filtres et trier.
Filtrer les résultats
Les attributs du champ d’index déterminent les champs pouvant être recherchés, filtrables, triables, à choix multiples et récupérables. Dans hotels-sample-index, les champs filtrables comprennent Catégorie, Adresse/Ville et Adresse/DépartementProvince. Cet exemple ajoute une expression $Filter dans Catégorie.
Un filtre s’exécute toujours en premier, suivi de la requête ; en supposant qu’il en soit spécifié un(e).
Ouvrez le
HomeController
et recherchez la méthode RunQueryAsync. Ajoutez un filtre à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); }
Exécutez l’application.
Sélectionnez Rechercher pour exécuter une requête vide. Le filtre renvoie 18 documents au lieu des 50 d’origine.
Pour plus d’informations sur les expressions de filtre, consultez Filtres dans la Recherche Azure AI et syntaxe $filter OData dans la Recherche Azure AI.
Trier les résultats
Dans hotels-sample-index, les champs triables comprennent Classement et LastRenovated. Cet exemple ajoute une expression $OrderBy au champ Classement.
Ouvrez
HomeController
et remplacez la méthode RunQueryAsync par la version suivante :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); }
Exécutez l’application. Les résultats sont présentés dans l’ordre décroissant du Classement.
Pour plus d’informations sur le tri, consultez Syntaxe $orderby OData dans la Recherche Azure AI.
Étapes suivantes
Dans ce tutoriel, vous avez créé un projet ASP.NET Core (MVC) qui s’est connecté à un service de recherche et a appelé des API de recherche pour le filtrage et le tri côté serveur.
Si vous souhaitez explorer le code côté client qui répond aux actions de l’utilisateur, envisagez d’ajouter un modèle React à votre solution :