Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Faset memungkinkan navigasi yang diarahkan sendiri dengan menyediakan sekumpulan tautan untuk memfilter hasil. ; Dalam tutorial ini, struktur navigasi berfasilitas ditempatkan di sisi kiri halaman, dengan label dan teks yang dapat diklik untuk memangkas hasilnya.
Dalam tutorial ini, Anda akan belajar cara:
- Mengatur properti model sebagai IsFacetable
- Menambahkan navigasi faset ke aplikasi Anda
Gambaran Umum
Faset didasarkan pada bidang dalam indeks pencarian Anda. Permintaan kueri yang menyertakan facet=[string] menyediakan bidang untuk difasetkan. Adalah umum untuk menyertakan beberapa faset, seperti &facet=category&facet=amenities, masing-masing dipisahkan oleh karakter ampersand (&). Menerapkan struktur navigasi berfasilitas mengharuskan Anda menentukan faset dan filter. Filter digunakan pada peristiwa klik untuk mempersempit hasil. Misalnya, mengklik "anggaran" memfilter hasil berdasarkan kriteria tersebut.
Tutorial ini memperluas proyek halaman yang dibuat di tutorial Tambahkan halaman ke hasil pencarian .
Versi selesai kode dalam tutorial ini dapat ditemukan dalam proyek berikut:
Prasyarat
- Solusi 2a-add-paging (GitHub). Proyek ini dapat berupa versi Anda sendiri yang dibangun dari tutorial sebelumnya atau salinan dari GitHub.
Atur properti model sebagai IsFacetable
Agar properti model terletak di pencarian faset, properti harus ditandai dengan IsFacetable.
Periksa kelas Hotel . Kategori dan Tag, misalnya, ditandai sebagai IsFacetable, tetapi HotelName dan Description tidak.
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; } }Kami tidak akan mengubah tag apa pun sebagai bagian dari tutorial ini, jadi tutup file hotel.cs yang tidak diubah.
Nota
Pencarian faset akan memunculkan kesalahan jika bidang yang diminta dalam pencarian tidak ditandai dengan tepat.
Menambahkan navigasi faset ke aplikasi Anda
Untuk contoh ini, kami akan memungkinkan pengguna untuk memilih satu kategori hotel, atau satu fasilitas, dari daftar tautan yang ditampilkan di sebelah kiri hasil. Pengguna mulai dengan memasukkan beberapa teks pencarian, lalu mempersempit hasil pencarian secara progresif dengan memilih kategori atau fasilitas.
Ini adalah tugas pengontrol untuk meneruskan daftar faset ke tampilan. Untuk mempertahankan pilihan pengguna saat pencarian berlangsung, kami menggunakan penyimpanan sementara sebagai mekanisme untuk mempertahankan status.
Menambahkan string filter ke model SearchData
Buka file SearchData.cs, dan tambahkan properti string ke kelas SearchData , untuk menahan string filter faset.
public string categoryFilter { get; set; } public string amenityFilter { get; set; }
Tambahkan metode aksi Faset
Pengontrol rumah memerlukan satu tindakan baru, Faset, serta melakukan pembaruan pada tindakan yang sudah ada, yaitu Indeks dan Halaman, serta pada metode RunQueryAsync.
Ganti metode aksi Index(model SearchData).
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); }Ganti metode tindakan PageAsync(model SearchData).
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); }Tambahkan metode tindakan FacetAsync(model SearchData), untuk diaktifkan saat pengguna mengklik tautan faset. Model akan memuat filter pencarian kategori atau fasilitas. Tambahkan setelah tindakan 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); }
Menyiapkan filter pencarian
Ketika pengguna memilih faset tertentu, misalnya, mereka mengklik kategori Resort and Spa , maka hanya hotel yang ditentukan sebagai kategori ini yang harus dikembalikan dalam hasil. Untuk mempersempit pencarian dengan cara ini, kita perlu menyiapkan filter.
Ganti metode RunQueryAsync dengan kode berikut. Pada dasarnya, dibutuhkan string filter kategori, dan string filter fasilitas, lalu mengatur parameter Filter pada 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); }Perhatikan bahwa properti Kategori dan Tag ditambahkan ke daftar Pilih item untuk dikembalikan. Penambahan ini bukan persyaratan untuk navigasi faset agar berfungsi, tetapi kami menggunakan informasi ini untuk memverifikasi bahwa filter berfungsi dengan benar.
Tambahkan daftar tautan faset ke dalam tampilan
Tampilan akan memerlukan beberapa perubahan yang signifikan.
Mulailah dengan membuka file hotels.css (di folder wwwroot/css), dan tambahkan kelas berikut.
.facetlist { list-style: none; } .facetchecks { width: 250px; display: normal; color: #666; margin: 10px; padding: 5px; } .facetheader { font-size: 10pt; font-weight: bold; color: darkgreen; }Untuk tampilan, atur output ke dalam tabel, agar daftar aspek tersusun rapi di sebelah kiri, dan hasilnya di sebelah kanan. Buka file index.cshtml. Ganti seluruh isi tag HTML <body> dengan kode berikut.
<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>Perhatikan penggunaan panggilan Html.ActionLink . Panggilan ini mengomunikasikan string filter yang valid ke pengontrol, ketika pengguna mengklik tautan faset.
Menjalankan dan menguji aplikasi
Keuntungan navigasi faset kepada pengguna adalah mereka dapat mempersempit pencarian dengan satu klik, yang dapat kita tampilkan dalam urutan berikut.
Jalankan aplikasi, ketik "airport" sebagai teks pencarian. Verifikasi bahwa daftar aspek muncul dengan rapi di sebelah kiri. Aspek-aspek ini adalah semua yang berlaku untuk hotel yang memiliki "bandara" dalam data teks mereka, dengan hitungan seberapa sering mereka terjadi.
Klik kategori Resor dan Spa . Verifikasi bahwa semua hasil berada dalam kategori ini.
Klik fasilitas sarapan kontinental . Verifikasi bahwa semua hasil masih dalam kategori "Resort and Spa", dengan fasilitas yang dipilih.
Mencoba memilih kategori lainnya, lalu satu fasilitas, dan lihat hasil yang semakin sempit. Kemudian coba sebaliknya, satu fasilitas, lalu satu kategori. Kirim pencarian kosong untuk mereset halaman.
Nota
Ketika satu pilihan dibuat dalam daftar faset (seperti kategori), itu akan menggantikan pilihan sebelumnya dalam daftar kategori.
Kesimpulan
Pertimbangkan hal-hal berikut dari proyek ini:
- Sangat penting untuk menandai setiap bidang yang dapat difaset dengan properti IsFacetable untuk dimasukkan dalam navigasi faset.
- Faset dikombinasikan dengan filter untuk mengurangi hasilnya.
- Faset bersifat kumulatif, dengan setiap pilihan membangun pada yang sebelumnya untuk semakin mempersempit hasil.
Langkah selanjutnya
Dalam tutorial berikutnya, kita melihat hasil pemesanan. Hingga saat ini, hasil diurutkan hanya dalam urutan lokasinya dalam database.