Поделиться через


Создание приложения поиска в ASP.NET Core

В этом руководстве создайте базовое приложение ASP.NET Core (Model-View-Controller), которое работает в localhost и подключается к индексу hotels-sample-index в службе поиска. Из этого руководства вы узнаете, как:

  • Создание базовой страницы поиска
  • Фильтрация результатов
  • Сортировка результатов

В этом руководстве основное внимание уделяется операциям на стороне сервера, которые вызываются через API поиска. Несмотря на то, что обычно выполняется сортировка и фильтрация в клиентском скрипте, зная, как вызывать эти операции на сервере, вы можете использовать дополнительные возможности при разработке интерфейса поиска.

Пример кода для этого руководства можно найти в репозитории azure-search-dotnet-samples на сайте GitHub.

Необходимые компоненты

Пошаговое руководство по импорту данных, чтобы создать индекс hotels-sample-index в службе поиска. Или измените имя индекса в HomeController.cs файле.

Создание проекта

  1. Откройте Visual Studio и выберите Создать проект.

  2. Выберите ASP.NET Core Web App (Model-View-Controller) и нажмите кнопку "Далее".

  3. Укажите имя проекта и нажмите кнопку "Далее".

  4. На следующей странице выберите .NET 6.0 или .NET 7.0 или .NET 8.0.

  5. Убедитесь, что операторы верхнего уровня не используются проверка.

  6. Нажмите кнопку создания.

Добавление пакетов NuGet

  1. В средствах выберите NuGet диспетчер пакетов> Manage NuGet Packages для решения.

  2. Azure.Search.Documents Найдите и установите последнюю стабильную версию.

  3. Найдите и установите Microsoft.Spatial пакет. Пример индекса включает тип данных GeographyPoint. Установка этого пакета позволяет избежать ошибок времени выполнения. Кроме того, удалите поле Location из класса Hotels, если вы не хотите установить пакет. Это поле не используется в этом руководстве.

Добавление сведений о службе

Для подключения приложение представляет ключ API запросов к полному URL-адресу поиска. Оба указаны в appsettings.json файле.

Измените значение appsettings.json , чтобы указать службу поиска и ключ API запроса.

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

Url-адрес службы и ключ API можно получить на портале. Так как этот код запрашивает индекс и не создает его, вместо ключа администратора можно использовать ключ запроса.

Обязательно укажите службу поиска с индексом hotels-sample-index.

Добавление моделей

На этом шаге создайте модели, представляющие схему индекса hotels-sample-index.

  1. В обозревателе решений щелкните правой кнопкой мыши модели и добавьте новый класс с именем Hotel для следующего кода:

     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. Добавьте класс с именем Address и замените его следующим кодом:

     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. Добавьте класс с именем "Комнаты" и замените его следующим кодом:

     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. Добавьте класс с именем SearchData и замените его следующим кодом:

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

Изменение контроллера

В этом руководстве измените значение по умолчанию HomeController , чтобы содержать методы, выполняемые в службе поиска.

  1. В обозревателе решений в разделе "Модели" откройте файл HomeController.

  2. Замените значение по умолчанию следующим содержимым:

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

Изменение представления

  1. В обозревателе решений в разделе "Просмотры>дома" откройте окно .index.cshtml

  2. Замените значение по умолчанию следующим содержимым:

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

Запуск примера

  1. Нажмите клавишу F5 , чтобы скомпилировать и запустить проект. Приложение выполняется на локальном узле и открывается в браузере по умолчанию.

  2. Выберите "Поиск", чтобы вернуть все результаты.

  3. Этот код использует конфигурацию поиска по умолчанию, поддерживая простой синтаксис и searchMode=Any. Можно ввести ключевое слово, расширить логические операторы или выполнить поиск префикса (pool*).

В следующих разделах измените метод RunQueryAsync в HomeController добавлении фильтров и сортировки.

Фильтрация результатов

Атрибуты поля индекса определяют, какие поля доступны для поиска, фильтруются, сортируются, фасетируются и извлекаются. В столбце hotels-sample-index поля, допускающие фильтрацию, включают категории, адрес, город и адрес/StateProvince. В этом примере добавляется выражение $Filter для категории.

Фильтр всегда выполняется сначала, за которым следует запрос, предполагающий, что он указан.

  1. HomeController Откройте и найдите метод RunQueryAsync. Добавление фильтра в 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. Запустите приложение.

  3. Выберите "Поиск", чтобы запустить пустой запрос. Фильтр возвращает 18 документов вместо исходного 50.

Дополнительные сведения о выражениях фильтров см. в статьях "Фильтры" в поиске ИИ Azure и синтаксисе OData $filter в службе "Поиск ИИ Azure".

Сортировка результатов

В столбце hotels-sample-index можно сортировать поля, включая Rating и LastRenovated. В этом примере в поле "Оценка" добавляется выражение $OrderBy .

  1. Откройте и замените HomeControllerметод RunQueryAsync следующей версией:

     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. Запустите приложение. Результаты отсортированы по рейтингу в порядке убывания.

Дополнительные сведения о сортировке см. в синтаксисе OData $orderby в поиске ИИ Azure.

Следующие шаги

В этом руководстве вы создали проект ASP.NET Core (MVC), подключенный к службе поиска и называемый API поиска для фильтрации и сортировки на стороне сервера.

Если вы хотите изучить клиентский код, реагирующий на действия пользователя, попробуйте добавить шаблон React в решение: