Öğretici: .NET SDK'sını kullanarak çok yönlü gezinti ekleme

Özellikler, sonuçları filtrelemek için kendi kendine yönlendirmeli bir gezinti sağlayan bir dizi bağlantı sunar. Bu öğreticide, sayfanın sol tarafına, sonuçları sınırlamak için etiketler ve tıklanabilir metin içeren bir fasetli gezinme yapısı yerleştirilir.

Bu öğreticide aşağıdakilerin nasıl yapılacağını öğreneceksiniz:

  • Model özelliklerini IsFacetable olarak ayarlama
  • Uygulamanıza faset gezintisi ekleyin

Genel Bakış

Fasetler, arama dizininizdeki alanlara dayanır. model=[dize] içeren bir sorgu isteği, modellenecek alanı sağlar. Ampersand (&) karakteriyle ayrılan &facet=category&facet=amenitiesgibi birden fazla boyutun eklenmesi yaygın bir durumdur. Özellikli bir gezinti yapısı uygulamak için hem özellikleri hem de filtreleri belirtmeniz gerekir. Filtre, sonuçları daraltmak için tıklama olayında kullanılır. Örneğin, "bütçe" seçeneğine tıklanması sonuçları bu ölçütlere göre filtreler.

Bu öğretici, Sayfalama ekleme öğreticisinde oluşturulan arama sonuçları projesini genişletir.

Bu öğreticideki kodun tamamlanmış bir sürümü aşağıdaki projede bulunabilir:

Önkoşullar

  • 2a-add-paging (GitHub) çözümü. Bu proje, önceki öğreticiden oluşturduğunuz kendi sürümünüz veya GitHub'dan bir kopya olabilir.

Model özelliklerini IsFacetable olarak ayarlama

Model özelliğinin faceted aramada bulunması için IsFacetableile etiketlenmesi gerekir.

  1. Hotel sınıfını inceleyin. Kategori ve Etiketleri, örneğin IsFacetableolarak etiketlenirken, HotelName ve Açıklama etiketlenmez.

    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. Bu öğreticinin bir parçası olarak hiçbir etiketi değiştirmeyeceğiz, bu nedenle hotel.cs dosyasını değiştirmeden kapatın.

    Uyarı

    Aramada istenen bir alan uygun şekilde etiketlenmediyse faset araması hata verir.

Uygulamanıza faset gezintisi ekleyin

Bu örnekte, kullanıcının sonuçların solunda gösterilen bağlantı listelerinden bir otel kategorisi veya bir olanak seçmesini etkinleştireceğiz. Kullanıcı bir arama metni girerek başlar, ardından bir kategori veya olanak seçerek arama sonuçlarını aşamalı olarak daraltıyor.

Fasetlerin listelerini görünüme iletmek denetleyicinin işidir. Arama ilerledikçe kullanıcı seçimlerini korumak için, durumu koruma mekanizması olarak geçici depolamayı kullanırız.

SearchData modeline filtre dizeleri ekleme

  1. SearchData.cs dosyasını açın ve faset filtresi dizelerini tutmak için SearchData sınıfına string özellikleri ekleyin.

    public string categoryFilter { get; set; }
    public string amenityFilter { get; set; }
    

"Facet eylem yöntemini ekle"

Ev denetleyicisinin bir yeni işlem ihtiyacı vardır, model , ve mevcut Dizin ve Sayfa işlemlerinin yanı sıra RunQueryAsync yöntemi için de güncellemeler gerekmektedir.

  1. Index(SearchData modeli) eylem yöntemini değiştirin.

    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. PageAsync(SearchData modeli) eylem yöntemini değiştirin.

    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. Kullanıcı bir faset bağlantısına tıkladığında etkinleştirilmek üzere bir FacetAsync(SearchData modeli) eylem yöntemi ekleyin. Model bir kategori veya olanak arama filtresi içerir. PageAsync eyleminden sonra ekleyin.

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

Arama filtresini ayarlama

Örneğin, bir kullanıcı belirli bir özellik seçtiklerinde, Resort ve Spa kategorisine tıklarsa, sonuçlarda yalnızca bu kategori olarak belirtilen oteller gösterilmelidir. Bir aramayı bu şekilde daraltmak için birfiltresi ayarlamamız gerekir.

  1. RunQueryAsync yöntemini aşağıdaki kodla değiştirin. Öncelikle bir kategori filtre dizesi ve bir olanak filtresi dizesi alır ve SearchOptions'ün Filter parametresini ayarlar.

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

    Kategori ve Etiketleri özelliklerinin döndürülecek öğeleri seçin listesine eklendiğine dikkat edin. Bu ekleme model gezintisinin çalışması için bir gereksinim değildir, ancak filtrelerin düzgün çalıştığını doğrulamak için bu bilgileri kullanırız.

Görünümde bazı önemli değişiklikler yapılması gerekecek.

  1. başlangıç olarak hotels.css dosyasını açın (wwwroot/css klasöründe) ve aşağıdaki sınıfları ekleyin.

    .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. Görünümde, faset listelerini sola ve sonuçları sağa düzenli bir şekilde hizalamak için çıkışı bir tablo halinde düzenleyin. index.cshtml dosyasını açın. HTML <gövdesi> etiketlerinin tüm içeriğini aşağıdaki kodla değiştirin.

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

    Html.ActionLink çağrısının kullanıldığına dikkat edin. Bu çağrı, kullanıcı bir faset bağlantısına tıkladığında denetleyiciye geçerli filtre dizelerini iletir.

Uygulamayı çalıştırma ve test edin

Kullanıcıya faset gezintisinin avantajı, aramaları tek bir tıklamayla daraltabilmesidir, bunu aşağıdaki sırayla gösterebiliriz.

  1. Uygulamayı çalıştırın, arama metni olarak "airport" yazın. Yüzeyler listesinin solda düzgün göründüğünü doğrulayın. Bu modeller, metin verilerinde "havaalanı" bulunan oteller için geçerli olan ve bunların ne sıklıkta gerçekleştiğini sayan modellerdir.

  2. Resort ve Spa kategorisine tıklayın. Tüm sonuçların bu kategoride olduğunu doğrulayın.

    Aramayı

  3. kontinental kahvaltı seçeneğine tıklayın. Seçilen olanakla birlikte tüm sonuçların hala "Resort and Spa" kategorisinde olduğunu doğrulayın.

    Aramayı

  4. Başka bir kategoriyi ve ardından bir olanak seçmeyi deneyin ve daraltma sonuçlarını görüntüleyin. Diğer yolu deneyin, önce bir olanak ve ardından bir kategori. Sayfayı sıfırlamak için boş bir arama gönderin.

    Uyarı

    Bir yüz listesinde (kategori gibi) bir seçim yapıldığında, kategori listesindeki önceki herhangi bir seçimi geçersiz kılar.

Çıkarımlar

Bu projeden aşağıdakileri göz önünde bulundurun:

  • Fazet gezintisine eklenmesi için her bir fazet alanını IsFacetable özelliğiyle işaretlemek zorunludur.
  • Özellikler, sonuçları daraltmak için filtrelerle birleştirilir.
  • Etmenler kümülatiftir, her seçim öncekilere dayanarak sonuçları daha da daraltır.

Sonraki adımlar

Bir sonraki öğreticide sonuçların sıralanmasına bakacağız. Bu noktaya kadar, sonuçlar yalnızca veritabanında bulundukları sırayla sıralanır.