教程:使用 .NET SDK 为搜索结果添加分页功能Tutorial: Add paging to search results using the .NET SDK

了解如何实现这两个不同的分页系统,其中第一个基于页码,第二个基于无限滚动。Learn how to implement two different paging systems, the first based on page numbers and the second on infinite scrolling. 这两个分页系统都得到广泛使用,请根据要用于结果的用户体验选择合适的系统。Both systems of paging are widely used, and selecting the right one depends on the user experience you would like with the results. 本教程将分页系统构建到在 C# 教程:创建第一个应用 - Azure 认知搜索教程中创建的项目。This tutorial builds the paging systems into the project created in the C# Tutorial: Create your first app - Azure Cognitive Search tutorial.

在本教程中,你将了解如何执行以下操作:In this tutorial, you learn how to:

  • 使用带编号的分页来扩展应用Extend your app with numbered paging
  • 使用无限滚动来扩展应用Extend your app with infinite scrolling

先决条件Prerequisites

要完成本教程,需要:To complete this tutorial, you need to:

启动并运行 C# 教程:创建第一个应用 - Azure 认知搜索项目。Have the C# Tutorial: Create your first app - Azure Cognitive Search project up and running. 此项目可以是你自己的版本,也可从 GitHub 安装:创建第一个应用This project can either be your own version, or install it from GitHub: Create first app.

使用带编号的分页来扩展应用Extend your app with numbered paging

带编号的分页是主 Internet 搜索引擎和大部分其他搜索网站选定的分页系统。Numbered paging is the paging system of choice of the main internet search engines and most other search websites. 通常除了一系列实际页码,带编号的分页还包含“上一页”和“下一页”选项。Numbered paging typically includes a "next" and "previous" option in addition to a range of actual page numbers. 可能还会提供“第一页”和“最后一页”选项。Also a "first page" and "last page" option might also be available. 通过这些选项,用户肯定能控制基于页面的结果导航。These options certainly give a user control over navigating through page-based results.

我们将添加一个系统,它包含“第一页”、“上一页”、“下一页”和“最后一页”选项,还包含不从 1 开始而是与用户所在的当前页面相邻的页码(因此,例如如果用户正在查找第 10 页,则可能会显示页码 8、9、10、11 和 12)。We will add a system that includes first, previous, next, and last options, along with page numbers that do not start from 1, but instead surround the current page the user is on (so, for example, if the user is looking at page 10, perhaps page numbers 8, 9, 10, 11, and 12 are displayed).

该系统将足够灵活,能在全局变量中设置可见页码的编号。The system will be flexible enough to allow the number of visible page numbers to be set in a global variable.

该系统会将最右侧和最右侧的页码视为特殊值,这表示它们将触发对所示页码范围的更改。The system will treat the left-most and right-most page number buttons as special, meaning they will trigger changing the range of page numbers displayed. 例如,如果显示页码 8、9、10、11 和 12,而用户单击页码 8,则显示的页码范围更改为 6、7、8、9 和 10。For example, if page numbers 8, 9, 10, 11 and 12 are displayed, and the user clicks on 8, then the range of page numbers displayed changes to 6, 7, 8, 9, and 10. 如果用户选择页码 12,也按类似方式向右移。And there is a similar shift to the right if they selected 12.

向模型添加分页字段Add paging fields to the model

打开基本搜索页面解决方案。Have the basic search page solution open.

  1. 打开 SearchData.cs 模型文件。Open the SearchData.cs model file.

  2. 首先添加一些全局变量。First add some global variables. 在 MVC 中,全局变量是在其自己的静态类中声明的。In MVC, global variables are declared in their own static class. ResultsPerPage 会设置每一页的结果数 。ResultsPerPage sets the number of results per page. MaxPageRange 确定视图上可见页码的编号 。MaxPageRange determines the number of visible page numbers on the view. PageRangeDelta 决定在选中最右侧或最左侧的页码时,页面范围应向右或向左偏移多少页 。PageRangeDelta determines how many pages left or right the page range should be shifted, when the left-most or right-most page number is selected. 通常,后面这个数字大约是 MaxPageRange 的 1/2 。Typically this latter number is around half of MaxPageRange. 将以下代码添加到命名空间。Add the following code into the namespace.

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

    提示

    如果在屏幕更小的设备(例如笔记本电脑)上运行此项目,请考虑将 ResultsPerPage 更改为 2 。If you are running this project on a device with a smaller screen, such as a laptop, then consider changing ResultsPerPage to 2.

  3. 将分页属性添加到 SearchData 类,例如添加到 searchText 属性的后面 。Add paging properties to the SearchData class, say, after the searchText property.

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

将分页选项表添加到视图Add a table of paging options to the view

  1. 打开 index.cshtml 文件,再添加以下代码,使其紧跟在结尾 </body> 标记的前面。Open the index.cshtml file, and add the following code right before the closing </body> tag. 这个新代码会显示分页选项表,其中包含“第一页”、“上一页”、1、2、3、4、5、“下一页”和“最后一页”。This new code presents a table of paging options: first, previous, 1, 2, 3, 4, 5, next, last.

    @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("<", "Page", "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(), "Page", "Home", new { paging = @pn }, null)
                        </p>
                    }
                </td>
            }
    
            <td>
                @if (Model.currentPage < Model.pageCount - 1)
                {
                    <p class="pageButton">
                        @Html.ActionLink(">", "Page", "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(">|", "Page", "Home", new { paging = Model.pageCount - 1 }, null)
                    </p>
                }
                else
                {
                    <p class="pageButtonDisabled">&gt;|</p>
                }
            </td>
        </tr>
    </table>
    }
    

    我们使用 HTML 表来保持内容井然有序。We use an HTML table to align things neatly. 但是,所有操作都来自 @Html.ActionLink 语句,每个语句使用通过不同条目创建的新模型将控制器调用到之前添加的 paging 属性中 。However all the action comes from the @Html.ActionLink statements, each calling the controller with a new model created with different entries to the paging property we added earlier.

    “第一页”和“最后一页”选项不会发送“first”和“last”之类的字符串,而是发送正确的页码。The first and last page options do not send strings such as "first" and "last", but instead send the correct page numbers.

  2. 将一些分页类添加到 hotels.css 文件中的 HTML 样式列表。Add some paging classes to the list of HTML styles in the hotels.css file. 存在 pageSelected 类来确定用户当前在页码列表中查看的页面(通过将页码加粗显示) 。The pageSelected class is there to identify the page the user is currently viewing (by turning the number bold) in the list of page numbers.

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

将 Page 操作添加到控制器Add a Page action to the controller

  1. 打开 HomeController.cs 文件并添加 Page 操作 。Open the HomeController.cs file, and add the Page action. 此操作会响应所选的任何页码选项。This action responds to any of the page options selected.

        public async Task<ActionResult> Page(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 stored 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);
        }
    

    由于第三个参数(我们将稍作介绍),RunQueryAsync 方法现将显示一个语法错误 。The RunQueryAsync method will now show a syntax error, because of the third parameter, which we will come to in a bit.

    备注

    TempData 调用会在临时存储中存储一个值(一个对象),虽然此存储仅保留一个调用的时长 。The TempData calls store a value (an object) in temporary storage, though this storage persists for only one call. 如果在临时数据中存储了一些内容,则可在下一次调用控制器操作时使用此内容,但内容大多数将在下次调用后明确消失!If we store something in temporary data, it will be available for the next call to a controller action, but will most definitely be gone by the call after that! 由于保留期很短,因此我们会在每次对 Page 的调用中,将搜索文本和分页属性重新存储到临时存储 。Because of this short lifespan, we store the search text and paging properties back in temporary storage each and every call to Page.

  2. 需要更新 Index(model) 操作来存储临时变量,还要向 RunQueryAsync 调用添加最左侧页面参数 。The Index(model) action needs updated to store the temporary variables, and to add the left-most page parameter to the RunQueryAsync call.

        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. RunQueryAsync 方法需要大幅更新 。The RunQueryAsync method needs updated significantly. 我们使用 SearchParameters 类的 Skip、Top 和 IncludeTotalResultCount 字段来请求仅相当于一页内容的结果,在 Skip 设置开始 。We use the Skip, Top, and IncludeTotalResultCount fields of the SearchParameters class to request only one page worth of results, starting at the Skip setting. 我们还需要为视图计算分页变量。We also need to calculate the paging variables for our view. 将整个方法替换为以下代码。Replace the entire method with the following code.

        private async Task<ActionResult> RunQueryAsync(SearchData model, int page, int leftMostPage)
        {
            InitSearch();
    
            var parameters = new SearchParameters
            {
                   // Enter Hotel property names into this list so only these values will be returned.
                   // If Select is empty, all values will be returned, which can be inefficient.
                   Select = new[] { "HotelName", "Description" },
                   SearchMode = SearchMode.All,
    
                   // Skip past results that have already been returned.
                   Skip = page * GlobalVariables.ResultsPerPage,
    
                   // Take only the next page worth of results.
                   Top = GlobalVariables.ResultsPerPage,
    
                   // Include the total number of results.
                   IncludeTotalResultCount = true,
               };
    
            // For efficiency, the search call should be asynchronous, so use SearchAsync rather than Search.
            model.resultList = await _indexClient.Documents.SearchAsync<Hotel>(model.searchText, parameters);
    
            // This variable communicates the total number of pages to the view.
            model.pageCount = ( (int)model.resultList.Count + 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. 最后,需要对视图进行小幅更改。Finally, we need to make a small change to the view. resultsList.Results.Count 变量现将包含在一个页面中返回的结果数(在本例中为 3),而不是总数 。The variable resultsList.Results.Count will now contain the number of results returned in one page (3 in our example), not the total number. 我们将 IncludeTotalResultCount 设置为 true,因此 resultsList.Count 变量现包含结果总数 。Because we set the IncludeTotalResultCount to true, the variable resultsList.Count now contains the total number of results. 所以,要找到结果数在视图中显示的位置,并将其更改为以下代码。So locate where the number of results is displayed in the view, and change it to the following code.

            // Show the result count.
            <p class="sampleText">
                @Html.DisplayFor(m => m.resultList.Count) Results
            </p>
    

    备注

    通过将 IncludeTotalResultCount 更改为 true,会影响性能,而通常影响得不多,原因是 Azure 认知搜索需要计算这一总数 。There is a performance hit, though not usually much of one, by setting IncludeTotalResultCount to true, as this total needs to be calculated by Azure Cognitive Search. 使用复杂数据集时,会出现警告显示返回的值是一个近似值 。With complex data sets there is a warning that the value returned is an approximation. 对于酒店数据而言,该值很准确。For our hotel data, it will be accurate.

编译并运行应用Compile and run the app

现在,选择“启动但不调试”(或按 F5 键) 。Now select Start Without Debugging (or press the F5 key).

  1. 搜索某个将返回多个结果的文本(例如“wifi”)。Search on some text that will give plenty of results (such as "wifi"). 能否通过页面熟练地浏览结果?Can you page neatly through the results?

    带编号的分页显示“泳池”结果

  2. 请尝试单击最右侧页码,然后单击最左侧页码。Try clicking on the right-most, and later, left-most page numbers. 页码是否适当调整,将你所在的页面居中显示?Do the page numbers adjust appropriately to center the page you are on?

  3. “第一页”和“最后一页”选项是否有用?Are the "first" and "last" options useful? 一些常用的 Web 搜索会使用这些选项,而一些不会。Some popular web searches use these options, and others do not.

  4. 请转到结果的最后一页。Go to the last page of results. 最后一页是唯一一个所含结果数可能少于 ResultsPerPage 结果数的页面 。The last page is the only page that may contain less than ResultsPerPage results.

    检查“wifi”的最后一页

  5. 键入“城镇”,然后单击“搜索”。Type in "town", and click search. 如果结果数不到一页,则不显示分页选项。No paging options are displayed if there are less than one page worth of results.

    搜索“城镇”

现在,保存此项目并尝试此分页形式的替代方法。Now save off this project and let's try an alternative to this form of paging.

使用无限滚动来扩展应用Extend your app with infinite scrolling

当用户将垂直滚动条滚动到正在显示的结果的最后,会触发无限滚动。Infinite scrolling is triggered when a user scrolls a vertical scroll bar to the last of the results being displayed. 在此情况下,会针对结果的下一页调用服务器。In this event, a call to the server is made for the next page of results. 如果没有更多结果,则不返回任何内容,且垂直滚动条保持不变。If there are no more results, nothing is returned and the vertical scroll bar does not change. 如有更多结果,这些结果会追加到当前页面上,而滚动条会更改,显示还有更多结果可用。If there are more results, they are appended to the current page, and the scroll bar changes to show that more results are available.

这里重要的一点是,不会替换正在显示的页面,而是新结果追加到该页面上。The important point here is that the page being displayed is not replaced, but appended to with the new results. 用户可始终滚回到搜索的第一批结果。A user can always scroll back up to the first results of the search.

要实现无限滚动,首先要在添加任何页码滚动元素之前处理项目。To implement infinite scrolling, let's start with the project before any of the page number scrolling elements were added. 因此,如果需要此滚动,请从 GitHub 再创建一个基本搜索页的副本:创建第一个应用So, if you need to, make another copy of the basic search page from GitHub: Create first app.

向模型添加分页字段Add paging fields to the model

  1. 首先,向 SearchData.cs 模型文件中的 SearchData 类添加 paging 属性 。First, add a paging property to the SearchData class (in the SearchData.cs model file).

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

    此变量是一个字符串;如果应发送下一页结果,或者这些结果对搜索的第一页而言为 null,则该字符串包含“next”。This variable is a string, which holds "next" if the next page of results should be sent, or be null for the first page of a search.

  2. 在同一文件且在命名空间中,添加具有一个属性的全局变量类。In the same file, and within the namespace, add a global variable class with one property. 在 MVC 中,全局变量是在其自己的静态类中声明的。In MVC, global variables are declared in their own static class. ResultsPerPage 会设置每一页的结果数 。ResultsPerPage sets the number of results per page.

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

将垂直滚动条添加到视图Add a vertical scroll bar to the view

  1. 找到 index.cshtml 文件中显示结果的部分(它以 @if (Model != null) 开头) 。Locate the section of the index.cshtml file that displays the results (it starts with the @if (Model != null)).

  2. 将此部分替换为以下代码。Replace the section with the code below. 新的 <div> 部分是围绕应可滚动的区域的,并添加了 overflow-y 属性和对名为“scrolled()”等的 onscroll 函数的调用 。The new <div> section is around the area that should be scrollable, and adds both an overflow-y attribute and a call to an onscroll function called "scrolled()", like so.

        @if (Model != null)
        {
            // Show the result count.
            <p class="sampleText">
                @Html.DisplayFor(m => m.resultList.Count) Results
            </p>
    
            <div id="myDiv" style="width: 800px; height: 450px; overflow-y: scroll;" onscroll="scrolled()">
    
                <!-- Show the hotel data. -->
                @for (var i = 0; i < Model.resultList.Results.Count; i++)
                {
                    // Display the hotel name and description.
                    @Html.TextAreaFor(m => Model.resultList.Results[i].Document.HotelName, new { @class = "box1" })
                    @Html.TextArea($"desc{i}", Model.resultList.Results[i].Document.Description, new { @class = "box2" })
                }
            </div>
        }
    
  3. 在循环的正下文,在 </div> 标记的后面添加 scrolled 函数 。Directly underneath the loop, after the </div> tag, add the scrolled function.

        <script>
                function scrolled() {
                    if (myDiv.offsetHeight + myDiv.scrollTop >= myDiv.scrollHeight) {
                        $.getJSON("/Home/Next", 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>
    

    上述脚本中的 if 语句会进行测试,查看用户是否已滚动到垂直滚动条的底部 。The if statement in the script above tests to see if the user has scrolled to the bottom of the vertical scroll bar. 如果已滚动到底部,则向名为 Next 的操作进行对主控制器的调用 。If they have, a call to the Home controller is made to an action called Next. 无需控制器提供任何其他信息,它将返回数据的下一页。No other information is needed by the controller, it will return the next page of data. 此数据随后按原始页面相同的 HTML 样式设置格式。This data is then formatted using identical HTML styles as the original page. 如果未返回任何结果,则不追加任何内容且原内容保持不变。If no results are returned, nothing is appended and things stay as they are.

处理 Next 操作Handle the Next action

只需要向控制器发送下列三个操作:应用的第一次运行(调用 Index())、用户的第一次搜索(调用 Index(model)),然后是通过 Next(model) 进行后续调用来查找更多结果 。There are only three actions that need to be sent to the controller: the first running of the app, which calls Index(), the first search by the user, which calls Index(model), and then the subsequent calls for more results via Next(model).

  1. 打开主控制器文件,从原始脚本中删除 RunQueryAsync 方法 。Open the home controller file and delete the RunQueryAsync method from the original tutorial.

  2. 将 Index(model) 操作替换为以下代码 。Replace the Index(model) action with the following code. 它现可处理“分页”字段(为 null 时),或者设置为“下一步”并处理对 Azure 认知搜索的调用 。It now handles the paging field when it is null, or set to "next", and handles the call to 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 parameters = new SearchParameters
                {
                    // Enter Hotel property names into this list so only these values will be returned.
                    // If Select is empty, all values will be returned, which can be inefficient.
                    Select = new[] { "HotelName", "Description" },
                    SearchMode = SearchMode.All,
    
                    // Skip past results that have already been returned.
                    Skip = page * GlobalVariables.ResultsPerPage,
    
                    // Take only the next page worth of results.
                    Top = GlobalVariables.ResultsPerPage,
    
                    // Include the total number of results.
                    IncludeTotalResultCount = true,
                };
    
                // For efficiency, the search call should be asynchronous, so use SearchAsync rather than Search.
                model.resultList = await _indexClient.Documents.SearchAsync<Hotel>(model.searchText, parameters);
    
                // 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);
        }
    

    与带编号的分页方法类似,我们使用 Skip 和 Top 搜索设置来仅请求返回我们所需的数据 。Similar to the numbered paging method, we use the Skip and Top search settings to request just the data we need is returned.

  3. 向主控制器添加 Next 操作 。Add the Next action to the home controller. 请注意它返回列表的方式:每家酒店会向列表添加两个元素 - 酒店名称和酒店说明。Note how it returns a list, each hotel adding two elements to the list: a hotel name and a hotel description. 设置此格式,使其与 scrolled 函数对视图中返回的数据的使用相一致 。This format is set to match the scrolled function's use of the returned data in the view.

        public async Task<ActionResult> Next(SearchData model)
        {
            // Set the next page setting, and call the Index(model) action.
            model.paging = "next";
            await Index(model);
    
            // Create an empty list.
            var nextHotels = new List<string>();
    
            // Add a hotel name, then description, to the list.
            for (int n = 0; n < model.resultList.Results.Count; n++)
            {
                nextHotels.Add(model.resultList.Results[n].Document.HotelName);
                nextHotels.Add(model.resultList.Results[n].Document.Description);
            }
    
            // Rather than return a view, return the list of data.
            return new JsonResult(nextHotels);
        }
    
  4. 如果在 List<string> 上收到语法错误,请向控制器文件的开头添加以下 using 指令 。If you are getting a syntax error on List<string>, then add the following using directive to the head of the controller file.

    using System.Collections.Generic;
    

编译并运行项目Compile and run your project

现在,选择“启动但不调试”(或按 F5 键) 。Now select Start Without Debugging (or press the F5 key).

  1. 输入一个会返回更多结果的词(例如“泳池”),然后测试垂直滚动条。Enter a term that will give plenty of results (such as "pool") and then test the vertical scroll bar. 它会触发新的一页结果吗?Does it trigger a new page of results?

    无限滚动查看“泳池”结果

    提示

    要确保第一页上显示滚动条,第一页结果必须稍微超过当前显示这些结果的区域的高度。To ensure that a scroll bar appears on the first page, the first page of results must slightly exceed the height of the area they are being displayed in. 在我们的示例中,.box1 具有 30 像素的高度,而 .box2 具有 100 像素的高度,其下边距为 24 像素 。In our example .box1 has a height of 30 pixels, .box2 has a height of 100 pixels and a bottom margin of 24 pixels. 因此,每个条目都使用 154 像素。So each entry uses 154 pixels. 三个条目占用的像素将为 3 x 154 = 462 个。Three entries will take up 3 x 154 = 462 pixels. 要确保显示垂直滚动条,必须将显示区域的高度设置为小于 462 像素(甚至可使用 461 像素)。To ensure that a vertical scroll bar appears, a height to the display area must be set that is smaller than 462 pixels, even 461 works. 此问题只出现在第一页上,后续页面肯定会显示滚动条。This issue only occurs on the first page, after that a scroll bar is sure to appear. 要更新的行为 <div id="myDiv" style="width:800px; height:450px; overflow-y: scroll;" onscroll="scrolled()">The line to update is: <div id="myDiv" style="width: 800px; height: 450px; overflow-y: scroll;" onscroll="scrolled()">.

  2. 向下滚动一直到结果的底部。Scroll down all the way to the bottom of the results. 请注意所有信息现在一个视图页面上的显示方式。Notice how all information is now on the one view page. 可一直滚动回顶部,而不触发任何服务器调用。You can scroll all the way back to the top without triggering any server calls.

更复杂的无限滚动系统可能会使用鼠标滚轮或其他类似机制来触发新页结果的加载。More sophisticated infinite scrolling systems might use the mouse wheel, or similar other mechanism, to trigger the loading of a new page of results. 在这些教程中,我们将不再进一步执行无限滚动,但它有一种魅力,它能免去额外的鼠标点击,而你可能想要进一步探讨其他选项!We will not be taking infinite scrolling any further in these tutorials, but it has a certain charm to it as it avoids extra mouse clicks, and you might want to investigate other options further!

要点Takeaways

请考虑此项目中的以下要点:Consider the following takeaways from this project:

  • 带编号的分页非常适合结果顺序有些任意的搜索,这意味着后续页面上可能有些用户感兴趣的内容。Numbered paging is good for searches where the order of the results is somewhat arbitrary, meaning there may well be something of interest to your users on the later pages.
  • 如果结果顺序非常重要,则无限滚动很适用。Infinite scrolling is good when the order of results is particularly important. 例如,如果结果是按到目标城市中心的距离进行排序的。For example, if the results are ordered on the distance from the center of a destination city.
  • 通过带编号的分页,可进行一些更棒的导航。Numbered paging allows for some better navigation. 例如,用户可能记得第 6 页上有一个有趣的结果,但是无限滚动中没有此类简单的参考。For example, a user can remember that an interesting result was on page 6, whereas no such easy reference exists in infinite scrolling.
  • 无限滚动有一种简单的魔力,你可上下滚动而不必单击页码。Infinite scrolling has an easy appeal, scrolling up and down with no fussy page numbers to click on.
  • 无限滚动的一项重要功能是,结果会追加到现有页面上而不是替代该页面,这很有效。A key feature of infinite scrolling is that results are appended to an existing page, not replacing that page, which is efficient.
  • 临时存储仅保存一个调用的时长,需要重置以经受住额外调用。Temporary storage persists for only one call, and needs to be reset to survive additional calls.

后续步骤Next steps

分页是 Internet 搜索的基础。Paging is fundamental to internet searches. 在详细了解分页后,下一步是添加预先输入搜索,进一步改进用户体验。With paging well covered, the next step is to improve the user experience further, by adding type-ahead searches.