Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Fasety umożliwiają samodzielną nawigację, udostępniając zestaw linków do filtrowania wyników. W tym samouczku struktura nawigacji fazetowej jest umieszczana po lewej stronie z etykietami i tekstem, który można kliknąć, aby ograniczyć wyniki.
W tym poradniku nauczysz się, jak:
- Ustaw właściwości modelu jako IsFacetable
- Dodaj nawigację fasetową do swojej aplikacji
Przegląd
Aspekty są oparte na polach w indeksie wyszukiwania. Żądanie zawierające facet=[string] udostępnia pole do filtrowania według. Często należy uwzględnić wiele aspektów, takich jak &facet=category&facet=amenities
, każdy z nich oddzielony znakiem ampersand (&). Zaimplementowanie struktury nawigacji aspektowej wymaga określenia zarówno aspektów, jak i filtrów. Filtr jest używany podczas zdarzenia kliknięcia, aby zawęzić wyniki. Na przykład kliknięcie pozycji "budżet" filtruje wyniki na podstawie tych kryteriów.
Ten samouczek rozszerza projekt stronicowania utworzony w samouczku Dodawanie stronicowania do wyników wyszukiwania .
Ukończona wersja kodu w tym samouczku można znaleźć w następującym projekcie:
Wymagania wstępne
- Rozwiązanie 2a-add-paging (GitHub). Ten projekt może być twoją własną wersją utworzoną z poprzedniego samouczka lub kopią z usługi GitHub.
Ustaw właściwości modelu jako IsFacetable
Aby właściwość modelu była uwzględniana w wyszukiwaniu aspektów, musi zostać otagowana IsFacetable.
Sprawdź klasę Hotel . Kategoria i Tagi są oznaczane jako IsFacetable, ale HotelName i Description nie są.
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 Room[] Rooms { get; set; } }
W ramach tego samouczka nie zmienimy żadnych tagów, dlatego zamknij plik hotel.cs bez zmian.
Uwaga / Notatka
Wyszukiwanie aspektów zgłosi błąd, jeśli pole żądane w wyszukiwaniu nie zostanie odpowiednio oznaczone.
Dodaj nawigację fasetową do aplikacji
W tym przykładzie umożliwimy użytkownikowi wybranie jednej kategorii hotelowej lub jednego udogodnienia z list linków wyświetlanych po lewej stronie wyników. Użytkownik zaczyna od wprowadzenia tekstu wyszukiwania, a następnie stopniowo zawęża wyniki wyszukiwania, wybierając kategorię lub udogodnienia.
Jest to zadanie kontrolera, aby przekazać listy aspektów do widoku. Aby zachować wybrane opcje użytkownika w miarę postępu wyszukiwania, używamy magazynu tymczasowego jako mechanizmu zachowania stanu.
Dodawanie ciągów filtru do modelu SearchData
Otwórz plik SearchData.cs i dodaj właściwości tekstowe do klasy SearchData, aby przechowywać ciągi filtrów faset.
public string categoryFilter { get; set; } public string amenityFilter { get; set; }
Dodaj metodę akcji Facet
Kontroler domu wymaga jednej nowej akcji, Facet, a także aktualizacji istniejących akcji Index i Page oraz metody RunQueryAsync.
Zastąp metodę akcji Index(SearchData model).
public async Task<ActionResult> Index(SearchData model) { try { // Ensure the search string is valid. if (model.searchText == null) { model.searchText = ""; } // Make the search call for the first page. await RunQueryAsync(model, 0, 0, "", "").ConfigureAwait(false); } catch { return View("Error", new ErrorViewModel { RequestId = "1" }); } return View(model); }
Zastąp metodę akcji PageAsync(SearchData model).
public async Task<ActionResult> PageAsync(SearchData model) { try { int page; // Calculate the page that should be displayed. switch (model.paging) { case "prev": page = (int)TempData["page"] - 1; break; case "next": page = (int)TempData["page"] + 1; break; default: page = int.Parse(model.paging); break; } // Recover the leftMostPage. int leftMostPage = (int)TempData["leftMostPage"]; // Recover the filters. string catFilter = TempData["categoryFilter"].ToString(); string ameFilter = TempData["amenityFilter"].ToString(); // Recover the search text. model.searchText = TempData["searchfor"].ToString(); // Search for the new page. await RunQueryAsync(model, page, leftMostPage, catFilter, ameFilter); } catch { return View("Error", new ErrorViewModel { RequestId = "2" }); } return View("Index", model); }
Dodaj metodę akcji FacetAsync(SearchData model), która ma zostać aktywowana po kliknięciu przez użytkownika linku faceta. Model będzie zawierać filtr wyszukiwania kategorii lub udogodnień. Dodaj ją po akcji PageAsync .
public async Task<ActionResult> FacetAsync(SearchData model) { try { // Filters set by the model override those stored in temporary data. string catFilter; string ameFilter; if (model.categoryFilter != null) { catFilter = model.categoryFilter; } else { catFilter = TempData["categoryFilter"].ToString(); } if (model.amenityFilter != null) { ameFilter = model.amenityFilter; } else { ameFilter = TempData["amenityFilter"].ToString(); } // Recover the search text. model.searchText = TempData["searchfor"].ToString(); // Initiate a new search. await RunQueryAsync(model, 0, 0, catFilter, ameFilter).ConfigureAwait(false); } catch { return View("Error", new ErrorViewModel { RequestId = "2" }); } return View("Index", model); }
Konfigurowanie filtru wyszukiwania
Gdy użytkownik wybierze określony aspekt, na przykład kliknie kategorię Resort and Spa , a następnie tylko hotele określone jako ta kategoria powinny zostać zwrócone w wynikach. Aby zawęzić wyszukiwanie w ten sposób, musimy skonfigurować filtr.
Zastąp metodę RunQueryAsync następującym kodem. Przede wszystkim przyjmuje ciąg filtru kategorii i ciąg filtru udogodnień oraz ustawia parametr Filter parametru SearchOptions.
private async Task<ActionResult> RunQueryAsync(SearchData model, int page, int leftMostPage, string catFilter, string ameFilter) { InitSearch(); string facetFilter = ""; if (catFilter.Length > 0 && ameFilter.Length > 0) { // Both facets apply. facetFilter = $"{catFilter} and {ameFilter}"; } else { // One, or zero, facets apply. facetFilter = $"{catFilter}{ameFilter}"; } var options = new SearchOptions { Filter = facetFilter, SearchMode = SearchMode.All, // Skip past results that have already been returned. Skip = page * GlobalVariables.ResultsPerPage, // Take only the next page worth of results. Size = GlobalVariables.ResultsPerPage, // Include the total number of results. IncludeTotalCount = true, }; // Return information on the text, and number, of facets in the data. options.Facets.Add("Category,count:20"); options.Facets.Add("Tags,count:20"); // Enter Hotel property names into this list, so only these values will be returned. options.Select.Add("HotelName"); options.Select.Add("Description"); options.Select.Add("Category"); options.Select.Add("Tags"); // 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); // This variable communicates the total number of pages to the view. model.pageCount = ((int)model.resultList.TotalCount + GlobalVariables.ResultsPerPage - 1) / GlobalVariables.ResultsPerPage; // This variable communicates the page number being displayed to the view. model.currentPage = page; // Calculate the range of page numbers to display. if (page == 0) { leftMostPage = 0; } else if (page <= leftMostPage) { // Trigger a switch to a lower page range. leftMostPage = Math.Max(page - GlobalVariables.PageRangeDelta, 0); } else if (page >= leftMostPage + GlobalVariables.MaxPageRange - 1) { // Trigger a switch to a higher page range. leftMostPage = Math.Min(page - GlobalVariables.PageRangeDelta, model.pageCount - GlobalVariables.MaxPageRange); } model.leftMostPage = leftMostPage; // Calculate the number of page numbers to display. model.pageRange = Math.Min(model.pageCount - leftMostPage, GlobalVariables.MaxPageRange); // Ensure Temp data is stored for the next call. TempData["page"] = page; TempData["leftMostPage"] = model.leftMostPage; TempData["searchfor"] = model.searchText; TempData["categoryFilter"] = catFilter; TempData["amenityFilter"] = ameFilter; // Return the new view. return View("Index", model); }
Zwróć uwagę, że właściwości Kategoria i Tagi są dodawane do listy elementów Wybieranych do zwrócenia. To dodanie nie jest wymagane, aby nawigacja aspektowa działała, ale używamy tych informacji do sprawdzania, czy filtry działają poprawnie.
Dodaj listy linków fasetowych do widoku
Widok będzie wymagał znaczących zmian.
Zacznij od otwarcia pliku hotels.css (w folderze wwwroot/css) i dodaj następujące klasy.
.facetlist { list-style: none; } .facetchecks { width: 250px; display: normal; color: #666; margin: 10px; padding: 5px; } .facetheader { font-size: 10pt; font-weight: bold; color: darkgreen; }
Aby widok był czytelny, dane wyjściowe należy uporządkować w tabeli, starannie wyrównując listy aspektów po lewej i wyniki po prawej stronie. Otwórz plik index.cshtml. Zastąp całą zawartość tagów HTML <body> następującym kodem.
<body> @using (Html.BeginForm("Index", "Home", FormMethod.Post)) { <table> <tr> <td></td> <td> <h1 class="sampleTitle"> <img src="~/images/azure-logo.png" width="80" /> Hotels Search - Facet Navigation </h1> </td> </tr> <tr> <td></td> <td> <!-- Display the search text box, with the search icon to the right of it.--> <div class="searchBoxForm"> @Html.TextBoxFor(m => m.searchText, new { @class = "searchBox" }) <input value="" class="searchBoxSubmit" type="submit"> </div> </td> </tr> <tr> <td valign="top"> <div id="facetplace" class="facetchecks"> @if (Model != null && Model.resultList != null) { List<string> categories = Model.resultList.Facets["Category"].Select(x => x.Value.ToString()).ToList(); if (categories.Count > 0) { <h5 class="facetheader">Category:</h5> <ul class="facetlist"> @for (var c = 0; c < categories.Count; c++) { var facetLink = $"{categories[c]} ({Model.resultList.Facets["Category"][c].Count})"; <li> @Html.ActionLink(facetLink, "FacetAsync", "Home", new { categoryFilter = $"Category eq '{categories[c]}'" }, null) </li> } </ul> } List<string> tags = Model.resultList.Facets["Tags"].Select(x => x.Value.ToString()).ToList(); if (tags.Count > 0) { <h5 class="facetheader">Amenities:</h5> <ul class="facetlist"> @for (var c = 0; c < tags.Count; c++) { var facetLink = $"{tags[c]} ({Model.resultList.Facets["Tags"][c].Count})"; <li> @Html.ActionLink(facetLink, "FacetAsync", "Home", new { amenityFilter = $"Tags/any(t: t eq '{tags[c]}')" }, null) </li> } </ul> } } </div> </td> <td valign="top"> <div id="resultsplace"> @if (Model != null && Model.resultList != null) { // Show the result count. <p class="sampleText"> @Model.resultList.TotalCount Results </p> var results = Model.resultList.GetResults().ToList(); @for (var i = 0; i < results.Count; i++) { string amenities = string.Join(", ", results[i].Document.Tags); string fullDescription = results[i].Document.Description; fullDescription += $"\nCategory: {results[i].Document.Category}"; fullDescription += $"\nAmenities: {amenities}"; // Display the hotel name and description. @Html.TextAreaFor(m => results[i].Document.HotelName, new { @class = "box1" }) @Html.TextArea($"desc{i}", fullDescription, new { @class = "box2" }) } } </div> </td> </tr> <tr> <td></td> <td valign="top"> @if (Model != null && Model.pageCount > 1) { // If there is more than one page of results, show the paging buttons. <table> <tr> <td class="tdPage"> @if (Model.currentPage > 0) { <p class="pageButton"> @Html.ActionLink("|<", "PageAsync", "Home", new { paging = "0" }, null) </p> } else { <p class="pageButtonDisabled">|<</p> } </td> <td class="tdPage"> @if (Model.currentPage > 0) { <p class="pageButton"> @Html.ActionLink("<", "PageAsync", "Home", new { paging = "prev" }, null) </p> } else { <p class="pageButtonDisabled"><</p> } </td> @for (var pn = Model.leftMostPage; pn < Model.leftMostPage + Model.pageRange; pn++) { <td class="tdPage"> @if (Model.currentPage == pn) { // Convert displayed page numbers to 1-based and not 0-based. <p class="pageSelected">@(pn + 1)</p> } else { <p class="pageButton"> @Html.ActionLink((pn + 1).ToString(), "PageAsync", "Home", new { paging = @pn }, null) </p> } </td> } <td class="tdPage"> @if (Model.currentPage < Model.pageCount - 1) { <p class="pageButton"> @Html.ActionLink(">", "PageAsync", "Home", new { paging = "next" }, null) </p> } else { <p class="pageButtonDisabled">></p> } </td> <td class="tdPage"> @if (Model.currentPage < Model.pageCount - 1) { <p class="pageButton"> @Html.ActionLink(">|", "PageAsync", "Home", new { paging = Model.pageCount - 1 }, null) </p> } else { <p class="pageButtonDisabled">>|</p> } </td> </tr> </table> } </td> </tr> </table> } </body>
Zwróć uwagę na użycie wywołania Html.ActionLink . To wywołanie komunikuje prawidłowe ciągi filtru do kontrolera, gdy użytkownik kliknie link aspektu.
Uruchamianie i testowanie aplikacji
Zaletą nawigacji aspektowej dla użytkownika jest możliwość zawężenia wyszukiwań jednym kliknięciem, które możemy wyświetlić w poniższej sekwencji.
Uruchom aplikację, wpisz "airport" jako tekst wyszukiwania. Sprawdź, czy lista aspektów jest wyświetlana starannie po lewej stronie. Te aspekty mają zastosowanie do hoteli, które mają "lotnisko" w swoich danych tekstowych, z liczbą częstotliwości ich występowania.
Kliknij kategorię Ośrodek i Spa . Sprawdź, czy wszystkie wyniki znajdują się w tej kategorii.
Kliknij opcję śniadania kontynentalnego. Sprawdź, czy wszystkie wyniki są nadal dostępne w kategorii "Resort and Spa" z wybranymi udogodnieniami.
Spróbuj wybrać dowolną inną kategorię, a następnie jeden z udogodnień i wyświetlić zawężone wyniki. Następnie spróbuj w drugą stronę, jedno udogodnienie, i potem jedna kategoria. Wprowadź puste wyszukiwanie, aby zresetować stronę.
Uwaga / Notatka
Po wybraniu jednego zaznaczenia na liście aspektów (takiej jak kategoria) zostaną one zastąpione dowolnym poprzednim wyborem na liście kategorii.
Wnioski
Rozważ następujące wnioski z tego projektu:
- Należy oznaczyć każde pole aspektowe właściwością IsFacetable w celu włączenia ich do nawigacji aspektowej.
- Aspekty są łączone z filtrami, aby zawęzić wyniki.
- Aspekty działają kumulatywnie, z każdym wyborem opierającym się na poprzednim, aby dodatkowo zawęzić wyniki.
Dalsze kroki
W następnym samouczku przyjrzymy się porządkowaniu wyników. Do tego momentu wyniki są uporządkowane po prostu w kolejności, w której znajdują się w bazie danych.