Sdílet prostřednictvím


Kurz: Přidání stránkování do výsledků hledání pomocí sady .NET SDK

Naučte se implementovat dva různé systémy stránkování, první založený na číslech stránek a druhý na nekonečné posouvání. Oba systémy stránkování jsou široce používány a výběr správného systému závisí na uživatelském prostředí, které chcete s výsledky použít.

V tomto kurzu získáte informace o těchto tématech:

  • Rozšíření aplikace o číslování stránkování
  • Rozšíření aplikace pomocí nekonečného posouvání

Přehled

Tento kurz překrývá stránkovací systém do dříve vytvořeného projektu popsaného v kurzu Vytvoření první vyhledávací aplikace .

Dokončené verze kódu, které budete vyvíjet v tomto kurzu, najdete v následujících projektech:

Požadavky

Rozšíření aplikace o číslování stránkování

Číslování stránkování je systém stránkování pro hlavní komerční webové vyhledávače a mnoho dalších vyhledávacích webů. Číslování stránkování obvykle zahrnuje kromě rozsahu skutečných čísel stránek možnost "další" a "předchozí". K dispozici mohou být také možnosti "první stránka" a "poslední stránka". Tyto možnosti jistě dávají uživateli kontrolu nad procházením výsledků založených na stránkách.

V tomto kurzu přidáte systém, který obsahuje první, předchozí, další a poslední možnosti spolu s čísly stránek, které nezačínají od 1, ale místo toho obklopují aktuální stránku, na které se uživatel nachází (takže pokud se například uživatel dívá na stránku 10, zobrazí se třeba čísla stránek 8, 9, 10, 11 a 12).

Systém bude dostatečně flexibilní, aby umožňoval nastavit počet viditelných čísel stránek v globální proměnné.

Systém bude zacházet s tlačítky číslování stránek úplně vlevo a nejvíce vpravo, což znamená, že aktivují změnu rozsahu zobrazených čísel stránek. Pokud se například zobrazí čísla stránek 8, 9, 10, 11 a 12 a uživatel klikne na 8, rozsah zobrazených čísel stránek se změní na 6, 7, 8, 9 a 10. A tam je podobný posun doprava, pokud vybrali 12.

Přidání stránkování polí do modelu

Nechte otevřít základní řešení vyhledávací stránky.

  1. Otevřete soubor modelu SearchData.cs.

  2. Přidejte globální proměnné pro podporu stránkování. V MVC jsou globální proměnné deklarovány ve své vlastní statické třídě. ResultsPerPage nastaví počet výsledků na stránku. MaxPageRange určuje počet viditelných čísel stránek v zobrazení. PageRangeDelta určuje, o kolik stránek se má posunout doleva nebo doprava, když je vybráno číslo stránky nejvíce vlevo nebo nejvíce vpravo. Toto druhé číslo je obvykle přibližně polovina hodnoty MaxPageRange. Do oboru názvů přidejte následující kód.

    public static class GlobalVariables
    {
        public static int ResultsPerPage
        {
            get
            {
                return 3;
            }
        }
        public static int MaxPageRange
        {
            get
            {
                return 5;
            }
        }
    
        public static int PageRangeDelta
        {
            get
            {
                return 2;
            }
        }
    }
    

    Tip

    Pokud tento projekt spouštíte na zařízení s menší obrazovkou, například na přenosném počítači, zvažte změnu možnosti ResultsPerPage na 2.

  3. Přidejte vlastnosti stránkování searchData třídy za vlastnost searchText .

    // The current page being displayed.
    public int currentPage { get; set; }
    
    // The total number of pages of results.
    public int pageCount { get; set; }
    
    // The left-most page number to display.
    public int leftMostPage { get; set; }
    
    // The number of page numbers to display - which can be less than MaxPageRange towards the end of the results.
    public int pageRange { get; set; }
    
    // Used when page numbers, or next or prev buttons, have been selected.
    public string paging { get; set; }
    

Přidání tabulky možností stránkování do zobrazení

  1. Otevřete soubor index.cshtml a přímo před uzavírací <značku /body> přidejte následující kód. Tento nový kód představuje tabulku možností stránkování: první, předchozí, 1, 2, 3, 4, 5, další, poslední.

    @if (Model != null && Model.pageCount > 1)
    {
    // If there is more than one page of results, show the paging buttons.
    <table>
        <tr>
            <td>
                @if (Model.currentPage > 0)
                {
                    <p class="pageButton">
                        @Html.ActionLink("|<", "Page", "Home", new { paging = "0" }, null)
                    </p>
                }
                else
                {
                    <p class="pageButtonDisabled">|&lt;</p>
                }
            </td>
    
            <td>
                @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>
                    @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>
                @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>
                @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>
    }
    

    K přehlednému zarovnání používáme tabulku HTML. Veškerá akce však pochází z @Html.ActionLink příkazů, z nichž každý volá kontroler s novým modelem vytvořeným s různými položkami do vlastnosti stránkování , kterou jsme přidali dříve.

    Možnosti první a poslední stránky neodesílají řetězce jako "první" a "poslední", ale místo toho odesílají správná čísla stránek.

  2. Přidejte třídy stránkování do seznamu stylů HTML v souboru hotels.css. Třída pageSelected slouží k identifikaci aktuální stránky (použitím tučného formátu na číslo stránky) v seznamu čísel stránek.

    .pageButton {
        border: none;
        color: darkblue;
        font-weight: normal;
        width: 50px;
    }
    
    .pageSelected {
        border: none;
        color: black;
        font-weight: bold;
        width: 50px;
    }
    
    .pageButtonDisabled {
        border: none;
        color: lightgray;
        font-weight: bold;
        width: 50px;
    }
    

Přidání akce Stránka do kontroleru

  1. Otevřete soubor HomeController.cs a přidejte akci PageAsync . Tato akce reaguje na některou z vybraných možností stránky.

    public async Task<ActionResult> PageAsync(SearchData model)
    {
        try
        {
            int page;
    
            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 search text and search for the data for the new page.
            model.searchText = TempData["searchfor"].ToString();
    
            await RunQueryAsync(model, page, leftMostPage);
    
            // Ensure Temp data is stored for next call, as TempData only stores for one call.
            TempData["page"] = (object)page;
            TempData["searchfor"] = model.searchText;
            TempData["leftMostPage"] = model.leftMostPage;
        }
    
        catch
        {
            return View("Error", new ErrorViewModel { RequestId = "2" });
        }
        return View("Index", model);
    }
    

    Metoda RunQueryAsync teď zobrazí chybu syntaxe kvůli třetímu parametru, ke kterému se trochu dostaneme.

    Poznámka

    Volání TempData ukládají hodnotu ( objekt) v dočasném úložišti, i když toto úložiště se uchovává pouze pro jedno volání. Pokud něco uložíme do dočasných dat, bude to k dispozici pro další volání akce kontroleru, ale po tom volání určitě zmizí. Vzhledem k této krátké životnosti ukládáme hledaný text a vlastnosti stránkování zpět do dočasného úložiště při každém volání PageAsync.

  2. Aktualizujte akci Index(model) tak, aby se ukládaly dočasné proměnné a aby se do volání RunQueryAsync přidal parametr stránky úplně vlevo.

    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);
    
            // Ensure temporary data is stored for the next call.
            TempData["page"] = 0;
            TempData["leftMostPage"] = 0;
            TempData["searchfor"] = model.searchText;
        }
    
        catch
        {
            return View("Error", new ErrorViewModel { RequestId = "1" });
        }
        return View(model);
    }
    
  3. Metoda RunQueryAsync , která byla představena v předchozí lekci, potřebuje úpravu, aby se vyřešila chyba syntaxe. Pole Skip, Size a IncludeTotalCount třídy SearchOptions používáme k vyžádání pouze jedné stránky výsledků, počínaje nastavením Přeskočit . Potřebujeme také vypočítat proměnné stránkování pro naše zobrazení. Celou metodu nahraďte následujícím kódem.

    private async Task<ActionResult> RunQueryAsync(SearchData model, int page, int leftMostPage)
    {
        InitSearch();
    
        var options = new SearchOptions
        {
            // 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
        };
    
        // Add fields to include in the search results.
        options.Select.Add("HotelName");
        options.Select.Add("Description");
    
        // 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);
    
        return View("Index", model);
    }
    
  4. Nakonec proveďte malou změnu zobrazení. Proměnná resultList.Results.TotalCount teď bude obsahovat počet výsledků vrácených na jedné stránce (v našem příkladu 3), nikoli celkový počet. Protože jsme nastavili IncludeTotalCount na true, proměnná resultList.TotalCount teď obsahuje celkový počet výsledků. Vyhledejte tedy, kde se v zobrazení zobrazuje počet výsledků, a změňte ho na následující kód.

    // 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++)
    {
        // Display the hotel name and description.
        @Html.TextAreaFor(m => results[i].Document.HotelName, new { @class = "box1" })
        @Html.TextArea($"desc{1}", results[i].Document.Description, new { @class = "box2" })
    }
    

    Poznámka

    Při nastavení IncludeTotalCount na hodnotu true dochází k menšímu výkonu, protože tento součet je potřeba vypočítat pomocí Azure Cognitive Search. U složitých sad dat se zobrazí upozornění, že vrácená hodnota je aproximace. Vzhledem k tomu, že korpus vyhledávání hotelu je malý, bude přesný.

Kompilace a spuštění aplikace

Teď vyberte Spustit bez ladění (nebo stiskněte klávesu F5).

  1. Vyhledejte řetězec, který vrací velké množství výsledků (například "wifi"). Můžete výsledky procházet úhledně?

    Číslování stránkování prostřednictvím výsledků fondu

  2. Zkuste kliknout na čísla stránek nejvíce vpravo a později nejvíce vlevo. Upraví se čísla stránek odpovídajícím způsobem tak, aby se zarovnat na střed stránky, na které se nacházíte?

  3. Jsou možnosti "první" a "poslední" užitečné? Některé komerční vyhledávací weby používají tyto možnosti, jiné ne.

  4. Přejděte na poslední stránku výsledků. Poslední stránka je jediná stránka, která může obsahovat méně než výsledky ResultsPerPage .

    Prozkoumání poslední stránky

  5. Zadejte "město" a klikněte na hledat. Pokud jsou výsledky menší než jedna stránka, nezobrazí se žádné možnosti stránkování.

    Hledání

Uložte tento projekt a pokračujte k další části, kde najdete alternativní formu stránkování.

Rozšíření aplikace pomocí nekonečného posouvání

Nekonečné posouvání se aktivuje, když uživatel posune svislý posuvník na poslední zobrazené výsledky. V tomto případě se provede volání vyhledávací služby pro další stránku výsledků. Pokud nejsou k dispozici žádné další výsledky, nic se nevrátí a svislý posuvník se nezmění. Pokud jsou k dispozici další výsledky, připojí se k aktuální stránce a posuvník se změní, aby se zobrazilo, že jsou k dispozici další výsledky.

Důležité je si uvědomit, že aktuální stránka není nahrazena, ale spíše rozšířena, aby se zobrazily další výsledky. Uživatel se může vždy posunout zpět k prvním výsledkům hledání.

Abychom mohli implementovat nekonečné posouvání, začněme s projektem před tím, než byl přidán některý z prvků posouvání čísel stránek. Na GitHubu se jedná o řešení FirstAzureSearchApp .

Přidání stránkování polí do modelu

  1. Nejprve přidejte vlastnost stránkování do třídy SearchData (v souboru modelu SearchData.cs).

    // Record if the next page is requested.
    public string paging { get; set; }
    

    Tato proměnná je řetězec, který obsahuje "další", pokud má být odeslána další stránka výsledků, nebo má hodnotu null pro první stránku hledání.

  2. Do stejného souboru a v rámci oboru názvů přidejte globální třídu proměnných s jednou vlastností. V MVC jsou globální proměnné deklarovány ve své vlastní statické třídě. ResultsPerPage nastaví počet výsledků na stránku.

    public static class GlobalVariables
    {
        public static int ResultsPerPage
        {
            get
            {
                return 3;
            }
        }
    }
    

Přidání svislého posuvníku do zobrazení

  1. Vyhledejte část souboru index.cshtml, která zobrazuje výsledky (začíná @if (Model != null)).

  2. Část nahraďte následujícím kódem. Nový <oddíl div> je kolem oblasti, která by měla být posouvatelná, a podobně přidá atribut overflow-y a volání funkce onscroll s názvem "scrolled().

    @if (Model != null)
    {
        // Show the result count.
        <p class="sampleText">
            @Model.resultList.TotalCount Results
        </p>
    
        var results = Model.resultList.GetResults().ToList();
    
        <div id="myDiv" style="width: 800px; height: 450px; overflow-y: scroll;" onscroll="scrolled()">
    
            <!-- Show the hotel data. -->
            @for (var i = 0; i < results.Count; i++)
            {
                // Display the hotel name and description.
                @Html.TextAreaFor(m => results[i].Document.HotelName, new { @class = "box1" })
                @Html.TextArea($"desc{i}", results[i].Document.Description, new { @class = "box2" })
            }
    
  3. Přímo pod smyčku za <značku /div> přidejte posunovanou funkci.

    <script>
        function scrolled() {
            if (myDiv.offsetHeight + myDiv.scrollTop >= myDiv.scrollHeight) {
                $.getJSON("/Home/NextAsync", function (data) {
                    var div = document.getElementById('myDiv');
    
                    // Append the returned data to the current list of hotels.
                    for (var i = 0; i < data.length; i += 2) {
                        div.innerHTML += '\n<textarea class="box1">' + data[i] + '</textarea>';
                        div.innerHTML += '\n<textarea class="box2">' + data[i + 1] + '</textarea>';
                    }
                });
            }
        }
    </script>
    

    Příkaz if ve výše uvedeném skriptu testuje, jestli se uživatel posunoval do dolní části svislého posuvníku. Pokud ano, zavolá se na řadič Domů akce s názvem NextAsync. Kontroler nepotřebuje žádné další informace, vrátí další stránku dat. Tato data se pak naformátují pomocí identických stylů HTML jako původní stránka. Pokud se nevrátí žádné výsledky, nic se nepřidá a všechno zůstane beze všeho.

Zpracování akce Další

Kontroleru je potřeba odeslat jenom tři akce: první spuštění aplikace, které volá Index(), první hledání uživatelem, které volá Index(model), a následné volání dalších výsledků prostřednictvím next(model).

  1. Otevřete soubor domácího kontroleru a odstraňte metodu RunQueryAsync z původního kurzu.

  2. Akci Index(model) nahraďte následujícím kódem. Nyní zpracovává stránkovací pole, pokud je null nebo je nastaveno na "další", a zpracovává volání Azure Cognitive Search.

    public async Task<ActionResult> Index(SearchData model)
    {
        try
        {
            InitSearch();
    
            int page;
    
            if (model.paging != null && model.paging == "next")
            {
                // Increment the page.
                page = (int)TempData["page"] + 1;
    
                // Recover the search text.
                model.searchText = TempData["searchfor"].ToString();
            }
            else
            {
                // First call. Check for valid text input.
                if (model.searchText == null)
                {
                    model.searchText = "";
                }
                page = 0;
            }
    
            // Setup the search parameters.
            var options = new SearchOptions
            {
                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
            };
    
            // Specify which fields to include in results.
            options.Select.Add("HotelName");
            options.Select.Add("Description");
    
            // 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);               
    
            // Ensure TempData is stored for the next call.
            TempData["page"] = page;
            TempData["searchfor"] = model.searchText;
        }
        catch
        {
            return View("Error", new ErrorViewModel { RequestId = "1" });
        }
    
        return View("Index", model);
    }
    

    Podobně jako u metody číslování stránkování používáme nastavení vyhledávání Skip a Size k vyžádání pouze potřebných dat.

  3. Přidejte akci NextAsync (Další synchronizace ) do domovského ovladače. Všimněte si, jak vrátí seznam, přičemž každý hotel přidá do seznamu dva prvky: název hotelu a popis hotelu. Tento formát je nastaven tak, aby odpovídal použití vrácených dat v zobrazení posunovanou funkcí.

    public async Task<ActionResult> NextAsync(SearchData model)
    {
        // Set the next page setting, and call the Index(model) action.
        model.paging = "next";
        await Index(model).ConfigureAwait(false);
    
        // Create an empty list.
        var nextHotels = new List<string>();
    
        // Add a hotel name, then description, to the list.
        await foreach (var searchResult in model.resultList.GetResultsAsync())
        {
            nextHotels.Add(searchResult.Document.HotelName);
            nextHotels.Add(searchResult.Document.Description);
        }
    
        // Rather than return a view, return the list of data.
        return new JsonResult(nextHotels);
    }
    
  4. Pokud se u řetězce> seznamu< zobrazí syntaktická chyba, přidejte do hlavy souboru kontroleru následující direktivu using.

    using System.Collections.Generic;
    

Kompilace a spuštění projektu

Teď vyberte Spustit bez ladění (nebo stiskněte klávesu F5).

  1. Zadejte termín, který poskytne spoustu výsledků (například "fond"), a otestujte svislý posuvník. Aktivuje se nová stránka výsledků?

    Nekonečné posouvání výsledků

    Tip

    Aby se na první stránce zobrazil posuvník, musí první stránka výsledků mírně překročit výšku oblasti, ve které se zobrazují. V našem příkladu má .box1 výšku 30 pixelů, .box2 má výšku 100 pixelů a dolní okraj 24 pixelů. Každá položka tedy používá 154 pixelů. Tři položky zaberou 3 x 154 = 462 pixelů. Chcete-li zajistit, aby se zobrazil svislý posuvník, musí být nastavena výška oblasti zobrazení, která je menší než 462 pixelů, a dokonce i 461 funguje. K tomuto problému dochází pouze na první stránce, po které se určitě zobrazí posuvník. Řádek, který se má aktualizovat, je: <div id="myDiv" style="width: 800px; height: 450px; overflow-y: scroll;" onscroll="scrolled()">.

  2. Posuňte se dolů až na konec výsledků. Všimněte si, že všechny informace jsou teď na stránce s jedním zobrazením. Můžete se posunout až na začátek, aniž byste aktivovala volání serveru.

Sofistikovanější systémy nekonečného posouvání můžou k aktivaci načítání nové stránky výsledků použít kolečko myši nebo podobný jiný mechanismus. V těchto kurzech nebudeme pokračovat nekonečné posouvání, ale má k tomu určité kouzlo, protože se vyhýbá dalším kliknutím myší, a možná budete chtít prozkoumat další možnosti dále.

Shrnutí

Vezměte v úvahu následující poznatky z tohoto projektu:

  • Číslování stránkování je užitečné pro hledání, kde je pořadí výsledků poněkud libovolné, což znamená, že na pozdějších stránkách může být něco zajímavého pro uživatele.
  • Nekonečné posouvání je užitečné, pokud je pořadí výsledků obzvláště důležité. Například pokud jsou výsledky seřazené ve vzdálenosti od centra cílového města.
  • Číslování stránkování umožňuje lepší navigaci. Uživatel si například může vzpomenout, že zajímavý výsledek byl na stránce 6, zatímco v nekonečném posouvání neexistuje žádný takový jednoduchý odkaz.
  • Nekonečné posouvání je snadné– posouvání nahoru a dolů bez čísel stránek, na které se dá kliknout.
  • Klíčovou funkcí nekonečného posouvání je, že se výsledky připojí k existující stránce a nenahrazují tuto stránku, což je efektivní.
  • Dočasné úložiště se zachová jenom pro jedno volání a je potřeba ho resetovat, aby přečkal další volání.

Další kroky

Stránkování je pro vyhledávání zásadní. Když je stránkování dobře probírané, dalším krokem je další vylepšení uživatelského prostředí přidáním hledání s předstihem.