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, obtendrá información sobre cómo:

  • 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

1 El servicio de búsqueda puede ser de cualquier nivel, pero debe tener acceso a la red pública para este tutorial.

2 Para completar este tutorial, debe crear el índice hotels-sample-index en el servicio de búsqueda. Asegúrese de que el nombre del índice de búsqueda sea hotels-sample-index o cambie el nombre del índice en el archivo HomeController.cs.

Creación del proyecto

  1. Inicie Visual Studio y seleccione Crear un proyecto.

  2. Seleccione ASP.NET Core Web App (Model-View-Controller) y, a continuación, seleccione Siguiente.

  3. Proporcione un nombre de proyecto y, a continuación, seleccione Siguiente.

  4. En la página siguiente, seleccione .NET 6.0 o .NET 7.0.

  5. Comprobar que la opción No usar instrucciones de nivel superior está desactivada.

  6. Seleccione Crear.

Adición de paquetes NuGet

  1. En Herramientas, seleccione Administrador de paquetes NuGet>Administrar paquetes NuGet para la solución.

  2. Busque Azure.Search.Documents e instale la versión estable más reciente.

  3. 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. 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>"
}

Puedes obtener la URL del servicio y la clave API del 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.

  1. 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; }
         }
     }
    
  2. 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; }
         }
     }
    
  3. 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; }
         }
     }
    
  4. 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.

  1. En el Explorador de soluciones, en Modelos, abra HomeController.

  2. 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

  1. En el Explorador de soluciones, en Vistas>Inicio, abra index.cshtml.

  2. Reemplace el valor predeterminado con el contenido siguiente:

    @model HotelDemoApp.Models.SearchData;
    
    @{
        ViewData["Title"] = "Index";
    }
    
    <div>
        <img src="~/images/azure-logo.png" width="80" />
        <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

  1. Presione F5 para compilar y ejecutar el proyecto. La aplicación se ejecuta en el host local y se abre en el explorador predeterminado.

  2. Seleccione Buscar para devolver todos los resultados.

  3. 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.

  1. Abra HomeController y busque el método RunQueryAsync. Agregar un Filtro a 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);
     }
    
  2. Ejecute la aplicación.

  3. Seleccione Buscar para ejecutar una consulta vacía. Los criterios de filtro devuelven 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".

  1. 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);
     }
    
  2. 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: