Criar um aplicativo de pesquisa no ASP.NET Core

Neste tutorial, crie um aplicativo básico do ASP.NET Core (Model-View-Controller) que seja executado no localhost e se conecte ao hotels-sample-index no seu serviço de pesquisa. Neste tutorial, você aprenderá a:

  • Crie uma página de pesquisa básica
  • Resultados do filtro
  • Classificar resultados

Este tutorial coloca o foco nas operações do lado do servidor chamadas através das APIs de Pesquisa. Embora seja comum classificar e filtrar o script do lado do cliente, saber como invocar essas operações no servidor dá a você mais opções ao projetar a experiência de pesquisa.

Código de exemplo deste tutorial pode ser encontrado no repositório azure-search-dotnet-samples no GitHub.

Pré-requisitos

Percorra o assistente de importação de dados, para criar o índice de exemplo de hotéis em seu serviço de pesquisa. Ou altere o nome do índice no arquivo HomeController.cs.

Criar o projeto

  1. Inicie o Visual Studio e selecione Criar um projeto.

  2. Selecione o Aplicativo Web ASP.NET Core (Model-View-Controller) e, em seguida, selecione Avançar.

  3. Forneça um nome de projeto e selecione Avançar.

  4. Na próxima página, selecione .NET 6.0 ou .NET 7.0 ou .NET 8.0.

  5. Verifique se Não usar instruções de nível superior está desmarcado.

  6. Selecione Criar.

Adicionar pacotes NuGet

  1. Em Ferramentas, selecione Gerenciador de Pacotes NuGet>Gerenciar Pacotes NuGet para a solução.

  2. Procure por Azure.Search.Documents e instale a última versão estável.

  3. Procure e instale o pacote Microsoft.Spatial. O índice de amostra inclui um tipo de dados GeographyPoint. A instalação deste pacote evita erros de tempo de execução. Como alternativa, remova o campo "Localização" da classe Hotéis se não quiser instalar o pacote. Esse campo não é usado neste tutorial.

Adicionar as informações do serviço

Para a conexão, o aplicativo apresenta uma chave de API de consulta à sua URL de pesquisa totalmente qualificada. Ambos estão especificados no arquivo appsettings.json.

Modifique appsettings.json para especificar seu serviço de pesquisa e a chave da API de consulta.

{
    "SearchServiceUri": "<YOUR-SEARCH-SERVICE-URL>",
    "SearchServiceQueryApiKey": "<YOUR-SEARCH-SERVICE-QUERY-API-KEY>"
}

Você pode obter o URL do serviço e a chave de API no portal. Como esse código está consultando um índice e não criando um, você pode usar uma chave de consulta em vez de uma chave de administração.

Especifique o serviço de pesquisa que tem o hotels-sample-index.

Adicionar modelos

Nesta etapa, crie modelos que representem o esquema hotels-sample-index.

  1. No Solution Explorer, selecione com o botão direito do mouse Modelos e adicione uma nova classe chamada “Hotel” no código a seguir:

     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. Adicione uma classe chamada "Endereço" e substitua-a pelo código a seguir:

     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. Adicione uma classe chamada “Salas” e substitua-a pelo código a seguir:

     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. Adicione uma classe chamada "SearchData" e substitua-a pelo código a seguir:

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

Modificar o controlador

Para este tutorial, modifique o padrão HomeController para conter os métodos que são executados no seu serviço de pesquisa.

  1. No Gerenciador de Soluções em Modelos, abra HomeController.

  2. Substitua o padrão pelo seguinte conteúdo:

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

Modificar o modo de exibição

  1. No explorador de soluções em Visualizações>Página inicial, abra index.cshtml.

  2. Substitua o padrão pelo seguinte conteúdo:

    @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>
    

Execute o exemplo

  1. Pressione F5 para compilar e executar o projeto. O aplicativo é executado em um host local e abre no seu navegador padrão.

  2. Selecione Pesquisar para retornar todos os resultados.

  3. Esse código usa a configuração de pesquisa padrão, suportando a sintaxe simples e searchMode=Any. Você pode inserir palavras-chave, aumentar com operadores booleanos ou executar uma pesquisa de prefixo (pool*).

Nas próximas seções, modifique o método RunQueryAsync em HomeController para adicionar filtros e classificação.

Resultados do filtro

Os atributos de campo de índice determinam quais campos são pesquisáveis, filtráveis, classificáveis, facetáveis e recuperáveis. Em hotels-sample-index, os campos filtráveis incluem Categoria, Endereço/Cidade e Endereço/Estado/Província. Esse exemplo adiciona uma expressão $Filter em Categoria.

Um filtro sempre é executado primeiro, seguido por uma consulta supondo que uma seja especificada.

  1. Abra o HomeController e encontre o método RunQueryAsync. Adicionar 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. Executar o aplicativo.

  3. Selecione Pesquisar para executar uma consulta vazia. O filtro retorna 18 documentos em vez dos 50 originais.

Para obter mais informações sobre expressões de filtro, consulte Filtros no Azure AI Search e a Sintaxe $filter do OData no Azure AI Search.

Classificar resultados

No hotels-sample-index, os campos classificáveis incluem Classificação e Última renovação. Esse exemplo adiciona uma expressão $OrderBy ao campo Classificação.

  1. Abra o método HomeController e substitua RunQueryAsync pela versão a seguir:

     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. Executar o aplicativo. Os resultados são classificados por Classificação em ordem descendente.

Para saber mais sobre a classificação, confira a Sintaxe $orderby OData no Azure AI Search.

Próximas etapas

Neste tutorial, você criou um projeto de ASP.NET Core (MVC) que se conectou a um serviço de pesquisa e chamou as APIs de Pesquisa para filtragem e classificação do lado do servidor.

Se você quiser explorar código do lado do cliente que responde às ações do usuário, considere adicionar um modelo React à sua solução: