Tutorial: Tambahkan navigasi tersaring menggunakan .NET SDK

Faset memungkinkan navigasi yang diarahkan sendiri dengan menyediakan set tautan untuk memfilter hasil. Dalam tutorial ini, struktur navigasi tersaring ditempatkan di sisi kiri halaman, dengan label dan teks yang dapat diklik untuk memangkas hasilnya.

Dalam tutorial ini, Anda akan mempelajari 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 faset=[untai] menyediakan bidang untuk faset. Ini umumnya menyertakan beberapa aspek, seperti &facet=category&facet=amenities, masing-masing dipisahkan oleh karakter ampersand (&). Menerapkan struktur navigasi tersaring mengharuskan Anda menentukan faset dan filter. Filter digunakan pada peristiwa klik untuk mempersempit hasil. Misalnya, mengklik "anggaran" akan memfilter hasil berdasarkan kriteria tersebut.

Tutorial ini memperluas proyek penomoran yang dibuat di tutorial Menambahkan penomoran 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 dibuat dari tutorial sebelumnya atau salinan dari GitHub.

Mengatur properti model sebagai IsFacetable

Agar properti model dapat ditemukan dalam pencarian faset, properti harus ditandai dengan IsFacetable.

  1. Periksa kelas Hotel. Kategori dan Tag, misalnya, ditandai sebagai IsFacetable, tetapi HotelName dan Deskripsi 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; }
    }
    
  2. Kita tidak akan mengubah tag apa pun sebagai bagian dari tutorial ini, jadi menutup file hotel.cs tidak berubah.

    Catatan

    Pencarian aspek akan menghasilkan kesalahan jika bidang yang diminta dalam pencarian tidak ditandai dengan tepat.

Menambahkan navigasi faset ke aplikasi Anda

Untuk contoh ini, kita akan mengizinkan pengguna untuk memilih satu kategori hotel, atau satu fasilitas, dari daftar tautan yang ditunjukkan di sebelah kiri hasil. Pengguna mulai dengan memasukkan beberapa teks pencarian, lalu secara progresif mempersempit hasil pencarian dengan memilih kategori atau fasilitas.

Ini adalah tugas pengendali untuk meneruskan daftar aspek ke tampilan. Untuk mempertahankan pilihan pengguna saat pencarian berlangsung, kita menggunakan penyimpanan sementara sebagai mekanisme untuk mempertahankan status.

Gunakan navigasi faset untuk mempersempit pencarian

Tambahkan string filter ke model SearchData

  1. 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 tindakan Faset

Pengendali beranda memerlukan satu tindakan baru, Faset, dan pembaruan untuk tindakan Indeks dan Halaman yang ada, dan ke metode RunQueryAsync.

  1. Ganti metode tindakan Indeks(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);
    }
    
  2. 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);
    }
    
  3. Tambahkan metode tindakan FacetAsync(model SearchData) , untuk diaktifkan saat pengguna mengklik tautan faset. Model akan berisi 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 Resor dan Spa, maka hanya hotel yang ditentukan sebagai kategori ini yang harus dikembalikan dalam hasil. Untuk mempersempit pencarian dengan cara ini, kita perlu menyiapkan filter.

  1. Ganti metode RunQueryAsync dengan kode berikut. Terutama, dibutuhkan string filter kategori, dan string filter fasilitas, dan mengatur parameter Filter dari 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 akan dikembalikan. Penambahan ini bukan persyaratan untuk navigasi agar faset berfungsi, tetapi kkita menggunakan informasi ini untuk memverifikasi apakah filter berfungsi dengan baik.

Tampilan ini akan membutuhkan beberapa perubahan yang signifikan.

  1. 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;
    }
    
  2. Untuk tampilan, atur output ke dalam tabel, untuk meratakan daftar faset dengan rapi di sebelah kiri, dan hasilnya di sebelah kanan. Buka file Index.cshtml. Ganti seluruh konten tag <isi> HTML 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">|&lt;</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">&lt;</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">&gt;</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">&gt;|</p>
                                        }
                                    </td>
                                </tr>
                            </table>
                        }
                    </td>
                </tr>
            </table>
        }
    </body>
    

    Perhatikan penggunaan panggilan Html.ActionLink. Panggilan ini mengomunikasikan string filter yang valid ke pengendali, ketika pengguna mengklik tautan faset.

Jalankan dan uji aplikasi

Keuntungan dari navigasi faset untuk pengguna adalah mereka dapat mempersempit pencarian dengan sekali klik, yang dapat kita tampilkan dalam urutan berikut.

  1. Jalankan aplikasi, ketik "bandara" sebagai teks pencarian. Verifikasi bahwa daftar aspek muncul dengan rapi di sebelah kiri. Faset-faset ini adalah semua yang berlaku untuk hotel yang memiliki "bandara" dalam data teks mereka, dengan hitungan seberapa sering mereka muncul.

    Gunakan navigasi faset untuk mempersempit pencarian

  2. Klik kategori Resor dan Spa. Verifikasi semua hasil dalam kategori ini.

    Mempersempit pencarian ke

  3. Klik fasilitas sarapan ringan. Verifikasi semua hasil masih dalam kategori "Resor dan Spa", dengan fasilitas yang dipilih.

    Mempersempit pencarian untuk

  4. Coba pilih kategori lainnya, lalu satu fasilitas, dan lihat hasil yang dipersempit. Lalu coba sebaliknya, satu fasilitas, lalu satu kategori. Kirim pencarian kosong untuk mengatur ulang halaman.

    Catatan

    Ketika satu pilihan dibuat dalam daftar aspek (seperti kategori) ini akan menimpa pilihan sebelumnya dalam daftar kategori.

Bawa pulang

Pertimbangkan bawa pulang berikut dari proyek ini:

  • Sangat penting untuk menandai setiap bidang yang dapat difaset dengan properti IsFacetable untuk disertakan dalam navigasi faset.
  • Faset dikombinasikan dengan filter untuk mengurangi hasilnya.
  • Faset bersifat kumulatif, dengan setiap pemilihan build pada build sebelumnya untuk hasil yang lebih sempit.

Langkah berikutnya

Dalam tutorial berikutnya, kita melihat hasil pemesanan. Hingga saat ini, hasil dipesan hanya dalam urutan bahwa mereka terletak di database.