Creare un'app di ricerca in ASP.NET Core
In questa esercitazione creare un'app di base ASP.NET Core (Model-View-Controller) che viene eseguita in localhost e si connette a hotels-sample-index nel servizio di ricerca. In questa esercitazione, scopri come:
- Creare una pagina di ricerca di base
- Filtrare i risultati
- Ordinare i risultati
Questa esercitazione pone l’attenzione sulle operazioni lato server chiamate tramite le API di ricerca. Sebbene sia frequente ordinare e filtrare nello script lato client, conoscere il modo con cui richiamare queste operazioni sul server offre più opzioni di progettazione dell'esperienza di ricerca.
Il codice di esempio per questa esercitazione è disponibile nel repository azure-search-dotnet-samples su GitHub.
Prerequisiti
- Visual Studio
- Pacchetto NuGet Azure.Search.Documents
- Azure AI Search, qualsiasi livello, ma deve avere accesso alla rete pubblica.
- Indice degli esempi di hotel
Eseguire l’importazione guidata dati per creare l’index-sample-hotels nel servizio di ricerca. In alternativa, modificare il nome dell'indice nel file HomeController.cs
.
Creare il progetto
Avviare Visual Studio e selezionare Crea un nuovo progetto.
Selezionare ASP.NET Core Web App (Model-View-Controller), quindi selezionare Avanti.
Specificare un nome di progetto e quindi selezionare Avanti.
Nella pagina successiva selezionare .NET 6.0 o .NET 7.0 o .NET 8.0.
Verificare che Non usare istruzioni di livello superiore sia deselezionata.
Seleziona Crea.
Aggiungere i pacchetti NuGet di
In Strumenti selezionare Gestione pacchetti NuGet>Gestisci pacchetti NuGet per la soluzione.
Cercare
Azure.Search.Documents
e installare la versione stabile più recente.Cercare e installare il pacchetto
Microsoft.Spatial
. L'indice di esempio include un tipo di dati GeographyPoint. L'installazione di questo pacchetto evita errori di fase di esecuzione. In alternativa, se non si vuole installare il pacchetto, rimuovere il campo "Posizione" dalla classe Hotel. Per l’esercitazione questo campo non viene usato.
Aggiungere informazioni sul servizio
Per la connessione, l'app presenta una chiave API di query all'URL di ricerca completo. Entrambi sono specificati nel file appsettings.json
.
Modificare appsettings.json
per specificare il servizio di ricerca e la chiave API di query.
{
"SearchServiceUri": "<YOUR-SEARCH-SERVICE-URL>",
"SearchServiceQueryApiKey": "<YOUR-SEARCH-SERVICE-QUERY-API-KEY>"
}
È possibile ottenere l'URL del servizio e la chiave API dal portale di Azure. Poiché questo codice esegue query su un indice e non ne crea uno, è possibile usare una chiave di query anziché una chiave di amministrazione.
Assicurarsi di specificare il servizio di ricerca che ha hotels-sample-index.
Aggiungi modelli
In questo passaggio, creare modelli che rappresentano lo schema dell'indice hotels-sample-index.
In Esplora soluzioni selezionare con il pulsante destro Modelli e aggiungere una nuova classe denominata "Hotel" per il codice seguente:
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; } } }
Aggiungere una classe denominata "Address" e sostituirla con il codice seguente:
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; } } }
Aggiungere una classe denominata "Rooms" e sostituirla con il codice seguente:
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; } } }
Aggiungere una classe denominata "SearchData" e sostituirla con il codice seguente:
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; } }
Modificare il controller
Per questa esercitazione, modificare l'impostazione predefinita HomeController
in modo che contenga i metodi eseguiti nel servizio di ricerca.
In Esplora soluzioni in Modelli, aprire
HomeController
.Sostituire il valore predefinito con il contenuto seguente:
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(); } } }
Modificare la visualizzazione
In Esplora soluzioni in Visualizzazioni>Home aprire
index.cshtml
.Sostituire il valore predefinito con il contenuto seguente:
@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>
Eseguire l'esempio
Premere F5 per compilare ed eseguire il progetto. L'app viene eseguita nell'host locale e si apre nel browser predefinito.
Selezionare Cerca per restituire tutti i risultati.
Questo codice usa la configurazione di ricerca predefinita, supportando la sintassi semplice e
searchMode=Any
. È possibile immettere le parole chiave, aumentare con operatori booleani o eseguire una ricerca di prefisso (pool*
).
Nelle diverse sezioni successive modificare il metodo RunQueryAsync in HomeController
per aggiungere i filtri e l’ordinamento.
Filtrare i risultati
Gli attributi dei campi di indice determinano i campi che sono ricercabili, filtrabili, ordinabili, facetable e recuperabili. Nei campi hotels-sample-index i campi filtrabili includono Category, Address/City e Address/StateProvince. In questo esempio viene aggiunta un'espressione $Filter su Categoria.
Prima viene sempre eseguito il filtro, seguito da una query, presupponendo che ne sia specificata una.
Aprire
HomeController
e trovare il metodo RunQueryAsync. Aggiungi filtro avar 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); }
Eseguire l'applicazione.
Selezionare Cerca per eseguire una query vuota. Il filtro restituisce 18 documenti anziché i 50 di origine.
Per altre informazioni sulle espressioni di filtro, vedere Filtri in Azure AI Search e sintassi $filter OData in Azure AI Search.
Ordinare i risultati
Nei campi hotels-sample-index i campi ordinabili includono Classificazione e LastRenovated. In questo esempio, nel campo Classificazione viene aggiunta un'espressione $OrderBy.
Aprire e sostituire
HomeController
e sostituire il metodo RunQueryAsync con la versione seguente: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); }
Eseguire l'applicazione. I risultati vengono ordinati per Classificazione, in ordine decrescente.
Per altre informazioni sull'ordinamento, vedere Sintassi $orderby OData in Azure AI Search.
Passaggi successivi
In questa esercitazione è stato creato un progetto ASP.NET Core (MVC) connesso a un servizio di ricerca, e sono state chiamate API di ricerca per il filtro e l'ordinamento sul lato server.
Se si vuole esplorare il codice lato client che risponde alle azioni dell'utente, è consigliabile aggiungere alla soluzione un modello Reagire: