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 em seu serviço de pesquisa. Neste tutorial, saiba como:

  • Criar uma página de pesquisa básica
  • Filtrar resultados
  • Ordenar os resultados

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

O código de exemplo para este tutorial pode ser encontrado no repositório azure-search-dotnet-samples no GitHub.

Pré-requisitos

Percorra o assistente Importar dados para criar o índice de amostra de hotéis no seu serviço de pesquisa. Ou altere o nome do HomeController.cs índice no arquivo.

Criar o projeto

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

  2. Selecione ASP.NET Core Web App (Model-View-Controller) e, em seguida, selecione Next.

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

  4. Na página seguinte, selecione .NET 6.0 ou .NET 7.0 ou .NET 8.0.

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

  6. Selecione Criar.

Adicionar pacotes NuGet

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

  2. Procure Azure.Search.Documents e instale a versão estável mais recente.

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

Adicionar informações de serviço

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

Modifique appsettings.json para especificar o 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 a URL do serviço e a chave da 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 administrador.

Certifique-se de especificar o serviço de pesquisa que tem o índice de amostra de hotéis.

Adicionar modelos

Nesta etapa, crie modelos que representem o esquema do índice de amostra de hotéis.

  1. No Gerenciador de soluções, selecione com o botão direito do mouse Modelos e adicione uma nova classe chamada "Hotel" para o seguinte código:

     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 "Address" e substitua-a pelo seguinte código:

     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 "Rooms" e substitua-a pelo seguinte código:

     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 seguinte código:

     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 métodos que são executados em 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 a vista

  1. No Gerenciador de soluções, em Página inicial de modos de exibição>, 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>
    

Executar o exemplo

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

  2. Selecione Pesquisar para retornar todos os resultados.

  3. Este 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 na HomeController para adicionar filtros e classificação.

Filtrar resultados

Os atributos de campo de índice determinam quais campos são pesquisáveis, filtráveis, classificáveis, compatíveis e recuperáveis. No índice de amostra de hotéis, os campos filtráveis incluem Categoria, Endereço/Cidade e Endereço/EstadoProvíncia. Este exemplo adiciona uma expressão $Filter em Category.

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

  1. Abra o HomeController e localize 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. Execute a aplicação.

  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 OData $filter sintaxe no Azure AI Search.

Ordenar os resultados

No índice de amostra de hotéis, os campos classificáveis incluem Rating e LastRenovated. Este exemplo adiciona uma expressão $OrderBy ao campo Rating.

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

     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. Execute a aplicação. Os resultados são ordenados por Classificação por ordem decrescente.

Para obter mais informações sobre classificação, consulte OData $orderby sintaxe no Azure AI Search.

Próximos passos

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

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