Condividi tramite


Esercitazione: Aggiungere la navigazione sfaccettata usando il .NET SDK

I facet consentono lo spostamento autoindirizzato fornendo un set di collegamenti per filtrare i risultati. In questo tutorial, una struttura di navigazione a faccette è posizionata sul lato sinistro della pagina, con etichette e testo cliccabile per filtrare i risultati.

In questa esercitazione si apprenderà come:

  • Impostare le proprietà del modello su IsFacetable
  • Aggiungere la navigazione a faccette all'app

Informazioni generali

Facet sono basati sui campi nel tuo indice di ricerca. Una richiesta di query che include facet=[string] fornisce il campo per determinare il campo da utilizzare per la suddivisione in facet. È comune includere più faccette, ad esempio &facet=category&facet=amenities, ogni faccetta separata da un carattere e commerciale (&). Per implementare una struttura di navigazione a faccette, è necessario specificare sia le faccette che i filtri. Il filtro viene utilizzato in un evento Click per restringere i risultati. Ad esempio, facendo clic su "budget" vengono filtrati i risultati in base a tali criteri.

Questa esercitazione amplia il progetto di creazione di paging realizzato nell'esercitazione Aggiungere paging ai risultati della ricerca.

Una versione completa del codice di questa esercitazione si trova nel progetto seguente:

Prerequisiti

Impostare le proprietà del modello su IsFacetable

Affinché una proprietà del modello si trovi in una ricerca in facet, deve essere contrassegnata con IsFacetable.

  1. Esaminare la classe Hotel . Categoria e Tag, ad esempio, vengono contrassegnati come IsFacetable, ma HotelName e Description non sono.

    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. Non modificheremo alcun tag come parte di questo tutorial, quindi chiudi il file hotel.cs senza modificarlo.

    Annotazioni

    Una ricerca facet genererà un errore se un campo richiesto nella ricerca non viene contrassegnato in modo appropriato.

Aggiungi la navigazione a faccette alla tua app

Per questo esempio, si abiliterà l'utente a selezionare una categoria di hotel, o un servizio, da elenchi di collegamenti visualizzati a sinistra dei risultati. L'utente inizia immettendo un testo di ricerca, quindi restringe progressivamente i risultati della ricerca selezionando una categoria o un servizio.

È compito del controller passare gli elenchi di facce alla vista. Per mantenere le selezioni utente man mano che la ricerca avanza, viene usata l'archiviazione temporanea come meccanismo per mantenere lo stato.

Uso della navigazione a faccette per restringere la ricerca su

Aggiungere stringhe di filtro al modello SearchData

  1. Aprire il file SearchData.cs e aggiungere proprietà stringa alla classe SearchData per contenere le stringhe di filtro facet.

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

Aggiungere il metodo di azione Facet

Il controller home richiede una nuova azione, Facet e aggiorna le azioni index e page esistenti e al metodo RunQueryAsync .

  1. Sostituire il metodo di azione 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);
    }
    
  2. Sostituire il metodo di azione 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);
    }
    
  3. Aggiungere un metodo di azione FacetAsync(SearchData model) da attivare quando l'utente fa clic su un collegamento a facet. Il modello conterrà un filtro di ricerca per categorie o servizi. Aggiungerlo dopo l'azione 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);
    }
    

Configurare il filtro di ricerca

Quando un utente seleziona un determinato facet, ad esempio, fa clic sulla categoria Resort e Spa , quindi solo gli hotel specificati come questa categoria devono essere restituiti nei risultati. Per restringere una ricerca in questo modo, è necessario configurare un filtro.

  1. Sostituire il metodo RunQueryAsync con il codice seguente. Principalmente, accetta una stringa di filtro di categoria e una stringa di filtro dei servizi e imposta il parametro Filter di 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);
    }
    

    Si noti che le proprietà Category e Tags vengono aggiunte all'elenco degli elementi Select da restituire. Questa aggiunta non è un requisito per il funzionamento dell'esplorazione facet, ma queste informazioni vengono usate per verificare che i filtri funzionino correttamente.

La visualizzazione richiederà alcune modifiche significative.

  1. Per iniziare, aprire il file hotels.css (nella cartella wwwroot/css) e aggiungere le classi seguenti.

    .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. Per la vista, organizzare l'output in una tabella, per allineare in modo ordinato gli elenchi di facet a sinistra e i risultati a destra. Aprire il file index.cshtml. Sostituire l'intero contenuto dei tag <body> HTML con il codice seguente.

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

    Si noti l'uso della chiamata Html.ActionLink . Questa chiamata comunica stringhe di filtro valide al controller, quando l'utente fa clic su un collegamento facet.

Eseguire e testare l'app

Il vantaggio della navigazione in facet all'utente è che è possibile restringere le ricerche con un solo clic, che è possibile visualizzare nella sequenza seguente.

  1. Eseguire l'app, digitare "airport" come testo di ricerca. Verificare che l'elenco delle facce appaia in modo ordinato a sinistra. Gli aspetti sono applicabili a tutti gli hotel che hanno "aeroporto" nei loro dati di testo, con un conteggio della loro frequenza.

    Utilizzo della navigazione a facet per affinare la ricerca di

  2. Fare clic sulla categoria Resort and Spa . Verificare che tutti i risultati siano in questa categoria.

    Restringendo la ricerca a

  3. Fare clic sul servizio colazione continentale . Verificare che tutti i risultati siano ancora nella categoria "Resort e Spa", con il servizio selezionato.

    Restringendo la ricerca alla

  4. Provare a selezionare qualsiasi altra categoria, quindi un servizio e visualizzare i risultati ristretti. Poi provare l'altro modo, un servizio, poi una categoria. Inviare una ricerca vuota per reimpostare la pagina.

    Annotazioni

    Quando viene effettuata una selezione in un elenco di facet, ad esempio categoria, sostituirà qualsiasi selezione precedente all'interno dell'elenco delle categorie.

Risultati

Si considerino le considerazioni seguenti di questo progetto:

  • È fondamentale contrassegnare ogni campo facetable con la proprietà IsFacetable per l'inclusione nella navigazione a faccette.
  • I facet vengono combinati con filtri per ridurre i risultati.
  • I facet sono cumulativi, ogni selezione si basa su quella precedente per restringere ulteriormente i risultati.

Passaggi successivi

Nel prossimo tutorial, esamineremo l'ordinamento dei risultati. Fino a questo punto, i risultati vengono ordinati semplicemente nell'ordine in cui si trovano nel database.