Creación de una aplicación de búsqueda en ASP.NET Core
En este tutorial, cree una aplicación básica de ASP.NET Core (Model-View-Controller) que se ejecute en localhost y se conecte a hotels-sample-index en el servicio de búsqueda. En este tutorial, aprenderá a:
- Creación de una página de búsqueda básica
- Filtrar resultados
- Ordenar resultados
Este tutorial se centra en las operaciones del lado servidor llamadas a través de las API de búsqueda. Aunque es habitual ordenar y filtrar en el script del lado cliente, saber cómo invocar estas operaciones en el servidor proporciona más opciones al diseñar la experiencia de búsqueda.
El código de ejemplo de este tutorial se puede encontrar en el repositorio azure-search-dotnet-samples en GitHub.
Requisitos previos
- Visual Studio
- Paquete NuGet Azure.Search.Documents
- Búsqueda de Azure AI, cualquier nivel, pero debe tener acceso a una red pública.
- Índice de muestras de hotel
Siga los pasos del asistente para importar datos para crear el índice hotels-sample-index en el servicio de búsqueda. O bien, cambie el nombre del índice en el archivo HomeController.cs
.
Creación del proyecto
Inicie Visual Studio y seleccione Crear un proyecto.
Seleccione ASP.NET Core Web App (Model-View-Controller) y, a continuación, seleccione Siguiente.
Proporcione un nombre de proyecto y, a continuación, seleccione Siguiente.
En la página siguiente, seleccione .NET 6.0, .NET 7.0 o .NET 8.0.
Comprobar que la opción No usar instrucciones de nivel superior está desactivada.
Seleccione Crear.
Adición de paquetes NuGet
En Herramientas, seleccione Administrador de paquetes NuGet>Administrar paquetes NuGet para la solución.
Busque
Azure.Search.Documents
e instale la versión estable más reciente.Busque e instale el paquete
Microsoft.Spatial
. El índice de ejemplo incluye un tipo de datos GeographyPoint. La instalación de este paquete evita errores en tiempo de ejecución. Como alternativa, quite el campo "Ubicación" de la clase Hoteles si no desea instalar el paquete. Ese campo no se usa en este tutorial.
Información sobre el servicio
Para la conexión, la aplicación presenta una clave de API de consulta a la dirección URL de búsqueda completa. Ambas se especifican en el archivo appsettings.json
.
Modifique appsettings.json
para especificar el servicio de búsqueda y la clave de API de consulta.
{
"SearchServiceUri": "<YOUR-SEARCH-SERVICE-URL>",
"SearchServiceQueryApiKey": "<YOUR-SEARCH-SERVICE-QUERY-API-KEY>"
}
Puede obtener la dirección URL del servicio y la clave de API de Azure Portal. Dado que este código está consultando un índice y no creando uno, puedes usar una clave de consulta en lugar de una clave de administrador.
Asegúrate de especificar el servicio de búsqueda que tiene hotels-sample-index.
Agregar modelos
En este paso, crea modelos que representen el esquema de hotels-sample-index.
En el Explorador de soluciones, seleccione Modelos y agregue una nueva clase denominada "Hotel" para el código siguiente:
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; } } }
Agregue una clase denominada "Dirección" y reemplácela por el código siguiente:
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; } } }
Agregue una clase denominada "Habitaciones" y reemplácela por el código siguiente:
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; } } }
Agregue una clase denominada "Datos de búsqueda" y reemplácela por el código siguiente:
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; } }
Modificación del controlador
Para este tutorial, modifique el valor predeterminado HomeController
para que contenga métodos que se ejecutan en el servicio de búsqueda.
En el Explorador de soluciones, en Modelos, abra
HomeController
.Reemplace el valor predeterminado con el contenido siguiente:
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(); } } }
Modificación de la vista
En el Explorador de soluciones, en Vistas>Inicio, abra
index.cshtml
.Reemplace el valor predeterminado con el contenido siguiente:
@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>
Ejecución del ejemplo
Presione F5 para compilar y ejecutar el proyecto. La aplicación se ejecuta en el host local y se abre en el explorador predeterminado.
Seleccione Buscar para devolver todos los resultados.
Este código usa la configuración de búsqueda predeterminada, que admite la sintaxis simple y
searchMode=Any
. Puede escribir palabras clave, aumentar con operadores booleanos o ejecutar una búsqueda de prefijos (pool*
).
En las secciones siguientes, modifique el método RunQueryAsync en HomeController
para agregar filtros y un criterio de ordenación.
Filtrar resultados
Los atributos de campo de índice determinan qué campos se pueden buscar, filtrar, ordenar, clasificar y recuperar. En el índice hotels-sample-index, los campos filtrables incluyen Categoría, Dirección/ciudad y Dirección/EstadoProvincia. En este ejemplo se agrega una expresión $Filter en Categoría.
Un filtro siempre se ejecuta primero, seguido de una consulta si es que se especificó una.
Abra
HomeController
y busque el método RunQueryAsync. Agregar un 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); }
Ejecute la aplicación.
Seleccione Buscar para ejecutar una consulta vacía. El filtro devuelve 18 documentos en lugar de los 50 originales.
Para obtener más información sobre las expresiones de filtro, consulta Filtros en Azure AI Search y Sintaxis de $filter de OData en Azure AI Search.
Ordenar resultados
En hotels-sample-index, los campos que se pueden ordenar incluyen Clasificación y ÚltimaRenovacion. En este ejemplo se agrega una expresión $OrderBy al campo Clasificación.
Abra
HomeController
y reemplace el método RunQueryAsync por la siguiente versión: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); }
Ejecute la aplicación. Los resultados se ordenan por Clasificación en orden descendente.
Para obtener más información sobre los criterios de ordenación, consulta Sintaxis de $orderby de OData en Azure AI Search.
Pasos siguientes
En este tutorial, ha creado un proyecto de ASP.NET Core (MVC) que se ha conectado a un servicio de búsqueda y ha llamado a las API de búsqueda para el filtrado y la ordenación del lado servidor.
Si desea explorar código del lado cliente que responda a las acciones del usuario, considere la posibilidad de agregar una plantilla de React a la solución: