├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── stale.yml ├── .gitignore ├── DefaultPagingControlStyles.png ├── LICENSE.md ├── README.md ├── X.PagedList.sln ├── examples ├── Example.DAL │ ├── Animal.cs │ ├── DatabaseContext.cs │ ├── Example.DAL.csproj │ └── User.cs └── Example.Website │ ├── Controllers │ ├── Bootstrap41Controller.cs │ ├── EFController.cs │ └── HomeController.cs │ ├── Example.Website.csproj │ ├── Names.txt │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Views │ ├── Bootstrap41 │ │ └── Index.cshtml │ ├── EF │ │ └── Index.cshtml │ ├── Home │ │ └── Index.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── Paging │ │ │ ├── _Pager.cshtml │ │ │ └── _Pager_85.cshtml │ │ ├── _Layout-41.cshtml │ │ └── _Layout.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── data │ └── example.sqlite │ └── wwwroot │ ├── css │ ├── PagedList.css │ └── site.css │ ├── favicon.ico │ └── lib │ └── jquery-ajax-unobtrusive │ ├── jquery.unobtrusive-ajax.js │ └── jquery.unobtrusive-ajax.min.js ├── src ├── Directory.Build.props ├── X.PagedList.EF │ ├── PagedListExtensions.cs │ ├── README.md │ ├── X.PagedList.EF.csproj │ └── xpagedlist.snk ├── X.PagedList.Mvc.Core │ ├── AjaxOptions.cs │ ├── Fluent │ │ ├── HtmlPagerBuilder.cs │ │ ├── HtmlPagerExtensions.cs │ │ └── IHtmlPagerBuilder.cs │ ├── GoToFormRenderOptions.cs │ ├── HtmlAttribute.cs │ ├── HtmlHelper.cs │ ├── HtmlHelperExtension.cs │ ├── InsertionMode.cs │ ├── ItemSliceAndTotalPosition.cs │ ├── PagedListDisplayMode.cs │ ├── PagedListRenderOptions.cs │ ├── README.md │ ├── TagBuilderExtensions.cs │ ├── TagBuilderFactory.cs │ ├── X.PagedList.Mvc.Core.csproj │ └── xpagedlist.snk └── X.PagedList │ ├── BasePagedList.cs │ ├── Extensions │ └── PagedListExtensions.cs │ ├── IPagedList.cs │ ├── OrderedPagedList.cs │ ├── PagedList.cs │ ├── PagedListMetaData.cs │ ├── StaticPagedList.cs │ ├── X.PagedList.csproj │ └── xpagedlist.snk ├── tests └── X.PagedList.Tests │ ├── Blog.cs │ ├── DbContext.cs │ ├── DbSet.cs │ ├── IDbAsyncEnumerable.cs │ ├── IDbAsyncEnumerator.cs │ ├── IDbAsyncQueryProvider.cs │ ├── PagedListExample.cs │ ├── PagedListFacts.cs │ ├── PagedListTheories.cs │ ├── SplitAndPartitionFacts.cs │ ├── StaticPagedListExample.cs │ ├── StaticPagedListFacts.cs │ ├── TestContext.cs │ ├── TestDbAsyncQueryProvider.cs │ └── X.PagedList.Tests.csproj └── x-pagedlist.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [a-gubskiy] 2 | buy_me_a_coffee: g.andrew 3 | custom: ["http://andrew.gubskiy.com/donate"] 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 30 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: true 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.userosscache 15 | *.sln.docstates 16 | 17 | # User-specific files (MonoDevelop/Xamarin Studio) 18 | *.userprefs 19 | 20 | # Visual Studio 2015 cache/options directory 21 | .vs/ 22 | 23 | # DNX 24 | project.lock.json 25 | artifacts/ 26 | 27 | # Build results 28 | [Dd]ebug/ 29 | [Dd]ebugPublic/ 30 | [Rr]elease/ 31 | [Rr]eleases/ 32 | x64/ 33 | x86/ 34 | build/ 35 | bld/ 36 | [Bb]in/ 37 | [Oo]bj/ 38 | *_i.c 39 | *_p.c 40 | *.ilk 41 | *.meta 42 | *.obj 43 | *.pch 44 | *.pdb 45 | *.pgc 46 | *.pgd 47 | *.rsp 48 | *.sbr 49 | *.tlb 50 | *.tli 51 | *.tlh 52 | *.tmp 53 | *.log 54 | *.vspscc 55 | *.vssscc 56 | .builds 57 | 58 | # Visual C++ cache files 59 | ipch/ 60 | *.aps 61 | *.ncb 62 | *.opensdf 63 | *.sdf 64 | 65 | # Visual Studio profiler 66 | *.psess 67 | *.vsp 68 | *.vspx 69 | 70 | # Guidance Automation Toolkit 71 | *.gpState 72 | 73 | # ReSharper is a .NET coding add-in 74 | _ReSharper* 75 | 76 | # NCrunch 77 | *.ncrunch* 78 | .*crunch*.local.xml 79 | 80 | # Installshield output folder 81 | [Ee]xpress 82 | 83 | # DocProject is a documentation generator add-in 84 | DocProject/buildhelp/ 85 | DocProject/Help/*.HxT 86 | DocProject/Help/*.HxC 87 | DocProject/Help/*.hhc 88 | DocProject/Help/*.hhk 89 | DocProject/Help/*.hhp 90 | DocProject/Help/Html2 91 | DocProject/Help/html 92 | 93 | # Click-Once directory 94 | publish 95 | 96 | # Publish Web Output 97 | *.Publish.xml 98 | 99 | # NuGet Packages Directory 100 | packages 101 | 102 | # Windows Azure Build Output 103 | csx 104 | *.build.csdef 105 | 106 | # Windows Store app package directory 107 | AppPackages/ 108 | 109 | # Others 110 | ClientBin/ 111 | [Ss]tyle[Cc]op.* 112 | ~$* 113 | *~ 114 | *.dbmdl 115 | *.dbproj.schemaview 116 | *.pfx 117 | *.publishsettings 118 | node_modules/ 119 | orleans.codegen.cs 120 | TestResults 121 | [Tt]est[Rr]esult* 122 | *.Cache 123 | 124 | # Backup & report files from converting an old project file to a newer 125 | # Visual Studio version. Backup files are not needed, because we have git ;-) 126 | _UpgradeReport_Files/ 127 | Backup*/ 128 | UpgradeLog*.XML 129 | 130 | TestResult.xml 131 | Tests.VisualState.xml 132 | 133 | # ReSharper is a .NET coding add-in 134 | _ReSharper*/ 135 | *.[Rr]e[Ss]harper 136 | *.DotSettings.user 137 | 138 | 139 | *.bak 140 | *.manifest 141 | *.cache 142 | *.suo 143 | *.orig 144 | *.user 145 | *.vspscc 146 | LastBuild.log 147 | .svn 148 | _ReSharper* 149 | .DS_Store 150 | src/PagedList.Mvc/Content/ 151 | 152 | .idea/ 153 | -------------------------------------------------------------------------------- /DefaultPagingControlStyles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dncuug/X.PagedList/f68e3e69d25bcbc66a1af4ecf08a52d6c2e1a141/DefaultPagingControlStyles.png -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2017 Troy Goode, Andrew Gubskiy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # X.PagedList 2 | 3 | [![NuGet Version](http://img.shields.io/nuget/v/X.PagedList.svg?style=flat)](https://www.nuget.org/packages/X.PagedList/) 4 | [![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/andrew_gubskiy.svg?style=social&label=Follow%20me!)](https://x.com/intent/user?screen_name=andrew_gubskiy) 5 | 6 | 7 | ## What is this? 8 | This is a fork of [Troy's](https://github.com/troygoode) project PagedList. The main difference is that X.PagedList is a library that supports the 9 | modern .NET platform — you can use it everywhere the .NET platform is supported. 10 | 11 | This library enables you to easily take an IEnumerable/IQueryable, chop it up into "pages", and grab a specific "page" 12 | by an index. PagedList.Mvc allows you to take that "page" and display a pager control that has links like "Previous", 13 | "Next", etc. 14 | 15 | ## How to use 16 | You can find all information about how to use X.PagedList libraries in [Wiki](https://github.com/dncuug/X.PagedList/wiki) 17 | 18 | ## Get a digital subscription for project news 19 | [Subscribe](https://x.com/intent/user?screen_name=andrew_gubskiy) to my X to keep up-to-date with project news and receive announcements. 20 | -------------------------------------------------------------------------------- /X.PagedList.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.10.35013.160 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BDDADD09-D112-418E-8469-BC762EC09936}" 7 | ProjectSection(SolutionItems) = preProject 8 | src\Directory.Build.props = src\Directory.Build.props 9 | EndProjectSection 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0170B742-C624-4C22-9DE1-2A93CF9C12D6}" 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{309A8FC8-4784-4D8D-903F-BD54EBB0F1D7}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "X.PagedList", "src\X.PagedList\X.PagedList.csproj", "{49F86DF5-728F-4F22-986E-38DFC9C2432A}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "X.PagedList.Tests", "tests\X.PagedList.Tests\X.PagedList.Tests.csproj", "{C78B1316-1EF9-45C3-A3FD-9A131BA3DD62}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.Website", "examples\Example.Website\Example.Website.csproj", "{288F5726-904F-48B8-8363-EA1A22D331D1}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.DAL", "examples\Example.DAL\Example.DAL.csproj", "{AD16A8D1-EAF0-4947-BCEC-A8B423B2F117}" 22 | EndProject 23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X.PagedList.Mvc.Core", "src\X.PagedList.Mvc.Core\X.PagedList.Mvc.Core.csproj", "{3B840A44-3150-4BB5-83DA-9B81D1FCB6BE}" 24 | EndProject 25 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X.PagedList.EF", "src\X.PagedList.EF\X.PagedList.EF.csproj", "{B72170CA-12E0-46E5-821C-FCE7E6F79736}" 26 | EndProject 27 | Global 28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 29 | Debug|Any CPU = Debug|Any CPU 30 | Release|Any CPU = Release|Any CPU 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {49F86DF5-728F-4F22-986E-38DFC9C2432A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {49F86DF5-728F-4F22-986E-38DFC9C2432A}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {49F86DF5-728F-4F22-986E-38DFC9C2432A}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {49F86DF5-728F-4F22-986E-38DFC9C2432A}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {C78B1316-1EF9-45C3-A3FD-9A131BA3DD62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {C78B1316-1EF9-45C3-A3FD-9A131BA3DD62}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {C78B1316-1EF9-45C3-A3FD-9A131BA3DD62}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {C78B1316-1EF9-45C3-A3FD-9A131BA3DD62}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {288F5726-904F-48B8-8363-EA1A22D331D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {288F5726-904F-48B8-8363-EA1A22D331D1}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {288F5726-904F-48B8-8363-EA1A22D331D1}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {288F5726-904F-48B8-8363-EA1A22D331D1}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {AD16A8D1-EAF0-4947-BCEC-A8B423B2F117}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {AD16A8D1-EAF0-4947-BCEC-A8B423B2F117}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {AD16A8D1-EAF0-4947-BCEC-A8B423B2F117}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {AD16A8D1-EAF0-4947-BCEC-A8B423B2F117}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {3B840A44-3150-4BB5-83DA-9B81D1FCB6BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {3B840A44-3150-4BB5-83DA-9B81D1FCB6BE}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {3B840A44-3150-4BB5-83DA-9B81D1FCB6BE}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {3B840A44-3150-4BB5-83DA-9B81D1FCB6BE}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {B72170CA-12E0-46E5-821C-FCE7E6F79736}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {B72170CA-12E0-46E5-821C-FCE7E6F79736}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {B72170CA-12E0-46E5-821C-FCE7E6F79736}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {B72170CA-12E0-46E5-821C-FCE7E6F79736}.Release|Any CPU.Build.0 = Release|Any CPU 57 | EndGlobalSection 58 | GlobalSection(SolutionProperties) = preSolution 59 | HideSolutionNode = FALSE 60 | EndGlobalSection 61 | GlobalSection(NestedProjects) = preSolution 62 | {49F86DF5-728F-4F22-986E-38DFC9C2432A} = {BDDADD09-D112-418E-8469-BC762EC09936} 63 | {C78B1316-1EF9-45C3-A3FD-9A131BA3DD62} = {0170B742-C624-4C22-9DE1-2A93CF9C12D6} 64 | {288F5726-904F-48B8-8363-EA1A22D331D1} = {309A8FC8-4784-4D8D-903F-BD54EBB0F1D7} 65 | {AD16A8D1-EAF0-4947-BCEC-A8B423B2F117} = {309A8FC8-4784-4D8D-903F-BD54EBB0F1D7} 66 | {3B840A44-3150-4BB5-83DA-9B81D1FCB6BE} = {BDDADD09-D112-418E-8469-BC762EC09936} 67 | {B72170CA-12E0-46E5-821C-FCE7E6F79736} = {BDDADD09-D112-418E-8469-BC762EC09936} 68 | EndGlobalSection 69 | GlobalSection(ExtensibilityGlobals) = postSolution 70 | SolutionGuid = {1A82D446-6F26-48B2-8085-DFA5F87453FC} 71 | EndGlobalSection 72 | EndGlobal 73 | -------------------------------------------------------------------------------- /examples/Example.DAL/Animal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Example.DAL; 5 | 6 | public partial class Animal 7 | { 8 | public long Id { get; set; } 9 | 10 | public string Name { get; set; } = null!; 11 | } 12 | -------------------------------------------------------------------------------- /examples/Example.DAL/DatabaseContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace Example.DAL; 6 | 7 | public partial class DatabaseContext : DbContext 8 | { 9 | public DatabaseContext() 10 | { 11 | } 12 | 13 | public DatabaseContext(DbContextOptions options) 14 | : base(options) 15 | { 16 | } 17 | 18 | public virtual DbSet Animals { get; set; } 19 | 20 | public virtual DbSet Users { get; set; } 21 | 22 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 23 | { 24 | // Only configure the DbContext if it hasn't been configured yet 25 | if (!optionsBuilder.IsConfigured) 26 | { 27 | // DbContext is not yet configured, configure it now 28 | optionsBuilder.UseSqlite("Data Source="); 29 | } 30 | } 31 | 32 | protected override void OnModelCreating(ModelBuilder modelBuilder) 33 | { 34 | modelBuilder.Entity(entity => 35 | { 36 | entity.ToTable("Animal"); 37 | }); 38 | 39 | modelBuilder.Entity(entity => 40 | { 41 | entity.ToTable("User"); 42 | }); 43 | 44 | OnModelCreatingPartial(modelBuilder); 45 | } 46 | 47 | partial void OnModelCreatingPartial(ModelBuilder modelBuilder); 48 | } 49 | -------------------------------------------------------------------------------- /examples/Example.DAL/Example.DAL.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | default 8 | 9 | 10 | 11 | 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | all 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/Example.DAL/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Example.DAL; 5 | 6 | public partial class User 7 | { 8 | public long Id { get; set; } 9 | 10 | public string Name { get; set; } = null!; 11 | } 12 | -------------------------------------------------------------------------------- /examples/Example.Website/Controllers/Bootstrap41Controller.cs: -------------------------------------------------------------------------------- 1 | using Example.DAL; 2 | 3 | namespace Example.Website.Controllers; 4 | 5 | public class Bootstrap41Controller : HomeController 6 | { 7 | public Bootstrap41Controller(DatabaseContext databaseContext) 8 | : base(databaseContext) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /examples/Example.Website/Controllers/EFController.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Example.DAL; 4 | using Microsoft.AspNetCore.Mvc; 5 | using X.PagedList.EF; 6 | 7 | namespace Example.Website.Controllers; 8 | 9 | public class EFController : Controller 10 | { 11 | private const int PageSize = 10; 12 | 13 | private readonly DatabaseContext _databaseContext; 14 | 15 | public EFController(DatabaseContext databaseContext) 16 | { 17 | _databaseContext = databaseContext; 18 | } 19 | 20 | public async Task Index(int page = 1) 21 | { 22 | // return a 404 if user browses to before the first page 23 | if (page < 1) 24 | { 25 | return NotFound(); 26 | } 27 | 28 | var records = await _databaseContext.Animals 29 | .Select(o => o.Name) 30 | .ToPagedListAsync(page, PageSize); 31 | 32 | // return a 404 if user browses to pages beyond last page. special case first page if no items exist 33 | if (records.PageNumber != 1 && page > records.PageCount) 34 | { 35 | return NotFound(); 36 | } 37 | 38 | ViewBag.Names = records; 39 | 40 | return View(); 41 | } 42 | } -------------------------------------------------------------------------------- /examples/Example.Website/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Example.DAL; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System.Collections.Generic; 4 | using X.PagedList; 5 | using X.PagedList.Extensions; 6 | 7 | namespace Example.Website.Controllers; 8 | 9 | public class HomeController : Controller 10 | { 11 | private const int PageSize = 10; 12 | 13 | private readonly DatabaseContext _databaseContext; 14 | 15 | public HomeController(DatabaseContext databaseContext) 16 | { 17 | _databaseContext = databaseContext; 18 | } 19 | 20 | public IActionResult Index(int page = 1) 21 | { 22 | ViewBag.Names = GetPagedNames(page); 23 | return View(); 24 | } 25 | 26 | public IActionResult AjaxIndex(int page = 1) 27 | { 28 | var listPaged = GetPagedNames(page); 29 | ViewBag.Names = listPaged; 30 | return View(); 31 | } 32 | 33 | public IActionResult Error() 34 | { 35 | return View(); 36 | } 37 | 38 | private IPagedList GetPagedNames(int? page) 39 | { 40 | // return a 404 if user browses to before the first page 41 | if (page.HasValue && page < 1) 42 | { 43 | return null; 44 | } 45 | 46 | // retrieve list from database/whereverand 47 | var listUnPaged = GetStuffFromFile(); 48 | 49 | // page the list 50 | 51 | var listPaged = listUnPaged.ToPagedList(page ?? 1, PageSize); 52 | 53 | // return a 404 if user browses to pages beyond last page. special case first page if no items exist 54 | if (listPaged.PageNumber != 1 && page.HasValue && page > listPaged.PageCount) 55 | { 56 | return null; 57 | } 58 | 59 | return listPaged; 60 | } 61 | 62 | /// 63 | /// In this case we return array of string, but in most DB situations you'll want to return IQueryable 64 | /// 65 | /// 66 | private IEnumerable GetStuffFromFile() 67 | { 68 | var sampleData = System.IO.File.ReadAllText("Names.txt"); 69 | 70 | return sampleData.Split('\n'); 71 | } 72 | } -------------------------------------------------------------------------------- /examples/Example.Website/Example.Website.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | default 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/Example.Website/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Example.DAL; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | 8 | namespace Example.Website; 9 | 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | var builder = WebApplication.CreateBuilder(args); 15 | builder.Services.AddControllersWithViews(); 16 | 17 | builder.Services.AddDbContext(options => 18 | { 19 | var connectionString = $"Data Source={Path.Combine(builder.Environment.ContentRootPath, "data", "example.sqlite")}"; 20 | 21 | options.UseSqlite(connectionString); 22 | }); 23 | 24 | var app = builder.Build(); 25 | 26 | if (builder.Environment.IsDevelopment()) 27 | { 28 | app.UseDeveloperExceptionPage(); 29 | } 30 | else 31 | { 32 | app.UseExceptionHandler("/Home/Error"); 33 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 34 | app.UseHsts(); 35 | } 36 | 37 | app.UseStaticFiles(); 38 | 39 | app.UseRouting(); 40 | 41 | app.UseAuthorization(); 42 | 43 | app.MapControllerRoute( 44 | name: "default", 45 | pattern: "{controller=Home}/{action=Index}/{id?}"); 46 | 47 | app.Run(); 48 | } 49 | } -------------------------------------------------------------------------------- /examples/Example.Website/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55924", 7 | "sslPort": 44326 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "X.PagedList.Mvc.Example.Core": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/Example.Website/Views/Bootstrap41/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/_Layout-41.cshtml"; 3 | ViewBag.Title = "Product Listing"; 4 | } 5 | 6 | @using X.PagedList; 7 | @using X.PagedList.Mvc.Core 8 | @*import this so we can cast our list to IPagedList (only necessary because ViewBag is dynamic)*@ 9 | 10 | 11 | 12 | 13 | 14 |
15 |

List of Products

16 |
    17 | @foreach (var name in ViewBag.Names) 18 | { 19 |
  • @name
  • 20 | } 21 |
22 |
23 | 24 | 25 | @Html.PagedListPager((IPagedList)ViewBag.Names, page => Url.Action("Index", new { page }), new PagedListRenderOptions 26 | { 27 | PageClasses = new string[] { "page-link" }, 28 | UlElementClasses = new string[] { "pagination" }, 29 | LiElementClasses = new string[] { "page-item" }, 30 | DisplayItemSliceAndTotal = true, 31 | }) -------------------------------------------------------------------------------- /examples/Example.Website/Views/EF/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Product Listing"; 3 | var pagedList = (IPagedList)ViewBag.Names; 4 | } 5 | 6 | @using X.PagedList; 7 | @using X.PagedList.Mvc.Core 8 | @using X.PagedList.Mvc.Core.Fluent 9 | @*import this so we can cast our list to IPagedList (only necessary because ViewBag is dynamic)*@ 10 | 11 | 12 | 13 | 14 | 15 | 16 |

List of Products from EF Core

17 |
    18 | @foreach (var name in ViewBag.Names) 19 | { 20 |
  • @name
  • 21 | } 22 |
23 | 24 | 25 | @Html.PagedListPager(pagedList, page => Url.Action("Index", new { page })) 26 | 27 |

Fluent pager

28 | @(Html.Pager(pagedList) 29 | .Url(page => Url.Action("Index", new { page })) 30 | .Build()) 31 | 32 |

Pager for #85

33 | @(Html.Pager(pagedList) 34 | .Url(page => Url.Action("Index", new { page })) 35 | .WithPartialView("Paging/_Pager_85") 36 | .DisplayLinkToFirstPage (PagedListDisplayMode.IfNeeded) 37 | .DisplayLinkToLastPage(PagedListDisplayMode.IfNeeded) 38 | .DisplayLinkToPreviousPage() 39 | .DisplayLinkToNextPage() 40 | .MaximumPageNumbersToDisplay(3) 41 | .Build()) 42 | 43 |

Pager with ItemSliceAndTotalPosition at the end

44 | @Html.PagedListPager(pagedList, 45 | page => Url.Action("Index", new { page }), 46 | new PagedListRenderOptions() 47 | { 48 | DisplayItemSliceAndTotal = true, 49 | ItemSliceAndTotalPosition = ItemSliceAndTotalPosition.End, 50 | FunctionToTransformEachPageLink = (tagBuilder, inner) => 51 | { 52 | // Create tag builder 53 | // var builder = new TagBuilder("div"); 54 | // builder.AppendHtml(inner.ToString(TagRenderMode.Normal)); 55 | // builder.AddCssClass(".ef-example"); 56 | 57 | inner.AddCssClass("ef-core-example"); 58 | tagBuilder.AppendHtml(inner.ToString(TagRenderMode.Normal)); 59 | 60 | return tagBuilder; 61 | } 62 | }) 63 | 64 |

Pager with ItemSliceAndTotalPosition at the beginning

65 | @Html.PagedListPager(pagedList, 66 | page => Url.Action("Index", new { page }), 67 | new PagedListRenderOptions() 68 | { 69 | DisplayItemSliceAndTotal = true, 70 | }) -------------------------------------------------------------------------------- /examples/Example.Website/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Product Listing"; 3 | var pagedList = (IPagedList)ViewBag.Names; 4 | } 5 | 6 | @using X.PagedList; 7 | @using X.PagedList.Mvc.Core 8 | @using X.PagedList.Mvc.Core.Fluent 9 | @*import this so we can cast our list to IPagedList (only necessary because ViewBag is dynamic)*@ 10 | 11 | 12 | 13 | 14 | 15 | 16 |

List of Products

17 |
    18 | @foreach (var name in ViewBag.Names) 19 | { 20 |
  • @name
  • 21 | } 22 |
23 | 24 | 25 | @Html.PagedListPager(pagedList, page => Url.Action("Index", new { page })) 26 | 27 |

Fluent pager

28 | @(Html.Pager(pagedList) 29 | .Url(page => Url.Action("Index", new { page })) 30 | .Build()) 31 | 32 |

Pager for #85

33 | @(Html.Pager(pagedList) 34 | .Url(page => Url.Action("Index", new { page })) 35 | .WithPartialView("Paging/_Pager_85") 36 | .DisplayLinkToFirstPage(PagedListDisplayMode.IfNeeded) 37 | .DisplayLinkToLastPage(PagedListDisplayMode.IfNeeded) 38 | .DisplayLinkToPreviousPage() 39 | .DisplayLinkToNextPage() 40 | .MaximumPageNumbersToDisplay(3) 41 | .Build()) 42 | 43 |

Pager with ItemSliceAndTotalPosition at the end

44 | @Html.PagedListPager(pagedList, 45 | page => Url.Action("Index", 46 | new { page }), 47 | new PagedListRenderOptions() 48 | { 49 | DisplayItemSliceAndTotal = true, 50 | ItemSliceAndTotalPosition = ItemSliceAndTotalPosition.End 51 | }) 52 | 53 |

Pager with ItemSliceAndTotalPosition at the beginning

54 | @Html.PagedListPager(pagedList, 55 | page => Url.Action("Index", 56 | new { page }), 57 | new PagedListRenderOptions() 58 | { 59 | DisplayItemSliceAndTotal = true, 60 | }) -------------------------------------------------------------------------------- /examples/Example.Website/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

7 | 8 |

Development Mode

9 |

10 | Swapping to Development environment will display more detailed information about the error that occurred. 11 |

12 |

13 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 14 |

15 | -------------------------------------------------------------------------------- /examples/Example.Website/Views/Shared/Paging/_Pager.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Text 2 | @using X.PagedList.Mvc.Core 3 | 4 | @model X.PagedList.IPagedList 5 | 6 | @{ 7 | var options = (PagedListRenderOptions)ViewData["Options"]; 8 | var generatePageUrl = (Func)ViewData["GeneratePageUrl"]; 9 | var containerDivClasses = (options.ContainerDivClasses ?? Enumerable.Empty()).Aggregate(new StringBuilder(), (c, n) => c.Append(n).Append(" ")); 10 | var ulElementClasses = (options.UlElementClasses ?? Enumerable.Empty()).Aggregate(new StringBuilder(), (c, n) => c.Append(n).Append(" ")); 11 | 12 | var list = Model; 13 | 14 | //calculate start and end of range of page numbers 15 | var firstPageToDisplay = 1; 16 | var lastPageToDisplay = list.PageCount; 17 | var pageNumbersToDisplay = lastPageToDisplay; 18 | 19 | if (options.MaximumPageNumbersToDisplay.HasValue && list.PageCount > options.MaximumPageNumbersToDisplay) 20 | { 21 | // cannot fit all pages into pager 22 | var maxPageNumbersToDisplay = options.MaximumPageNumbersToDisplay.Value; 23 | 24 | firstPageToDisplay = list.PageNumber - maxPageNumbersToDisplay / 2; 25 | 26 | if (firstPageToDisplay < 1) 27 | { 28 | firstPageToDisplay = 1; 29 | } 30 | 31 | pageNumbersToDisplay = maxPageNumbersToDisplay; 32 | lastPageToDisplay = firstPageToDisplay + pageNumbersToDisplay - 1; 33 | 34 | if (lastPageToDisplay > list.PageCount) 35 | { 36 | firstPageToDisplay = list.PageCount - maxPageNumbersToDisplay + 1; 37 | } 38 | } 39 | } 40 | 41 | @*pagination-container*@ 42 |
43 |
    44 | @*first*@ 45 | @if (options.DisplayLinkToFirstPage == PagedListDisplayMode.Always || (options.DisplayLinkToFirstPage == PagedListDisplayMode.IfNeeded && firstPageToDisplay > 1)) 46 | { 47 | var pageClasses = (options.PageClasses ?? Enumerable.Empty()).Aggregate(new StringBuilder(), (c, n) => c.Append(n).Append(" ")); 48 | const int targetPageNumber = 1; 49 | 50 | if (list.IsFirstPage) 51 | { 52 | //FunctionToTransformEachPageLink 53 | 54 |
  • 55 | @string.Format(options.LinkToFirstPageFormat, targetPageNumber) 56 |
  • 57 | } 58 | else 59 | { 60 | //FunctionToTransformEachPageLink 61 | 62 |
  • 63 | @string.Format(options.LinkToFirstPageFormat, targetPageNumber) 64 |
  • 65 | } 66 | } 67 | 68 | @*previous*@ 69 | @if (options.DisplayLinkToPreviousPage == PagedListDisplayMode.Always || (options.DisplayLinkToPreviousPage == PagedListDisplayMode.IfNeeded && !list.IsFirstPage)) 70 | { 71 | var pageClasses = (options.PageClasses ?? Enumerable.Empty()).Aggregate(new StringBuilder(), (c, n) => c.Append(n).Append(" ")); 72 | var targetPageNumber = list.PageNumber - 1; 73 | 74 | if (!list.HasPreviousPage) 75 | { 76 | //FunctionToTransformEachPageLink 77 | 78 |
  • 79 | @string.Format(options.LinkToPreviousPageFormat, targetPageNumber) 80 |
  • 81 | } 82 | else 83 | { 84 | //FunctionToTransformEachPageLink 85 | 86 |
  • 87 | 88 |
  • 89 | } 90 | } 91 | 92 | @*text*@ 93 | @if (options.DisplayPageCountAndCurrentLocation) 94 | { 95 |
  • 96 | @string.Format(options.PageCountAndCurrentLocationFormat, list.PageNumber, list.PageCount) 97 |
  • 98 | } 99 | 100 | @*text*@ 101 | @if (options.DisplayItemSliceAndTotal) 102 | { 103 |
  • 104 | @string.Format(options.ItemSliceAndTotalFormat, list.FirstItemOnPage, list.LastItemOnPage, list.TotalItemCount) 105 |
  • 106 | } 107 | 108 | @*page*@ 109 | @if (options.DisplayLinkToIndividualPages) 110 | { 111 | //if there are previous page numbers not displayed, show an ellipsis 112 | if (options.DisplayEllipsesWhenNotShowingAllPageNumbers && firstPageToDisplay > 1) 113 | { 114 | var targetPageNumber = firstPageToDisplay - 1; 115 | 116 | if (!list.HasPreviousPage) 117 | { 118 | //FunctionToTransformEachPageLink 119 | 120 |
  • 121 | @Html.Raw(string.Format(options.EllipsesFormat, targetPageNumber)) 122 |
  • 123 | } 124 | else 125 | { 126 | //FunctionToTransformEachPageLink 127 | 128 |
  • 129 | 130 |
  • 131 | } 132 | } 133 | 134 | foreach (var i in Enumerable.Range(firstPageToDisplay, pageNumbersToDisplay)) 135 | { 136 | //show delimiter between page numbers 137 | if (i > firstPageToDisplay && !string.IsNullOrWhiteSpace(options.DelimiterBetweenPageNumbers)) 138 | { 139 |
  • @options.DelimiterBetweenPageNumbers
  • 140 | } 141 | 142 | var pageClasses = (options.PageClasses ?? Enumerable.Empty()).Aggregate(new StringBuilder(), (c, n) => c.Append(n).Append(" ")); 143 | var targetPageNumber = i; 144 | 145 | if (i == list.PageNumber) 146 | { 147 | //FunctionToTransformEachPageLink 148 | 149 |
  • 150 | @string.Format(options.LinkToIndividualPageFormat, targetPageNumber) 151 |
  • 152 | } 153 | else 154 | { 155 | //FunctionToTransformEachPageLink 156 | 157 |
  • 158 | 159 |
  • 160 | } 161 | } 162 | 163 | //if there are subsequent page numbers not displayed, show an ellipsis 164 | if (options.DisplayEllipsesWhenNotShowingAllPageNumbers && (firstPageToDisplay + pageNumbersToDisplay - 1) < list.PageCount) 165 | { 166 | var targetPageNumber = lastPageToDisplay + 1; 167 | 168 | if (!list.HasPreviousPage) 169 | { 170 | //FunctionToTransformEachPageLink 171 | 172 |
  • 173 | @Html.Raw(string.Format(options.EllipsesFormat, targetPageNumber)) 174 |
  • 175 | } 176 | else 177 | { 178 | //FunctionToTransformEachPageLink 179 | 180 |
  • 181 | 182 |
  • 183 | } 184 | } 185 | } 186 | 187 | @*next*@ 188 | @if (options.DisplayLinkToNextPage == PagedListDisplayMode.Always || (options.DisplayLinkToNextPage == PagedListDisplayMode.IfNeeded && !list.IsLastPage)) 189 | { 190 | var pageClasses = (options.PageClasses ?? Enumerable.Empty()).Aggregate(new StringBuilder(), (c, n) => c.Append(n).Append(" ")); 191 | var targetPageNumber = list.PageNumber + 1; 192 | 193 | if (!list.HasNextPage) 194 | { 195 | //FunctionToTransformEachPageLink 196 | 197 |
  • 198 | @string.Format(options.LinkToNextPageFormat, targetPageNumber) 199 |
  • 200 | } 201 | else 202 | { 203 | //FunctionToTransformEachPageLink 204 | 205 |
  • 206 | 207 |
  • 208 | } 209 | } 210 | 211 | @*last*@ 212 | @if (options.DisplayLinkToLastPage == PagedListDisplayMode.Always || (options.DisplayLinkToLastPage == PagedListDisplayMode.IfNeeded && lastPageToDisplay < list.PageCount)) 213 | { 214 | var pageClasses = (options.PageClasses ?? Enumerable.Empty()).Aggregate(new StringBuilder(), (c, n) => c.Append(n).Append(" ")); 215 | var targetPageNumber = list.PageCount; 216 | 217 | if (list.IsLastPage) 218 | { 219 | //FunctionToTransformEachPageLink 220 | 221 |
  • 222 | @string.Format(options.LinkToLastPageFormat, targetPageNumber) 223 |
  • 224 | } 225 | else 226 | { 227 | //FunctionToTransformEachPageLink 228 | 229 |
  • 230 | @string.Format(options.LinkToLastPageFormat, targetPageNumber) 231 |
  • 232 | } 233 | } 234 |
235 |
236 | -------------------------------------------------------------------------------- /examples/Example.Website/Views/Shared/Paging/_Pager_85.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Text 2 | @using X.PagedList.Mvc.Core 3 | 4 | @model X.PagedList.IPagedList 5 | 6 | @{ 7 | var options = (PagedListRenderOptions)ViewData["Options"]; 8 | var generatePageUrl = (Func)ViewData["GeneratePageUrl"]; 9 | var containerDivClasses = (options.ContainerDivClasses ?? Enumerable.Empty()).Aggregate(new StringBuilder(), (c, n) => c.Append(n).Append(" ")); 10 | var ulElementClasses = (options.UlElementClasses ?? Enumerable.Empty()).Aggregate(new StringBuilder(), (c, n) => c.Append(n).Append(" ")); 11 | 12 | var list = Model; 13 | 14 | //calculate start and end of range of page numbers 15 | var firstPageToDisplay = 1; 16 | var lastPageToDisplay = list.PageCount; 17 | var pageNumbersToDisplay = lastPageToDisplay; 18 | 19 | if (options.MaximumPageNumbersToDisplay.HasValue && list.PageCount > options.MaximumPageNumbersToDisplay) 20 | { 21 | // cannot fit all pages into pager 22 | var maxPageNumbersToDisplay = options.MaximumPageNumbersToDisplay.Value; 23 | 24 | firstPageToDisplay = list.PageNumber - maxPageNumbersToDisplay / 2; 25 | 26 | if (firstPageToDisplay < 1) 27 | { 28 | firstPageToDisplay = 1; 29 | } 30 | 31 | pageNumbersToDisplay = maxPageNumbersToDisplay; 32 | lastPageToDisplay = firstPageToDisplay + pageNumbersToDisplay - 1; 33 | 34 | if (lastPageToDisplay > list.PageCount) 35 | { 36 | firstPageToDisplay = list.PageCount - maxPageNumbersToDisplay + 1; 37 | } 38 | } 39 | } 40 | 41 | @*pagination-container*@ 42 |
43 |
    44 | @*previous*@ 45 | @if (options.DisplayLinkToPreviousPage == PagedListDisplayMode.Always || (options.DisplayLinkToPreviousPage == PagedListDisplayMode.IfNeeded && !list.IsFirstPage)) 46 | { 47 | var pageClasses = (options.PageClasses ?? Enumerable.Empty()).Aggregate(new StringBuilder(), (c, n) => c.Append(n).Append(" ")); 48 | var targetPageNumber = list.PageNumber - 1; 49 | 50 | if (!list.HasPreviousPage) 51 | { 52 | //FunctionToTransformEachPageLink 53 | 54 |
  • 55 | 56 |
  • 57 | } 58 | else 59 | { 60 | //FunctionToTransformEachPageLink 61 | 62 |
  • 63 | 64 |
  • 65 | } 66 | } 67 | 68 | @*first*@ 69 | @if (options.DisplayLinkToFirstPage == PagedListDisplayMode.Always || (options.DisplayLinkToFirstPage == PagedListDisplayMode.IfNeeded && firstPageToDisplay > 1)) 70 | { 71 | var pageClasses = (options.PageClasses ?? Enumerable.Empty()).Aggregate(new StringBuilder(), (c, n) => c.Append(n).Append(" ")); 72 | const int targetPageNumber = 1; 73 | 74 | if (list.IsFirstPage) 75 | { 76 | //FunctionToTransformEachPageLink 77 | 78 |
  • 79 | @targetPageNumber 80 |
  • 81 | } 82 | else 83 | { 84 | //FunctionToTransformEachPageLink 85 | 86 |
  • 87 | @targetPageNumber 88 |
  • 89 | } 90 | } 91 | 92 | @*text*@ 93 | @if (options.DisplayPageCountAndCurrentLocation) 94 | { 95 |
  • 96 | @string.Format(options.PageCountAndCurrentLocationFormat, list.PageNumber, list.PageCount) 97 |
  • 98 | } 99 | 100 | @*text*@ 101 | @if (options.DisplayItemSliceAndTotal) 102 | { 103 |
  • 104 | @string.Format(options.ItemSliceAndTotalFormat, list.FirstItemOnPage, list.LastItemOnPage, list.TotalItemCount) 105 |
  • 106 | } 107 | 108 | @*page*@ 109 | @if (options.DisplayLinkToIndividualPages) 110 | { 111 | //if there are previous page numbers not displayed, show an ellipsis 112 | if (options.DisplayEllipsesWhenNotShowingAllPageNumbers && firstPageToDisplay > 1) 113 | { 114 | var targetPageNumber = firstPageToDisplay - 1; 115 | 116 | if (!list.HasPreviousPage) 117 | { 118 | //FunctionToTransformEachPageLink 119 | 120 |
  • 121 | @Html.Raw(string.Format(options.EllipsesFormat, targetPageNumber)) 122 |
  • 123 | } 124 | else 125 | { 126 | //FunctionToTransformEachPageLink 127 | 128 |
  • 129 | 130 |
  • 131 | } 132 | } 133 | 134 | foreach (var i in Enumerable.Range(firstPageToDisplay, pageNumbersToDisplay)) 135 | { 136 | //show delimiter between page numbers 137 | if (i > firstPageToDisplay && !string.IsNullOrWhiteSpace(options.DelimiterBetweenPageNumbers)) 138 | { 139 |
  • @options.DelimiterBetweenPageNumbers
  • 140 | } 141 | 142 | var pageClasses = (options.PageClasses ?? Enumerable.Empty()).Aggregate(new StringBuilder(), (c, n) => c.Append(n).Append(" ")); 143 | var targetPageNumber = i; 144 | 145 | if (i == list.PageNumber) 146 | { 147 | //FunctionToTransformEachPageLink 148 | 149 |
  • 150 | @string.Format(options.LinkToIndividualPageFormat, targetPageNumber) 151 |
  • 152 | } 153 | else 154 | { 155 | //FunctionToTransformEachPageLink 156 | 157 |
  • 158 | 159 |
  • 160 | } 161 | } 162 | 163 | //if there are subsequent page numbers not displayed, show an ellipsis 164 | if (options.DisplayEllipsesWhenNotShowingAllPageNumbers && (firstPageToDisplay + pageNumbersToDisplay - 1) < list.PageCount) 165 | { 166 | var targetPageNumber = lastPageToDisplay + 1; 167 | 168 | if (!list.HasPreviousPage) 169 | { 170 | //FunctionToTransformEachPageLink 171 | 172 |
  • 173 | @Html.Raw(string.Format(options.EllipsesFormat, targetPageNumber)) 174 |
  • 175 | } 176 | else 177 | { 178 | //FunctionToTransformEachPageLink 179 | 180 |
  • 181 | 182 |
  • 183 | } 184 | } 185 | } 186 | 187 | @*last*@ 188 | @if (options.DisplayLinkToLastPage == PagedListDisplayMode.Always || (options.DisplayLinkToLastPage == PagedListDisplayMode.IfNeeded && lastPageToDisplay < list.PageCount)) 189 | { 190 | var pageClasses = (options.PageClasses ?? Enumerable.Empty()).Aggregate(new StringBuilder(), (c, n) => c.Append(n).Append(" ")); 191 | var targetPageNumber = list.PageCount; 192 | 193 | if (list.IsLastPage) 194 | { 195 | //FunctionToTransformEachPageLink 196 | 197 |
  • 198 | @targetPageNumber 199 |
  • 200 | } 201 | else 202 | { 203 | //FunctionToTransformEachPageLink 204 | 205 |
  • 206 | @targetPageNumber 207 |
  • 208 | } 209 | } 210 | 211 | @*next*@ 212 | @if (options.DisplayLinkToNextPage == PagedListDisplayMode.Always || (options.DisplayLinkToNextPage == PagedListDisplayMode.IfNeeded && !list.IsLastPage)) 213 | { 214 | var pageClasses = (options.PageClasses ?? Enumerable.Empty()).Aggregate(new StringBuilder(), (c, n) => c.Append(n).Append(" ")); 215 | var targetPageNumber = list.PageNumber + 1; 216 | 217 | if (!list.HasNextPage) 218 | { 219 | //FunctionToTransformEachPageLink 220 | 221 |
  • 222 | 223 |
  • 224 | } 225 | else 226 | { 227 | //FunctionToTransformEachPageLink 228 | 229 |
  • 230 | 231 |
  • 232 | } 233 | } 234 |
235 |
236 | -------------------------------------------------------------------------------- /examples/Example.Website/Views/Shared/_Layout-41.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - Example.Website 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 37 | 38 |
39 | @RenderBody() 40 |
41 |
42 |

© @DateTime.Now.Year - Example.Website

43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | @RenderSection("Scripts", required: false) 52 | 53 | 54 | -------------------------------------------------------------------------------- /examples/Example.Website/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - Example.Website 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 37 |
38 | @RenderBody() 39 |
40 |
41 |

© @DateTime.Now.Year - Example.Website

42 |
43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | @await RenderSectionAsync("Scripts", required: false) 51 | 52 | 53 | -------------------------------------------------------------------------------- /examples/Example.Website/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 2 | -------------------------------------------------------------------------------- /examples/Example.Website/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /examples/Example.Website/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information", 7 | "Microsoft.EntityFrameworkCore.Database.Command": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/Example.Website/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information", 7 | "Microsoft.EntityFrameworkCore.Database.Command": "Information" 8 | } 9 | }, 10 | "AllowedHosts": "*" 11 | } 12 | -------------------------------------------------------------------------------- /examples/Example.Website/data/example.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dncuug/X.PagedList/f68e3e69d25bcbc66a1af4ecf08a52d6c2e1a141/examples/Example.Website/data/example.sqlite -------------------------------------------------------------------------------- /examples/Example.Website/wwwroot/css/PagedList.css: -------------------------------------------------------------------------------- 1 | .pagination { 2 | display: inline-block; 3 | padding-left: 0; 4 | margin: 20px 0; 5 | border-radius: 4px; 6 | } 7 | 8 | .pagination > li { 9 | display: inline; 10 | } 11 | 12 | .pagination > li > a, 13 | .pagination > li > span { 14 | position: relative; 15 | float: left; 16 | padding: 6px 12px; 17 | margin-left: -1px; 18 | line-height: 1.428571429; 19 | text-decoration: none; 20 | background-color: #ffffff; 21 | border: 1px solid #dddddd; 22 | } 23 | 24 | .pagination > li:first-child > a, 25 | .pagination > li:first-child > span { 26 | margin-left: 0; 27 | border-bottom-left-radius: 4px; 28 | border-top-left-radius: 4px; 29 | } 30 | 31 | .pagination > li:last-child > a, 32 | .pagination > li:last-child > span { 33 | border-top-right-radius: 4px; 34 | border-bottom-right-radius: 4px; 35 | } 36 | 37 | .pagination > li > a:hover, 38 | .pagination > li > span:hover, 39 | .pagination > li > a:focus, 40 | .pagination > li > span:focus { 41 | background-color: #eeeeee; 42 | } 43 | 44 | .pagination > .active > a, 45 | .pagination > .active > span, 46 | .pagination > .active > a:hover, 47 | .pagination > .active > span:hover, 48 | .pagination > .active > a:focus, 49 | .pagination > .active > span:focus { 50 | z-index: 2; 51 | color: #ffffff; 52 | cursor: default; 53 | background-color: #428bca; 54 | border-color: #428bca; 55 | } 56 | 57 | .pagination > .disabled > span, 58 | .pagination > .disabled > a, 59 | .pagination > .disabled > a:hover, 60 | .pagination > .disabled > a:focus { 61 | color: #999999; 62 | cursor: not-allowed; 63 | background-color: #ffffff; 64 | border-color: #dddddd; 65 | } 66 | 67 | .pagination-lg > li > a, 68 | .pagination-lg > li > span { 69 | padding: 10px 16px; 70 | font-size: 18px; 71 | } 72 | 73 | .pagination-lg > li:first-child > a, 74 | .pagination-lg > li:first-child > span { 75 | border-bottom-left-radius: 6px; 76 | border-top-left-radius: 6px; 77 | } 78 | 79 | .pagination-lg > li:last-child > a, 80 | .pagination-lg > li:last-child > span { 81 | border-top-right-radius: 6px; 82 | border-bottom-right-radius: 6px; 83 | } 84 | 85 | .pagination-sm > li > a, 86 | .pagination-sm > li > span { 87 | padding: 5px 10px; 88 | font-size: 12px; 89 | } 90 | 91 | .pagination-sm > li:first-child > a, 92 | .pagination-sm > li:first-child > span { 93 | border-bottom-left-radius: 3px; 94 | border-top-left-radius: 3px; 95 | } 96 | 97 | .pagination-sm > li:last-child > a, 98 | .pagination-sm > li:last-child > span { 99 | border-top-right-radius: 3px; 100 | border-bottom-right-radius: 3px; 101 | } 102 | 103 | .pager { 104 | padding-left: 0; 105 | margin: 20px 0; 106 | text-align: center; 107 | list-style: none; 108 | } 109 | 110 | .pager:before, 111 | .pager:after { 112 | display: table; 113 | content: " "; 114 | } 115 | 116 | .pager:after { 117 | clear: both; 118 | } 119 | 120 | .pager:before, 121 | .pager:after { 122 | display: table; 123 | content: " "; 124 | } 125 | 126 | .pager:after { 127 | clear: both; 128 | } 129 | 130 | .pager li { 131 | display: inline; 132 | } 133 | 134 | .pager li > a, 135 | .pager li > span { 136 | display: inline-block; 137 | padding: 5px 14px; 138 | background-color: #ffffff; 139 | border: 1px solid #dddddd; 140 | border-radius: 15px; 141 | } 142 | 143 | .pager li > a:hover, 144 | .pager li > a:focus { 145 | text-decoration: none; 146 | background-color: #eeeeee; 147 | } 148 | 149 | .pager .next > a, 150 | .pager .next > span { 151 | float: right; 152 | } 153 | 154 | .pager .previous > a, 155 | .pager .previous > span { 156 | float: left; 157 | } 158 | 159 | .pager .disabled > a, 160 | .pager .disabled > a:hover, 161 | .pager .disabled > a:focus, 162 | .pager .disabled > span { 163 | color: #999999; 164 | cursor: not-allowed; 165 | background-color: #ffffff; 166 | } -------------------------------------------------------------------------------- /examples/Example.Website/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Wrapping element */ 7 | /* Set some basic padding to keep content from hitting the edges */ 8 | .body-content { 9 | padding-left: 15px; 10 | padding-right: 15px; 11 | } 12 | 13 | /* Set widths on the form inputs since otherwise they're 100% wide */ 14 | input, 15 | select, 16 | textarea { 17 | max-width: 280px; 18 | } 19 | 20 | /* Carousel */ 21 | .carousel-caption p { 22 | font-size: 20px; 23 | line-height: 1.4; 24 | } 25 | 26 | /* Make .svg files in the carousel display properly in older browsers */ 27 | .carousel-inner .item img[src$=".svg"] { 28 | width: 100%; 29 | } 30 | 31 | /* Hide/rearrange for smaller screens */ 32 | @media screen and (max-width: 767px) { 33 | /* Hide captions */ 34 | .carousel-caption { 35 | display: none; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/Example.Website/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dncuug/X.PagedList/f68e3e69d25bcbc66a1af4ecf08a52d6c2e1a141/examples/Example.Website/wwwroot/favicon.ico -------------------------------------------------------------------------------- /examples/Example.Website/wwwroot/lib/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Microsoft grants you the right to use these script files for the sole 3 | * purpose of either: (i) interacting through your browser with the Microsoft 4 | * website or online service, subject to the applicable licensing or use 5 | * terms; or (ii) using the files as included with a Microsoft product subject 6 | * to that product's license terms. Microsoft reserves all other rights to the 7 | * files not expressly granted by Microsoft, whether by implication, estoppel 8 | * or otherwise. Insofar as a script file is dual licensed under GPL, 9 | * Microsoft neither took the code under GPL nor distributes it thereunder but 10 | * under the terms set out in this paragraph. All notices and licenses 11 | * below are for informational purposes only. 12 | /*! 13 | ** Unobtrusive Ajax support library for jQuery 14 | ** Copyright (C) Microsoft Corporation. All rights reserved. 15 | */ 16 | 17 | /*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */ 18 | /*global window: false, jQuery: false */ 19 | 20 | (function ($) { 21 | var data_click = "unobtrusiveAjaxClick", 22 | data_target = "unobtrusiveAjaxClickTarget", 23 | data_validation = "unobtrusiveValidation"; 24 | 25 | function getFunction(code, argNames) { 26 | var fn = window, parts = (code || "").split("."); 27 | while (fn && parts.length) { 28 | fn = fn[parts.shift()]; 29 | } 30 | if (typeof (fn) === "function") { 31 | return fn; 32 | } 33 | argNames.push(code); 34 | return Function.constructor.apply(null, argNames); 35 | } 36 | 37 | function isMethodProxySafe(method) { 38 | return method === "GET" || method === "POST"; 39 | } 40 | 41 | function asyncOnBeforeSend(xhr, method) { 42 | if (!isMethodProxySafe(method)) { 43 | xhr.setRequestHeader("X-HTTP-Method-Override", method); 44 | } 45 | } 46 | 47 | function asyncOnSuccess(element, data, contentType) { 48 | var mode; 49 | 50 | if (contentType.indexOf("application/x-javascript") !== -1) { // jQuery already executes JavaScript for us 51 | return; 52 | } 53 | 54 | mode = (element.getAttribute("data-ajax-mode") || "").toUpperCase(); 55 | $(element.getAttribute("data-ajax-update")).each(function (i, update) { 56 | var top; 57 | 58 | switch (mode) { 59 | case "BEFORE": 60 | top = update.firstChild; 61 | $("
").html(data).contents().each(function () { 62 | update.insertBefore(this, top); 63 | }); 64 | break; 65 | case "AFTER": 66 | $("
").html(data).contents().each(function () { 67 | update.appendChild(this); 68 | }); 69 | break; 70 | case "REPLACE-WITH": 71 | $(update).replaceWith(data); 72 | break; 73 | default: 74 | $(update).html(data); 75 | break; 76 | } 77 | }); 78 | } 79 | 80 | function asyncRequest(element, options) { 81 | var confirm, loading, method, duration; 82 | 83 | confirm = element.getAttribute("data-ajax-confirm"); 84 | if (confirm && !window.confirm(confirm)) { 85 | return; 86 | } 87 | 88 | loading = $(element.getAttribute("data-ajax-loading")); 89 | duration = parseInt(element.getAttribute("data-ajax-loading-duration"), 10) || 0; 90 | 91 | $.extend(options, { 92 | type: element.getAttribute("data-ajax-method") || undefined, 93 | url: element.getAttribute("data-ajax-url") || undefined, 94 | cache: (element.getAttribute("data-ajax-cache") || "").toLowerCase() === "true", 95 | beforeSend: function (xhr) { 96 | var result; 97 | asyncOnBeforeSend(xhr, method); 98 | result = getFunction(element.getAttribute("data-ajax-begin"), ["xhr"]).apply(element, arguments); 99 | if (result !== false) { 100 | loading.show(duration); 101 | } 102 | return result; 103 | }, 104 | complete: function () { 105 | loading.hide(duration); 106 | getFunction(element.getAttribute("data-ajax-complete"), ["xhr", "status"]).apply(element, arguments); 107 | }, 108 | success: function (data, status, xhr) { 109 | asyncOnSuccess(element, data, xhr.getResponseHeader("Content-Type") || "text/html"); 110 | getFunction(element.getAttribute("data-ajax-success"), ["data", "status", "xhr"]).apply(element, arguments); 111 | }, 112 | error: function () { 113 | getFunction(element.getAttribute("data-ajax-failure"), ["xhr", "status", "error"]).apply(element, arguments); 114 | } 115 | }); 116 | 117 | options.data.push({ name: "X-Requested-With", value: "XMLHttpRequest" }); 118 | 119 | method = options.type.toUpperCase(); 120 | if (!isMethodProxySafe(method)) { 121 | options.type = "POST"; 122 | options.data.push({ name: "X-HTTP-Method-Override", value: method }); 123 | } 124 | 125 | $.ajax(options); 126 | } 127 | 128 | function validate(form) { 129 | var validationInfo = $(form).data(data_validation); 130 | return !validationInfo || !validationInfo.validate || validationInfo.validate(); 131 | } 132 | 133 | $(document).on("click", "a[data-ajax=true]", function (evt) { 134 | evt.preventDefault(); 135 | asyncRequest(this, { 136 | url: this.href, 137 | type: "GET", 138 | data: [] 139 | }); 140 | }); 141 | 142 | $(document).on("click", "form[data-ajax=true] input[type=image]", function (evt) { 143 | var name = evt.target.name, 144 | target = $(evt.target), 145 | form = $(target.parents("form")[0]), 146 | offset = target.offset(); 147 | 148 | form.data(data_click, [ 149 | { name: name + ".x", value: Math.round(evt.pageX - offset.left) }, 150 | { name: name + ".y", value: Math.round(evt.pageY - offset.top) } 151 | ]); 152 | 153 | setTimeout(function () { 154 | form.removeData(data_click); 155 | }, 0); 156 | }); 157 | 158 | $(document).on("click", "form[data-ajax=true] :submit", function (evt) { 159 | var name = evt.currentTarget.name, 160 | target = $(evt.target), 161 | form = $(target.parents("form")[0]); 162 | 163 | form.data(data_click, name ? [{ name: name, value: evt.currentTarget.value }] : []); 164 | form.data(data_target, target); 165 | 166 | setTimeout(function () { 167 | form.removeData(data_click); 168 | form.removeData(data_target); 169 | }, 0); 170 | }); 171 | 172 | $(document).on("submit", "form[data-ajax=true]", function (evt) { 173 | var clickInfo = $(this).data(data_click) || [], 174 | clickTarget = $(this).data(data_target), 175 | isCancel = clickTarget && clickTarget.hasClass("cancel"); 176 | evt.preventDefault(); 177 | if (!isCancel && !validate(this)) { 178 | return; 179 | } 180 | asyncRequest(this, { 181 | url: this.action, 182 | type: this.method || "GET", 183 | data: clickInfo.concat($(this).serializeArray()) 184 | }); 185 | }); 186 | }(jQuery)); 187 | -------------------------------------------------------------------------------- /examples/Example.Website/wwwroot/lib/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /* 16 | ** Unobtrusive Ajax support library for jQuery 17 | ** Copyright (C) Microsoft Corporation. All rights reserved. 18 | */ 19 | (function(a){var b="unobtrusiveAjaxClick",d="unobtrusiveAjaxClickTarget",h="unobtrusiveValidation";function c(d,b){var a=window,c=(d||"").split(".");while(a&&c.length)a=a[c.shift()];if(typeof a==="function")return a;b.push(d);return Function.constructor.apply(null,b)}function e(a){return a==="GET"||a==="POST"}function g(b,a){!e(a)&&b.setRequestHeader("X-HTTP-Method-Override",a)}function i(c,b,e){var d;if(e.indexOf("application/x-javascript")!==-1)return;d=(c.getAttribute("data-ajax-mode")||"").toUpperCase();a(c.getAttribute("data-ajax-update")).each(function(f,c){var e;switch(d){case"BEFORE":e=c.firstChild;a("
").html(b).contents().each(function(){c.insertBefore(this,e)});break;case"AFTER":a("
").html(b).contents().each(function(){c.appendChild(this)});break;case"REPLACE-WITH":a(c).replaceWith(b);break;default:a(c).html(b)}})}function f(b,d){var j,k,f,h;j=b.getAttribute("data-ajax-confirm");if(j&&!window.confirm(j))return;k=a(b.getAttribute("data-ajax-loading"));h=parseInt(b.getAttribute("data-ajax-loading-duration"),10)||0;a.extend(d,{type:b.getAttribute("data-ajax-method")||undefined,url:b.getAttribute("data-ajax-url")||undefined,cache:(b.getAttribute("data-ajax-cache")||"").toLowerCase()==="true",beforeSend:function(d){var a;g(d,f);a=c(b.getAttribute("data-ajax-begin"),["xhr"]).apply(b,arguments);a!==false&&k.show(h);return a},complete:function(){k.hide(h);c(b.getAttribute("data-ajax-complete"),["xhr","status"]).apply(b,arguments)},success:function(a,e,d){i(b,a,d.getResponseHeader("Content-Type")||"text/html");c(b.getAttribute("data-ajax-success"),["data","status","xhr"]).apply(b,arguments)},error:function(){c(b.getAttribute("data-ajax-failure"),["xhr","status","error"]).apply(b,arguments)}});d.data.push({name:"X-Requested-With",value:"XMLHttpRequest"});f=d.type.toUpperCase();if(!e(f)){d.type="POST";d.data.push({name:"X-HTTP-Method-Override",value:f})}a.ajax(d)}function j(c){var b=a(c).data(h);return!b||!b.validate||b.validate()}a(document).on("click","a[data-ajax=true]",function(a){a.preventDefault();f(this,{url:this.href,type:"GET",data:[]})});a(document).on("click","form[data-ajax=true] input[type=image]",function(c){var g=c.target.name,e=a(c.target),f=a(e.parents("form")[0]),d=e.offset();f.data(b,[{name:g+".x",value:Math.round(c.pageX-d.left)},{name:g+".y",value:Math.round(c.pageY-d.top)}]);setTimeout(function(){f.removeData(b)},0)});a(document).on("click","form[data-ajax=true] :submit",function(e){var g=e.currentTarget.name,f=a(e.target),c=a(f.parents("form")[0]);c.data(b,g?[{name:g,value:e.currentTarget.value}]:[]);c.data(d,f);setTimeout(function(){c.removeData(b);c.removeData(d)},0)});a(document).on("submit","form[data-ajax=true]",function(h){var e=a(this).data(b)||[],c=a(this).data(d),g=c&&c.hasClass("cancel");h.preventDefault();if(!g&&!j(this))return;f(this,{url:this.action,type:this.method||"GET",data:e.concat(a(this).serializeArray())})})})(jQuery); 20 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | True 5 | 6 | README.md 7 | LICENSE.md 8 | 9 | Troy Goode, Andrew Gubskiy 10 | Andrew Gubskiy © 2024 11 | Ukrainian .NET Developer Community 12 | 13 | 10.5.3 14 | 10.5.3 15 | 10.5.3 16 | 10.5.3 17 | 18 | git 19 | https://github.com/dncuug/X.PagedList.git 20 | https://andrew.gubskiy.com/open-source 21 | 22 | true 23 | xpagedlist.snk 24 | 25 | enable 26 | 27 | x-pagedlist.png 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/X.PagedList.EF/PagedListExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using JetBrains.Annotations; 7 | using Microsoft.EntityFrameworkCore; 8 | 9 | namespace X.PagedList.EF; 10 | 11 | /// 12 | /// EntityFramework extension methods designed to simplify the creation of instances of . 13 | /// 14 | [PublicAPI] 15 | public static class PagedListExtensions 16 | { 17 | /// 18 | /// Async creates a subset of this collection of objects that can be individually accessed by index and 19 | /// containing metadata about the collection of objects the subset was created from. 20 | /// 21 | /// The type of object the collection should contain. 22 | /// The collection of objects to be divided into subsets. If the collection implements , it will be treated as such. 23 | /// The one-based index of the subset of objects to be contained by this instance. 24 | /// The maximum size of any individual subset. 25 | /// The total size of set 26 | /// 27 | /// 28 | /// A subset of this collection of objects that can be individually accessed by index and containing metadata 29 | /// about the collection of objects the subset was created from. 30 | /// 31 | /// 32 | public static async Task> ToPagedListAsync(this IQueryable superset, int pageNumber, int pageSize, int? totalSetCount, CancellationToken cancellationToken) 33 | { 34 | if (superset == null) 35 | { 36 | throw new ArgumentNullException(nameof(superset)); 37 | } 38 | 39 | if (pageNumber < 1) 40 | { 41 | throw new ArgumentOutOfRangeException($"pageNumber = {pageNumber}. PageNumber cannot be below 1."); 42 | } 43 | 44 | if (pageSize < 1) 45 | { 46 | throw new ArgumentOutOfRangeException($"pageSize = {pageSize}. PageSize cannot be less than 1."); 47 | } 48 | 49 | List subset; 50 | int totalCount; 51 | 52 | if (totalSetCount.HasValue) 53 | { 54 | totalCount = totalSetCount.Value; 55 | } 56 | else 57 | { 58 | totalCount = await superset.CountAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 59 | } 60 | 61 | if (totalCount > 0) 62 | { 63 | int skip = (pageNumber - 1) * pageSize; 64 | 65 | subset = await superset.Skip(skip).Take(pageSize).ToListAsync(cancellationToken).ConfigureAwait(false); 66 | } 67 | else 68 | { 69 | subset = new List(); 70 | } 71 | 72 | return new StaticPagedList(subset, pageNumber, pageSize, totalCount); 73 | } 74 | 75 | /// 76 | /// Async creates a subset of this collection of objects that can be individually accessed by index and 77 | /// containing metadata about the collection of objects the subset was created from. 78 | /// 79 | /// The type of object the collection should contain. 80 | /// The collection of objects to be divided into subsets. If the collection implements , it will be treated as such. 81 | /// The one-based index of the subset of objects to be contained by this instance. 82 | /// The maximum size of any individual subset. 83 | /// The total size of set 84 | /// 85 | /// A subset of this collection of objects that can be individually accessed by index and containing metadata 86 | /// about the collection of objects the subset was created from. 87 | /// 88 | /// 89 | public static Task> ToPagedListAsync(this IQueryable superset, int pageNumber, int pageSize, int? totalSetCount = null) 90 | { 91 | return ToPagedListAsync(superset, pageNumber, pageSize, totalSetCount, CancellationToken.None); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/X.PagedList.EF/README.md: -------------------------------------------------------------------------------- 1 | # X.Extensions.PagedList.EF 2 | 3 | [![NuGet Version](http://img.shields.io/nuget/v/X.PagedList.EF.svg?style=flat)](https://www.nuget.org/packages/X.PagedList.EF/) 4 | [![Twitter URL](https://img.shields.io/twitter/url/https/x.com/andrew_gubskiy.svg?style=social&label=Follow%20me!)](https://x.com/intent/user?screen_name=andrew_gubskiy) 5 | 6 | 7 | ## What is this? 8 | The X.Extensions.PagedList.EF library provides Entity Framework Core extensions for the X.PagedList library, enabling easier 9 | paging through data collections within Entity Framework contexts. This extension facilitates the creation of paged 10 | lists from IQueryable collections, streamlining the process of managing large datasets in .NET applications. 11 | 12 | ## How to use 13 | You can find all information about how to use X.PagedList libraries in [Wiki](https://github.com/dncuug/X.PagedList/wiki) 14 | 15 | ## Get a digital subscription for project news 16 | [Subscribe](https://x.com/intent/user?screen_name=andrew_gubskiy) to my X to keep up-to-date with project news and receive announcements. -------------------------------------------------------------------------------- /src/X.PagedList.EF/X.PagedList.EF.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | X.PagedList.EF 5 | EF extensions for X.PagedList library 6 | default 7 | net6.0;net7.0;net8.0 8 | paging pagedlist paged list entity framework ef 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/X.PagedList.EF/xpagedlist.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dncuug/X.PagedList/f68e3e69d25bcbc66a1af4ecf08a52d6c2e1a141/src/X.PagedList.EF/xpagedlist.snk -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/AjaxOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace X.PagedList.Mvc.Core; 4 | 5 | public class AjaxOptions 6 | { 7 | public virtual IEnumerable ToUnobtrusiveHtmlAttributes() 8 | { 9 | var attrs = new List 10 | { 11 | new() {Key = "data-ajax-method", Value = HttpMethod}, 12 | new() {Key = "data-ajax-mode", Value = InsertionMode}, 13 | new() {Key = "data-ajax-update", Value = "#" + UpdateTargetId}, 14 | new() {Key = "data-ajax", Value = "true"} 15 | }; 16 | 17 | if (!string.IsNullOrEmpty(Confirm)) 18 | { 19 | attrs.Add(new HtmlAttribute { Key = "data-ajax-confirm", Value = Confirm }); 20 | } 21 | 22 | if (!string.IsNullOrEmpty(LoadingElementId)) 23 | { 24 | attrs.Add(new HtmlAttribute { Key = "data-ajax-loading", Value = LoadingElementId }); 25 | } 26 | 27 | if (LoadingElementDuration > 0) 28 | { 29 | attrs.Add(new HtmlAttribute { Key = "data-ajax-loading-duration", Value = LoadingElementDuration }); 30 | } 31 | 32 | if (!string.IsNullOrEmpty(OnBegin)) 33 | { 34 | attrs.Add(new HtmlAttribute { Key = "data-ajax-begin", Value = OnBegin }); 35 | } 36 | 37 | if (!string.IsNullOrEmpty(OnComplete)) 38 | { 39 | attrs.Add(new HtmlAttribute { Key = "data-ajax-complete", Value = OnComplete }); 40 | } 41 | 42 | if (!string.IsNullOrEmpty(OnFailure)) 43 | { 44 | attrs.Add(new HtmlAttribute { Key = "data-ajax-failure", Value = OnFailure }); 45 | } 46 | 47 | if (!string.IsNullOrEmpty(OnSuccess)) 48 | { 49 | attrs.Add(new HtmlAttribute { Key = "data-ajax-success", Value = OnSuccess }); 50 | } 51 | 52 | if (!string.IsNullOrEmpty(Url)) 53 | { 54 | attrs.Add(new HtmlAttribute { Key = "data-ajax-url", Value = Url }); 55 | } 56 | 57 | if (AllowCache) 58 | { 59 | attrs.Add(new HtmlAttribute { Key = "data-ajax-cache", Value = "true" }); 60 | } 61 | 62 | return attrs; 63 | } 64 | 65 | /// 66 | /// The HTTP method to make the request with. The default value is "GET". 67 | /// 68 | public string HttpMethod { get; set; } = "GET"; 69 | 70 | /// 71 | /// The mode used to handle the data received as response. The default value is "Replace". 72 | /// 73 | public InsertionMode InsertionMode { get; set; } = InsertionMode.Replace; 74 | 75 | public string? UpdateTargetId { get; set; } 76 | public string? Confirm { get; set; } 77 | public int LoadingElementDuration { get; set; } 78 | public string? LoadingElementId { get; set; } 79 | public string? OnBegin { get; set; } 80 | public string? OnComplete { get; set; } 81 | public string? OnFailure { get; set; } 82 | public string? OnSuccess { get; set; } 83 | public string? Url { get; set; } 84 | public bool AllowCache { get; set; } 85 | } -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/Fluent/HtmlPagerBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Html; 3 | using Microsoft.AspNetCore.Mvc.Rendering; 4 | 5 | namespace X.PagedList.Mvc.Core.Fluent; 6 | 7 | internal sealed class HtmlPagerBuilder : IHtmlPagerBuilder 8 | { 9 | private readonly IHtmlHelper _htmlHelper; 10 | private readonly IPagedList _pagedList; 11 | 12 | private Func _generatePageUrl; 13 | private PagedListRenderOptions _options; 14 | private string? _partialViewName; 15 | 16 | public HtmlPagerBuilder(IHtmlHelper htmlHelper, IPagedList pagedList) 17 | { 18 | _htmlHelper = htmlHelper; 19 | _pagedList = pagedList; 20 | _generatePageUrl = x => x.ToString(); 21 | _options = new PagedListRenderOptions(); 22 | } 23 | 24 | public IHtmlPagerBuilder Url(Func builder) 25 | { 26 | _generatePageUrl = builder; 27 | 28 | return this; 29 | } 30 | 31 | public IHtmlPagerBuilder DisplayLinkToFirstPage(PagedListDisplayMode displayMode = PagedListDisplayMode.Always) 32 | { 33 | _options.DisplayLinkToFirstPage = displayMode; 34 | 35 | return this; 36 | } 37 | 38 | public IHtmlPagerBuilder DisplayLinkToLastPage(PagedListDisplayMode displayMode = PagedListDisplayMode.Always) 39 | { 40 | _options.DisplayLinkToLastPage = displayMode; 41 | 42 | return this; 43 | } 44 | 45 | public IHtmlPagerBuilder DisplayLinkToPreviousPage(PagedListDisplayMode displayMode = PagedListDisplayMode.Always) 46 | { 47 | _options.DisplayLinkToPreviousPage = displayMode; 48 | 49 | return this; 50 | } 51 | 52 | public IHtmlPagerBuilder DisplayLinkToNextPage(PagedListDisplayMode displayMode = PagedListDisplayMode.Always) 53 | { 54 | _options.DisplayLinkToNextPage = displayMode; 55 | 56 | return this; 57 | } 58 | 59 | public IHtmlPagerBuilder DisplayLinkToIndividualPages(bool displayMode = true) 60 | { 61 | _options.DisplayLinkToIndividualPages = displayMode; 62 | 63 | return this; 64 | } 65 | 66 | public IHtmlPagerBuilder DisplayPageCountAndCurrentLocation(bool displayMode = true) 67 | { 68 | _options.DisplayPageCountAndCurrentLocation = displayMode; 69 | 70 | return this; 71 | } 72 | 73 | public IHtmlPagerBuilder DisplayEllipsesWhenNotShowingAllPageNumbers(bool displayMode = true) 74 | { 75 | _options.DisplayEllipsesWhenNotShowingAllPageNumbers = displayMode; 76 | 77 | return this; 78 | } 79 | 80 | public IHtmlPagerBuilder MaximumPageNumbersToDisplay(int pageNumbers) 81 | { 82 | _options.MaximumPageNumbersToDisplay = pageNumbers; 83 | 84 | return this; 85 | } 86 | 87 | public IHtmlContent Classic() 88 | { 89 | _options = PagedListRenderOptions.Classic; 90 | 91 | return Build(); 92 | } 93 | 94 | public IHtmlContent ClassicPlusFirstAndLast() 95 | { 96 | _options = PagedListRenderOptions.ClassicPlusFirstAndLast; 97 | 98 | return Build(); 99 | } 100 | 101 | public IHtmlContent Minimal() 102 | { 103 | _options = PagedListRenderOptions.Minimal; 104 | 105 | return Build(); 106 | } 107 | 108 | public IHtmlContent MinimalWithPageCountText() 109 | { 110 | _options = PagedListRenderOptions.MinimalWithPageCountText; 111 | 112 | return Build(); 113 | } 114 | 115 | public IHtmlContent MinimalWithItemCountText() 116 | { 117 | _options = PagedListRenderOptions.MinimalWithItemCountText; 118 | 119 | return Build(); 120 | } 121 | 122 | public IHtmlContent PageNumbersOnly() 123 | { 124 | _options = PagedListRenderOptions.PageNumbersOnly; 125 | 126 | return Build(); 127 | } 128 | 129 | public IHtmlContent OnlyShowFivePagesAtATime() 130 | { 131 | _options = PagedListRenderOptions.OnlyShowFivePagesAtATime; 132 | 133 | return Build(); 134 | } 135 | 136 | public IHtmlPagerBuilder WithPartialView(string partialViewName) 137 | { 138 | _partialViewName = partialViewName; 139 | 140 | return this; 141 | } 142 | 143 | public IHtmlContent Build(PagedListRenderOptions? options) 144 | { 145 | _options = options ?? _options; 146 | 147 | return Build(); 148 | } 149 | 150 | public IHtmlContent Build() 151 | { 152 | if (string.IsNullOrWhiteSpace(_partialViewName)) 153 | { 154 | return _htmlHelper.PagedListPager(_pagedList, _generatePageUrl, _options); 155 | } 156 | 157 | _htmlHelper.ViewBag.Options = _options; 158 | _htmlHelper.ViewBag.GeneratePageUrl = _generatePageUrl; 159 | 160 | return _htmlHelper.Partial(_partialViewName, _pagedList); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/Fluent/HtmlPagerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using JetBrains.Annotations; 3 | using Microsoft.AspNetCore.Html; 4 | using Microsoft.AspNetCore.Mvc.Rendering; 5 | using X.PagedList.Extensions; 6 | 7 | namespace X.PagedList.Mvc.Core.Fluent; 8 | 9 | [PublicAPI] 10 | public static class HtmlPagerExtensions 11 | { 12 | public static IHtmlContent Pager(this IHtmlHelper htmlHelper) 13 | { 14 | return new HtmlPagerBuilder(htmlHelper, Enumerable.Empty().ToPagedList()).Build(); 15 | } 16 | 17 | public static IHtmlPagerBuilder Pager(this IHtmlHelper htmlHelper, IPagedList list) 18 | { 19 | return new HtmlPagerBuilder(htmlHelper, list); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/Fluent/IHtmlPagerBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | using Microsoft.AspNetCore.Html; 4 | 5 | namespace X.PagedList.Mvc.Core.Fluent; 6 | 7 | [PublicAPI] 8 | public interface IHtmlPagerBuilder 9 | { 10 | IHtmlPagerBuilder Url(Func builder); 11 | 12 | IHtmlPagerBuilder DisplayLinkToFirstPage(PagedListDisplayMode displayMode = PagedListDisplayMode.Always); 13 | 14 | IHtmlPagerBuilder DisplayLinkToLastPage(PagedListDisplayMode displayMode = PagedListDisplayMode.Always); 15 | 16 | IHtmlPagerBuilder DisplayLinkToPreviousPage(PagedListDisplayMode displayMode = PagedListDisplayMode.Always); 17 | 18 | IHtmlPagerBuilder DisplayLinkToNextPage(PagedListDisplayMode displayMode = PagedListDisplayMode.Always); 19 | 20 | IHtmlPagerBuilder DisplayLinkToIndividualPages(bool displayMode = true); 21 | 22 | IHtmlPagerBuilder DisplayPageCountAndCurrentLocation(bool displayMode = true); 23 | 24 | IHtmlPagerBuilder DisplayEllipsesWhenNotShowingAllPageNumbers(bool displayMode = true); 25 | 26 | IHtmlPagerBuilder MaximumPageNumbersToDisplay(int pageNumbers); 27 | 28 | IHtmlPagerBuilder WithPartialView(string partialViewName); 29 | 30 | IHtmlContent Classic(); 31 | 32 | IHtmlContent ClassicPlusFirstAndLast(); 33 | 34 | IHtmlContent Minimal(); 35 | 36 | IHtmlContent MinimalWithPageCountText(); 37 | 38 | IHtmlContent MinimalWithItemCountText(); 39 | 40 | IHtmlContent PageNumbersOnly(); 41 | 42 | IHtmlContent OnlyShowFivePagesAtATime(); 43 | 44 | IHtmlContent Build(); 45 | 46 | IHtmlContent Build(PagedListRenderOptions? options); 47 | } 48 | -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/GoToFormRenderOptions.cs: -------------------------------------------------------------------------------- 1 | namespace X.PagedList.Mvc.Core; 2 | 3 | /// 4 | /// Options for configuring the output of . 5 | /// 6 | public class GoToFormRenderOptions 7 | { 8 | /// 9 | /// The default settings, with configurable querystring key (input field name). 10 | /// 11 | public GoToFormRenderOptions(string inputFieldName) 12 | { 13 | LabelFormat = "Go to page:"; 14 | SubmitButtonFormat = "Go"; 15 | InputFieldName = inputFieldName; 16 | InputFieldType = "number"; 17 | } 18 | 19 | /// 20 | /// The default settings. 21 | /// 22 | public GoToFormRenderOptions() : this("page") 23 | { 24 | } 25 | 26 | /// 27 | /// The text to show in the form's input label. 28 | /// 29 | /// 30 | /// "Go to page:" 31 | /// 32 | public string LabelFormat { get; set; } 33 | 34 | /// 35 | /// The text to show in the form's submit button. 36 | /// 37 | /// 38 | /// "Go" 39 | /// 40 | public string SubmitButtonFormat { get; set; } 41 | 42 | /// 43 | /// Submit button width in px 44 | /// 45 | public int SubmitButtonWidth { get; set; } 46 | 47 | /// 48 | /// The querystring key this form should submit the new page number as. 49 | /// 50 | /// 51 | /// "page" 52 | /// 53 | public string InputFieldName { get; set; } 54 | 55 | /// 56 | /// The HTML input type for this field. Defaults to the HTML5 "number" type, but can be changed to "text" if targetting previous versions of HTML. 57 | /// 58 | /// 59 | /// "number" 60 | /// 61 | public string InputFieldType { get; set; } 62 | 63 | /// 64 | /// Input width in px 65 | /// 66 | public int InputWidth { get; set; } 67 | 68 | /// 69 | /// Class that will be applied for input field 70 | /// 71 | public string? InputFieldClass { get; set; } 72 | 73 | /// 74 | /// Class that will be applied for submit button 75 | /// 76 | public string? SubmitButtonClass { get; set; } 77 | } 78 | -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/HtmlAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace X.PagedList.Mvc.Core; 2 | 3 | /// 4 | /// Represents one attribute of a DOM element 5 | /// 6 | /// 7 | /// Setting and is required. 8 | /// 9 | public class HtmlAttribute 10 | { 11 | public string Key { get; set; } = ""; 12 | public object? Value { get; set; } 13 | } -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/HtmlHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Linq; 5 | using System.Text; 6 | using Microsoft.AspNetCore.Mvc.Rendering; 7 | 8 | namespace X.PagedList.Mvc.Core; 9 | 10 | public class HtmlHelper 11 | { 12 | private readonly ITagBuilderFactory _tagBuilderFactory; 13 | 14 | public HtmlHelper(ITagBuilderFactory tagBuilderFactory) 15 | { 16 | _tagBuilderFactory = tagBuilderFactory; 17 | } 18 | 19 | private static void SetInnerText(TagBuilder tagBuilder, string innerText) 20 | { 21 | tagBuilder.SetInnerText(innerText); 22 | } 23 | 24 | private static void AppendHtml(TagBuilder tagBuilder, string innerHtml) 25 | { 26 | tagBuilder.AppendHtml(innerHtml); 27 | } 28 | 29 | private static string TagBuilderToString(TagBuilder tagBuilder) 30 | { 31 | return tagBuilder.ToString(TagRenderMode.Normal); 32 | } 33 | 34 | private static string TagBuilderToString(TagBuilder tagBuilder, TagRenderMode renderMode) 35 | { 36 | return tagBuilder.ToString(renderMode); 37 | } 38 | 39 | private TagBuilder WrapInListItem(string text) 40 | { 41 | var li = _tagBuilderFactory.Create("li"); 42 | 43 | SetInnerText(li, text); 44 | 45 | return li; 46 | } 47 | 48 | private TagBuilder WrapInListItem(TagBuilder inner, PagedListRenderOptions? options, params string[] classes) 49 | { 50 | var li = _tagBuilderFactory.Create("li"); 51 | 52 | foreach (var @class in classes) 53 | { 54 | li.AddCssClass(@class); 55 | } 56 | 57 | if (options?.FunctionToTransformEachPageLink != null) 58 | { 59 | return options.FunctionToTransformEachPageLink(li, inner); 60 | } 61 | 62 | AppendHtml(li, TagBuilderToString(inner)); 63 | 64 | return li; 65 | } 66 | 67 | private TagBuilder First(IPagedList list, Func generatePageUrl, PagedListRenderOptions options) 68 | { 69 | const int targetPageNumber = 1; 70 | 71 | var first = _tagBuilderFactory.Create("a"); 72 | 73 | AppendHtml(first, string.Format(options.LinkToFirstPageFormat, targetPageNumber)); 74 | 75 | foreach (var c in options.PageClasses ?? Enumerable.Empty()) 76 | { 77 | first.AddCssClass(c); 78 | } 79 | 80 | if (list.IsFirstPage) 81 | { 82 | return WrapInListItem(first, options, "PagedList-skipToFirst", "disabled"); 83 | } 84 | 85 | first.Attributes.Add("href", generatePageUrl(targetPageNumber)); 86 | 87 | return WrapInListItem(first, options, "PagedList-skipToFirst"); 88 | } 89 | 90 | private TagBuilder Previous(IPagedList list, Func generatePageUrl, PagedListRenderOptions options) 91 | { 92 | var targetPageNumber = list.PageNumber - 1; 93 | 94 | var previous = _tagBuilderFactory.Create("a"); 95 | 96 | AppendHtml(previous, string.Format(options.LinkToPreviousPageFormat, targetPageNumber)); 97 | 98 | previous.Attributes.Add("rel", "prev"); 99 | 100 | foreach (var c in options.PageClasses ?? Enumerable.Empty()) 101 | { 102 | previous.AddCssClass(c); 103 | } 104 | 105 | if (!list.HasPreviousPage) 106 | { 107 | return WrapInListItem(previous, options, options.PreviousElementClass, "disabled"); 108 | } 109 | 110 | previous.Attributes.Add("href", generatePageUrl(targetPageNumber)); 111 | 112 | return WrapInListItem(previous, options, options.PreviousElementClass); 113 | } 114 | 115 | private TagBuilder Page(int i, IPagedList list, Func generatePageUrl, PagedListRenderOptions options) 116 | { 117 | var format = options.FunctionToDisplayEachPageNumber 118 | ?? (pageNumber => string.Format(options.LinkToIndividualPageFormat, pageNumber)); 119 | 120 | var targetPageNumber = i; 121 | 122 | var page = i == list.PageNumber 123 | ? _tagBuilderFactory.Create("span") 124 | : _tagBuilderFactory.Create("a"); 125 | 126 | SetInnerText(page, format(targetPageNumber)); 127 | 128 | foreach (var c in options.PageClasses ?? Enumerable.Empty()) 129 | { 130 | page.AddCssClass(c); 131 | } 132 | 133 | if (i == list.PageNumber) 134 | { 135 | return WrapInListItem(page, options, options.ActiveLiElementClass); 136 | } 137 | 138 | page.Attributes.Add("href", generatePageUrl(targetPageNumber)); 139 | 140 | return WrapInListItem(page, options); 141 | } 142 | 143 | private TagBuilder Next(IPagedList list, Func generatePageUrl, PagedListRenderOptions options) 144 | { 145 | var targetPageNumber = list.PageNumber + 1; 146 | var next = _tagBuilderFactory.Create("a"); 147 | 148 | AppendHtml(next, string.Format(options.LinkToNextPageFormat, targetPageNumber)); 149 | 150 | next.Attributes.Add("rel", "next"); 151 | 152 | foreach (var c in options.PageClasses ?? Enumerable.Empty()) 153 | { 154 | next.AddCssClass(c); 155 | } 156 | 157 | if (!list.HasNextPage) 158 | { 159 | return WrapInListItem(next, options, options.NextElementClass, "disabled"); 160 | } 161 | 162 | next.Attributes.Add("href", generatePageUrl(targetPageNumber)); 163 | 164 | return WrapInListItem(next, options, options.NextElementClass); 165 | } 166 | 167 | private TagBuilder Last(IPagedList list, Func generatePageUrl, PagedListRenderOptions options) 168 | { 169 | var targetPageNumber = list.PageCount; 170 | var last = _tagBuilderFactory.Create("a"); 171 | 172 | AppendHtml(last, string.Format(options.LinkToLastPageFormat, targetPageNumber)); 173 | 174 | foreach (var c in options.PageClasses ?? Enumerable.Empty()) 175 | { 176 | last.AddCssClass(c); 177 | } 178 | 179 | if (list.IsLastPage) 180 | { 181 | return WrapInListItem(last, options, "PagedList-skipToLast", "disabled"); 182 | } 183 | 184 | last.Attributes.Add("href", generatePageUrl(targetPageNumber)); 185 | 186 | return WrapInListItem(last, options, "PagedList-skipToLast"); 187 | } 188 | 189 | private TagBuilder PageCountAndLocationText(IPagedList list, PagedListRenderOptions options) 190 | { 191 | var text = _tagBuilderFactory.Create("a"); 192 | 193 | SetInnerText(text, string.Format(options.PageCountAndCurrentLocationFormat, list.PageNumber, list.PageCount)); 194 | 195 | return WrapInListItem(text, options, "PagedList-pageCountAndLocation", "disabled"); 196 | } 197 | 198 | private TagBuilder ItemSliceAndTotalText(IPagedList list, PagedListRenderOptions options) 199 | { 200 | var text = _tagBuilderFactory.Create("a"); 201 | 202 | SetInnerText(text, string.Format(options.ItemSliceAndTotalFormat, list.FirstItemOnPage, list.LastItemOnPage, list.TotalItemCount)); 203 | 204 | return WrapInListItem(text, options, "PagedList-pageCountAndLocation", "disabled"); 205 | } 206 | 207 | private TagBuilder PreviousEllipsis(IPagedList list, Func generatePageUrl, PagedListRenderOptions options, int firstPageToDisplay) 208 | { 209 | var previous = _tagBuilderFactory.Create("a"); 210 | 211 | AppendHtml(previous, options.EllipsesFormat); 212 | 213 | previous.Attributes.Add("rel", "prev"); 214 | previous.AddCssClass("PagedList-skipToPrevious"); 215 | 216 | foreach (var c in options.PageClasses ?? Enumerable.Empty()) 217 | { 218 | previous.AddCssClass(c); 219 | } 220 | 221 | if (!list.HasPreviousPage) 222 | { 223 | return WrapInListItem(previous, options, options.EllipsesElementClass, "disabled"); 224 | } 225 | 226 | int targetPageNumber = firstPageToDisplay - 1; 227 | 228 | previous.Attributes.Add("href", generatePageUrl(targetPageNumber)); 229 | 230 | return WrapInListItem(previous, options, options.EllipsesElementClass); 231 | } 232 | 233 | private TagBuilder NextEllipsis(IPagedList list, Func generatePageUrl, PagedListRenderOptions options, int lastPageToDisplay) 234 | { 235 | var next = _tagBuilderFactory.Create("a"); 236 | 237 | AppendHtml(next, options.EllipsesFormat); 238 | 239 | next.Attributes.Add("rel", "next"); 240 | next.AddCssClass("PagedList-skipToNext"); 241 | 242 | foreach (var c in options.PageClasses ?? Enumerable.Empty()) 243 | { 244 | next.AddCssClass(c); 245 | } 246 | 247 | if (!list.HasNextPage) 248 | { 249 | return WrapInListItem(next, options, options.EllipsesElementClass, "disabled"); 250 | } 251 | 252 | var targetPageNumber = lastPageToDisplay + 1; 253 | 254 | next.Attributes.Add("href", generatePageUrl(targetPageNumber)); 255 | 256 | return WrapInListItem(next, options, options.EllipsesElementClass); 257 | } 258 | 259 | public string? PagedListPager(IPagedList? pagedList, Func generatePageUrl, PagedListRenderOptions options) 260 | { 261 | var list = pagedList ?? new StaticPagedList(ImmutableList.Empty, 1, 10, 0); 262 | 263 | if (options.Display == PagedListDisplayMode.Never || (options.Display == PagedListDisplayMode.IfNeeded && list.PageCount <= 1)) 264 | { 265 | return null; 266 | } 267 | 268 | var listItemLinks = new List(); 269 | 270 | //calculate start and end of range of page numbers 271 | int firstPageToDisplay = 1; 272 | int lastPageToDisplay = list.PageCount; 273 | int pageNumbersToDisplay = lastPageToDisplay; 274 | 275 | if (options.MaximumPageNumbersToDisplay.HasValue && list.PageCount > options.MaximumPageNumbersToDisplay) 276 | { 277 | // cannot fit all pages into pager 278 | int maxPageNumbersToDisplay = options.MaximumPageNumbersToDisplay.Value; 279 | 280 | firstPageToDisplay = list.PageNumber - maxPageNumbersToDisplay / 2; 281 | 282 | if (firstPageToDisplay < 1) 283 | { 284 | firstPageToDisplay = 1; 285 | } 286 | 287 | pageNumbersToDisplay = maxPageNumbersToDisplay; 288 | lastPageToDisplay = firstPageToDisplay + pageNumbersToDisplay - 1; 289 | 290 | if (lastPageToDisplay > list.PageCount) 291 | { 292 | firstPageToDisplay = list.PageCount - maxPageNumbersToDisplay + 1; 293 | } 294 | } 295 | 296 | //first 297 | if (options.DisplayLinkToFirstPage == PagedListDisplayMode.Always || 298 | (options.DisplayLinkToFirstPage == PagedListDisplayMode.IfNeeded && firstPageToDisplay > 1)) 299 | { 300 | listItemLinks.Add(First(list, generatePageUrl, options)); 301 | } 302 | 303 | //previous 304 | if (options.DisplayLinkToPreviousPage == PagedListDisplayMode.Always || 305 | (options.DisplayLinkToPreviousPage == PagedListDisplayMode.IfNeeded && !list.IsFirstPage)) 306 | { 307 | listItemLinks.Add(Previous(list, generatePageUrl, options)); 308 | } 309 | 310 | //text 311 | if (options.DisplayPageCountAndCurrentLocation) 312 | { 313 | listItemLinks.Add(PageCountAndLocationText(list, options)); 314 | } 315 | 316 | //text 317 | if (options.DisplayItemSliceAndTotal && options.ItemSliceAndTotalPosition == ItemSliceAndTotalPosition.Start) 318 | { 319 | listItemLinks.Add(ItemSliceAndTotalText(list, options)); 320 | } 321 | 322 | //page 323 | if (options.DisplayLinkToIndividualPages) 324 | { 325 | //if there are previous page numbers not displayed, show an ellipsis 326 | if (options.DisplayEllipsesWhenNotShowingAllPageNumbers && firstPageToDisplay > 1) 327 | { 328 | listItemLinks.Add(PreviousEllipsis(list, generatePageUrl, options, firstPageToDisplay)); 329 | } 330 | 331 | foreach (int i in Enumerable.Range(firstPageToDisplay, pageNumbersToDisplay)) 332 | { 333 | //show delimiter between page numbers 334 | if (i > firstPageToDisplay && !string.IsNullOrWhiteSpace(options.DelimiterBetweenPageNumbers)) 335 | { 336 | listItemLinks.Add(WrapInListItem(options.DelimiterBetweenPageNumbers)); 337 | } 338 | 339 | //show page number link 340 | listItemLinks.Add(Page(i, list, generatePageUrl, options)); 341 | } 342 | 343 | //if there are subsequent page numbers not displayed, show an ellipsis 344 | if (options.DisplayEllipsesWhenNotShowingAllPageNumbers && 345 | (firstPageToDisplay + pageNumbersToDisplay - 1) < list.PageCount) 346 | { 347 | listItemLinks.Add(NextEllipsis(list, generatePageUrl, options, lastPageToDisplay)); 348 | } 349 | } 350 | 351 | //next 352 | if (options.DisplayLinkToNextPage == PagedListDisplayMode.Always || 353 | (options.DisplayLinkToNextPage == PagedListDisplayMode.IfNeeded && !list.IsLastPage)) 354 | { 355 | listItemLinks.Add(Next(list, generatePageUrl, options)); 356 | } 357 | 358 | //last 359 | if (options.DisplayLinkToLastPage == PagedListDisplayMode.Always || 360 | (options.DisplayLinkToLastPage == PagedListDisplayMode.IfNeeded && lastPageToDisplay < list.PageCount)) 361 | { 362 | listItemLinks.Add(Last(list, generatePageUrl, options)); 363 | } 364 | 365 | //text 366 | if (options.DisplayItemSliceAndTotal && options.ItemSliceAndTotalPosition == ItemSliceAndTotalPosition.End) 367 | { 368 | listItemLinks.Add(ItemSliceAndTotalText(list, options)); 369 | } 370 | 371 | if (listItemLinks.Any()) 372 | { 373 | //append class to first item in list? 374 | if (!string.IsNullOrWhiteSpace(options.ClassToApplyToFirstListItemInPager)) 375 | { 376 | listItemLinks.First().AddCssClass(options.ClassToApplyToFirstListItemInPager); 377 | } 378 | 379 | //append class to last item in list? 380 | if (!string.IsNullOrWhiteSpace(options.ClassToApplyToLastListItemInPager)) 381 | { 382 | listItemLinks.Last().AddCssClass(options.ClassToApplyToLastListItemInPager); 383 | } 384 | 385 | //append classes to all list item links 386 | foreach (var li in listItemLinks) 387 | { 388 | foreach (var c in options.LiElementClasses ?? Enumerable.Empty()) 389 | { 390 | li.AddCssClass(c); 391 | } 392 | } 393 | } 394 | 395 | //Collapse all the list items into one big string 396 | var listItemLinksString = listItemLinks.Aggregate( 397 | new StringBuilder(), 398 | (sb, listItem) => sb.Append(TagBuilderToString(listItem)), 399 | sb => sb.ToString()); 400 | 401 | var ul = _tagBuilderFactory.Create("ul"); 402 | 403 | AppendHtml(ul, listItemLinksString); 404 | 405 | foreach (var c in options.UlElementClasses ?? Enumerable.Empty()) 406 | { 407 | ul.AddCssClass(c); 408 | } 409 | 410 | if (options.UlElementattributes != null) 411 | { 412 | foreach (var c in options.UlElementattributes) 413 | { 414 | ul.MergeAttribute(c.Key, c.Value); 415 | } 416 | } 417 | 418 | var outerDiv = _tagBuilderFactory 419 | .Create("div"); 420 | 421 | foreach (var c in options.ContainerDivClasses ?? Enumerable.Empty()) 422 | { 423 | outerDiv.AddCssClass(c); 424 | } 425 | 426 | AppendHtml(outerDiv, TagBuilderToString(ul)); 427 | 428 | return TagBuilderToString(outerDiv); 429 | } 430 | 431 | public string PagedListGoToPageForm(IPagedList list, string formAction, GoToFormRenderOptions options) 432 | { 433 | var form = _tagBuilderFactory.Create("form"); 434 | 435 | form.AddCssClass("PagedList-goToPage"); 436 | form.Attributes.Add("action", formAction); 437 | form.Attributes.Add("method", "get"); 438 | 439 | var fieldset = _tagBuilderFactory.Create("fieldset"); 440 | 441 | var label = _tagBuilderFactory.Create("label"); 442 | 443 | label.Attributes.Add("for", options.InputFieldName); 444 | 445 | SetInnerText(label, options.LabelFormat); 446 | 447 | var input = _tagBuilderFactory.Create("input"); 448 | 449 | input.Attributes.Add("type", options.InputFieldType); 450 | input.Attributes.Add("name", options.InputFieldName); 451 | input.Attributes.Add("value", list.PageNumber.ToString()); 452 | input.Attributes.Add("class", options.InputFieldClass); 453 | input.Attributes.Add("Style", $"width: {options.InputWidth}px"); 454 | 455 | var submit = _tagBuilderFactory.Create("input"); 456 | 457 | submit.Attributes.Add("type", "submit"); 458 | submit.Attributes.Add("value", options.SubmitButtonFormat); 459 | submit.Attributes.Add("class", options.SubmitButtonClass); 460 | submit.Attributes.Add("Style", $"width: {options.SubmitButtonWidth}px"); 461 | 462 | AppendHtml(fieldset, TagBuilderToString(label)); 463 | AppendHtml(fieldset, TagBuilderToString(input, TagRenderMode.SelfClosing)); 464 | AppendHtml(fieldset, TagBuilderToString(submit, TagRenderMode.SelfClosing)); 465 | AppendHtml(form, TagBuilderToString(fieldset)); 466 | 467 | return TagBuilderToString(form); 468 | } 469 | } 470 | -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/HtmlHelperExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web; 3 | using JetBrains.Annotations; 4 | using Microsoft.AspNetCore.Html; 5 | using Microsoft.AspNetCore.Mvc.Rendering; 6 | 7 | namespace X.PagedList.Mvc.Core; 8 | 9 | /// 10 | /// Extension methods for generating paging controls that can operate on instances of IPagedList. 11 | /// 12 | [PublicAPI] 13 | public static class HtmlHelperExtension 14 | { 15 | /// 16 | /// Displays a configurable paging control for instances of PagedList. 17 | /// 18 | /// This method is meant to hook off HtmlHelper as an extension method. 19 | /// The PagedList to use as the data source. 20 | /// A function that takes the page number of the desired page and returns a URL-string that will load that page. 21 | /// Outputs the paging control HTML. 22 | public static HtmlString PagedListPager(this IHtmlHelper html, 23 | IPagedList? list, 24 | Func generatePageUrl) 25 | { 26 | return PagedListPager(html, list, generatePageUrl, new PagedListRenderOptions()); 27 | } 28 | 29 | /// 30 | /// Displays a configurable paging control for instances of PagedList. 31 | /// 32 | /// This method is meant to hook off HtmlHelper as an extension method. 33 | /// The PagedList to use as the data source. 34 | /// A function that takes the page number of the desired page and returns a URL-string that will load that page. 35 | /// Formatting options. 36 | /// Outputs the paging control HTML. 37 | public static HtmlString PagedListPager(this IHtmlHelper html, 38 | IPagedList? list, 39 | Func generatePageUrl, 40 | PagedListRenderOptions options) 41 | { 42 | HtmlHelper htmlHelper = new HtmlHelper(new TagBuilderFactory()); 43 | string? htmlString = htmlHelper.PagedListPager(list, generatePageUrl, options); 44 | 45 | htmlString = HttpUtility.HtmlDecode(htmlString); 46 | 47 | return new HtmlString(htmlString); 48 | } 49 | 50 | /// 51 | /// Displays a configurable "Go To Page:" form for instances of PagedList. 52 | /// 53 | /// This method is meant to hook off HtmlHelper as an extension method. 54 | /// The PagedList to use as the data source. 55 | /// The URL this form should submit the GET request to. 56 | /// Outputs the "Go To Page:" form HTML. 57 | public static HtmlString PagedListGoToPageForm(this IHtmlHelper html, IPagedList list, string formAction) 58 | { 59 | return PagedListGoToPageForm(html, list, formAction, "page"); 60 | } 61 | 62 | /// 63 | /// Displays a configurable "Go To Page:" form for instances of PagedList. 64 | /// 65 | /// This method is meant to hook off HtmlHelper as an extension method. 66 | /// The PagedList to use as the data source. 67 | /// The URL this form should submit the GET request to. 68 | /// The querystring key this form should submit the new page number as. 69 | /// Outputs the "Go To Page:" form HTML. 70 | public static HtmlString PagedListGoToPageForm(this IHtmlHelper html, 71 | IPagedList list, 72 | string formAction, 73 | string inputFieldName) 74 | { 75 | return PagedListGoToPageForm(html, list, formAction, new GoToFormRenderOptions(inputFieldName)); 76 | } 77 | 78 | /// 79 | /// Displays a configurable "Go To Page:" form for instances of PagedList. 80 | /// 81 | /// This method is meant to hook off HtmlHelper as an extension method. 82 | /// The PagedList to use as the data source. 83 | /// The URL this form should submit the GET request to. 84 | /// Formatting options. 85 | /// Outputs the "Go To Page:" form HTML. 86 | public static HtmlString PagedListGoToPageForm(this IHtmlHelper html, 87 | IPagedList list, 88 | string formAction, 89 | GoToFormRenderOptions options) 90 | { 91 | HtmlHelper htmlHelper = new HtmlHelper(new TagBuilderFactory()); 92 | string htmlString = htmlHelper.PagedListGoToPageForm(list, formAction, options); 93 | 94 | return new HtmlString(htmlString); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/InsertionMode.cs: -------------------------------------------------------------------------------- 1 | namespace X.PagedList.Mvc.Core; 2 | 3 | /// 4 | /// Enumerates the different modes of inserting content into the DOM 5 | /// 6 | public enum InsertionMode 7 | { 8 | Replace 9 | } -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/ItemSliceAndTotalPosition.cs: -------------------------------------------------------------------------------- 1 | namespace X.PagedList.Mvc.Core; 2 | 3 | /// 4 | /// A two-state enum that controls the position of ItemSliceAndTotal text within PagedList items. 5 | /// 6 | public enum ItemSliceAndTotalPosition 7 | { 8 | /// 9 | /// Shows ItemSliceAndTotal info at the beginning of the PagedList items. 10 | /// 11 | Start, 12 | 13 | /// 14 | /// Shows ItemSliceAndTotal info at the end of the PagedList items. 15 | /// 16 | End 17 | } 18 | -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/PagedListDisplayMode.cs: -------------------------------------------------------------------------------- 1 | namespace X.PagedList.Mvc.Core; 2 | 3 | /// 4 | /// A tri-state enum that controls the visibility of portions of the PagedList paging control. 5 | /// 6 | public enum PagedListDisplayMode 7 | { 8 | /// 9 | /// Always render. 10 | /// 11 | Always, 12 | 13 | /// 14 | /// Never render. 15 | /// 16 | Never, 17 | 18 | /// 19 | /// Only render when there is data that makes sense to show (context sensitive). 20 | /// 21 | IfNeeded 22 | } 23 | -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/PagedListRenderOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Encodings.Web; 5 | using Microsoft.AspNetCore.Mvc.Rendering; 6 | 7 | namespace X.PagedList.Mvc.Core; 8 | 9 | public class PagedListRenderOptions 10 | { 11 | /// 12 | /// The default settings render all navigation links and no descriptive text 13 | /// 14 | public PagedListRenderOptions() 15 | { 16 | HtmlEncoder = HtmlEncoder.Default; 17 | DisplayLinkToFirstPage = PagedListDisplayMode.IfNeeded; 18 | DisplayLinkToLastPage = PagedListDisplayMode.IfNeeded; 19 | DisplayLinkToPreviousPage = PagedListDisplayMode.IfNeeded; 20 | DisplayLinkToNextPage = PagedListDisplayMode.IfNeeded; 21 | DisplayLinkToIndividualPages = true; 22 | DisplayPageCountAndCurrentLocation = false; 23 | MaximumPageNumbersToDisplay = 10; 24 | DisplayEllipsesWhenNotShowingAllPageNumbers = true; 25 | EllipsesFormat = "…"; 26 | LinkToFirstPageFormat = "<<"; 27 | LinkToPreviousPageFormat = "<"; 28 | LinkToIndividualPageFormat = "{0}"; 29 | LinkToNextPageFormat = ">"; 30 | LinkToLastPageFormat = ">>"; 31 | PageCountAndCurrentLocationFormat = "Page {0} of {1}."; 32 | ItemSliceAndTotalFormat = "Showing items {0} through {1} of {2}."; 33 | ItemSliceAndTotalPosition = ItemSliceAndTotalPosition.Start; 34 | FunctionToDisplayEachPageNumber = null; 35 | ClassToApplyToFirstListItemInPager = null; 36 | ClassToApplyToLastListItemInPager = null; 37 | ContainerDivClasses = new[] { "pagination-container" }; 38 | UlElementClasses = new[] { "pagination" }; 39 | LiElementClasses = Enumerable.Empty(); 40 | PageClasses = Enumerable.Empty(); 41 | UlElementattributes = null; 42 | ActiveLiElementClass = "active"; 43 | EllipsesElementClass = "PagedList-ellipses"; 44 | PreviousElementClass = "PagedList-skipToPrevious"; 45 | NextElementClass = "PagedList-skipToNext"; 46 | } 47 | 48 | /// 49 | /// Gets or sets the HtmlEncoder to use encoding HTML render. 50 | /// 51 | public HtmlEncoder HtmlEncoder { get; set; } 52 | 53 | /// 54 | /// CSS Classes to append to the <div> element that wraps the paging control. 55 | /// 56 | public IEnumerable? ContainerDivClasses { get; set; } 57 | 58 | /// 59 | /// CSS Classes to append to the <ul> element in the paging control. 60 | /// 61 | public IEnumerable? UlElementClasses { get; set; } 62 | 63 | /// 64 | /// Attributes to append to the <ul> element in the paging control 65 | /// 66 | public IDictionary? UlElementattributes { get; set; } 67 | 68 | /// 69 | /// CSS Classes to append to every <li> element in the paging control. 70 | /// 71 | public IEnumerable? LiElementClasses { get; set; } 72 | 73 | /// 74 | /// CSS Classes to appent to active <li> element in the paging control. 75 | /// 76 | public string ActiveLiElementClass { get; set; } 77 | 78 | /// 79 | /// CSS Classes to append to every <a> or <span> element that represent each page in the paging control. 80 | /// 81 | public IEnumerable? PageClasses { get; set; } 82 | 83 | /// 84 | /// CSS Classes to append to previous element in the paging control. 85 | /// 86 | public string PreviousElementClass { get; set; } 87 | 88 | /// 89 | /// CSS Classes to append to next element in the paging control. 90 | /// 91 | public string NextElementClass { get; set; } 92 | 93 | /// 94 | /// CSS Classes to append to Ellipses element in the paging control. 95 | /// 96 | public string EllipsesElementClass { get; set; } 97 | 98 | /// 99 | /// Specifies a CSS class to append to the first list item in the pager. If null or whitespace is defined, no additional class is added to first list item in list. 100 | /// 101 | public string? ClassToApplyToFirstListItemInPager { get; set; } 102 | 103 | /// 104 | /// Specifies a CSS class to append to the last list item in the pager. If null or whitespace is defined, no additional class is added to last list item in list. 105 | /// 106 | public string? ClassToApplyToLastListItemInPager { get; set; } 107 | 108 | /// 109 | /// If set to Always, always renders the paging control. If set to IfNeeded, render the paging control when there is more than one page. 110 | /// 111 | public PagedListDisplayMode Display { get; set; } 112 | 113 | /// 114 | /// If set to Always, render a hyperlink to the first page in the list. If set to IfNeeded, render the hyperlink only when the first page isn't visible in the paging control. 115 | /// 116 | public PagedListDisplayMode DisplayLinkToFirstPage { get; set; } 117 | 118 | /// 119 | /// If set to Always, render a hyperlink to the last page in the list. If set to IfNeeded, render the hyperlink only when the last page isn't visible in the paging control. 120 | /// 121 | public PagedListDisplayMode DisplayLinkToLastPage { get; set; } 122 | 123 | /// 124 | /// If set to Always, render a hyperlink to the previous page of the list. If set to IfNeeded, render the hyperlink only when there is a previous page in the list. 125 | /// 126 | public PagedListDisplayMode DisplayLinkToPreviousPage { get; set; } 127 | 128 | /// 129 | /// If set to Always, render a hyperlink to the next page of the list. If set to IfNeeded, render the hyperlink only when there is a next page in the list. 130 | /// 131 | public PagedListDisplayMode DisplayLinkToNextPage { get; set; } 132 | 133 | /// 134 | /// When true, includes hyperlinks for each page in the list. 135 | /// 136 | public bool DisplayLinkToIndividualPages { get; set; } 137 | 138 | /// 139 | /// When true, shows the current page number and the total number of pages in the list. 140 | /// 141 | /// 142 | /// "Page 3 of 8." 143 | /// 144 | public bool DisplayPageCountAndCurrentLocation { get; set; } 145 | 146 | /// 147 | /// When true, shows the one-based index of the first and last items on the page, and the total number of items in the list. 148 | /// 149 | /// 150 | /// "Showing items 75 through 100 of 183." 151 | /// 152 | public bool DisplayItemSliceAndTotal { get; set; } 153 | 154 | /// 155 | /// The maximum number of page numbers to display. Null displays all page numbers. 156 | /// 157 | public int? MaximumPageNumbersToDisplay { get; set; } 158 | 159 | /// 160 | /// If true, adds an ellipsis where not all page numbers are being displayed. 161 | /// 162 | /// 163 | /// "1 2 3 4 5 ...", 164 | /// "... 6 7 8 9 10 ...", 165 | /// "... 11 12 13 14 15" 166 | /// 167 | public bool DisplayEllipsesWhenNotShowingAllPageNumbers { get; set; } 168 | 169 | /// 170 | /// The pre-formatted text to display when not all page numbers are displayed at once. 171 | /// 172 | /// 173 | /// "..." 174 | /// 175 | public string EllipsesFormat { get; set; } 176 | 177 | /// 178 | /// The pre-formatted text to display inside the hyperlink to the first page. The one-based index of the page (always 1 in this case) is passed into the formatting function - use {0} to reference it. 179 | /// 180 | /// 181 | /// "<< First" 182 | /// 183 | public string LinkToFirstPageFormat { get; set; } 184 | 185 | /// 186 | /// The pre-formatted text to display inside the hyperlink to the previous page. The one-based index of the page is passed into the formatting function - use {0} to reference it. 187 | /// 188 | /// 189 | /// "< Previous" 190 | /// 191 | public string LinkToPreviousPageFormat { get; set; } 192 | 193 | /// 194 | /// The pre-formatted text to display inside the hyperlink to each individual page. The one-based index of the page is passed into the formatting function - use {0} to reference it. 195 | /// 196 | /// 197 | /// "{0}" 198 | /// 199 | public string LinkToIndividualPageFormat { get; set; } 200 | 201 | /// 202 | /// The pre-formatted text to display inside the hyperlink to the next page. The one-based index of the page is passed into the formatting function - use {0} to reference it. 203 | /// 204 | /// 205 | /// "Next >" 206 | /// 207 | public string LinkToNextPageFormat { get; set; } 208 | 209 | /// 210 | /// The pre-formatted text to display inside the hyperlink to the last page. The one-based index of the page is passed into the formatting function - use {0} to reference it. 211 | /// 212 | /// 213 | /// "Last >>" 214 | /// 215 | public string LinkToLastPageFormat { get; set; } 216 | 217 | /// 218 | /// The pre-formatted text to display when DisplayPageCountAndCurrentLocation is true. Use {0} to reference the current page and {1} to reference the total number of pages. 219 | /// 220 | /// 221 | /// "Page {0} of {1}." 222 | /// 223 | public string PageCountAndCurrentLocationFormat { get; set; } 224 | 225 | /// 226 | /// The pre-formatted text to display when DisplayItemSliceAndTotal is true. Use {0} to reference the first item on the page, {1} for the last item on the page, and {2} for the total number of items across all pages. 227 | /// 228 | /// 229 | /// "Showing items {0} through {1} of {2}." 230 | /// 231 | public string ItemSliceAndTotalFormat { get; set; } 232 | 233 | /// 234 | /// If set to , render an info at the beginning of the list. If set to , render the at the beginning of the list. 235 | /// 236 | public ItemSliceAndTotalPosition ItemSliceAndTotalPosition { get; set; } 237 | 238 | /// 239 | /// A function that will render each page number when specified (and DisplayLinkToIndividualPages is true). If no function is specified, the LinkToIndividualPageFormat value will be used instead. 240 | /// 241 | public Func? FunctionToDisplayEachPageNumber { get; set; } 242 | 243 | /// 244 | /// Text that will appear between each page number. If null or whitespace is specified, no delimiter will be shown. 245 | /// 246 | public string? DelimiterBetweenPageNumbers { get; set; } 247 | 248 | /// 249 | /// Also includes links to First and Last pages. 250 | /// 251 | public static PagedListRenderOptions Classic => new PagedListRenderOptions 252 | { 253 | DisplayLinkToFirstPage = PagedListDisplayMode.Never, 254 | DisplayLinkToLastPage = PagedListDisplayMode.Never, 255 | DisplayLinkToPreviousPage = PagedListDisplayMode.Always, 256 | DisplayLinkToNextPage = PagedListDisplayMode.Always 257 | }; 258 | 259 | /// 260 | /// Also includes links to First and Last pages. 261 | /// 262 | public static PagedListRenderOptions ClassicPlusFirstAndLast => new PagedListRenderOptions 263 | { 264 | DisplayLinkToFirstPage = PagedListDisplayMode.Always, 265 | DisplayLinkToLastPage = PagedListDisplayMode.Always, 266 | DisplayLinkToPreviousPage = PagedListDisplayMode.Always, 267 | DisplayLinkToNextPage = PagedListDisplayMode.Always 268 | }; 269 | 270 | /// 271 | /// Shows only the Previous and Next links. 272 | /// 273 | public static PagedListRenderOptions Minimal => new PagedListRenderOptions 274 | { 275 | DisplayLinkToFirstPage = PagedListDisplayMode.Never, 276 | DisplayLinkToLastPage = PagedListDisplayMode.Never, 277 | DisplayLinkToPreviousPage = PagedListDisplayMode.Always, 278 | DisplayLinkToNextPage = PagedListDisplayMode.Always, 279 | DisplayLinkToIndividualPages = false 280 | }; 281 | 282 | /// 283 | /// Shows Previous and Next links along with current page number and page count. 284 | /// 285 | public static PagedListRenderOptions MinimalWithPageCountText => new PagedListRenderOptions 286 | { 287 | DisplayLinkToFirstPage = PagedListDisplayMode.Never, 288 | DisplayLinkToLastPage = PagedListDisplayMode.Never, 289 | DisplayLinkToPreviousPage = PagedListDisplayMode.Always, 290 | DisplayLinkToNextPage = PagedListDisplayMode.Always, 291 | DisplayLinkToIndividualPages = false, 292 | DisplayPageCountAndCurrentLocation = true 293 | }; 294 | 295 | /// 296 | /// Shows Previous and Next links along with index of first and last items on page and total number of items across all pages. 297 | /// 298 | public static PagedListRenderOptions MinimalWithItemCountText => new PagedListRenderOptions 299 | { 300 | DisplayLinkToFirstPage = PagedListDisplayMode.Never, 301 | DisplayLinkToLastPage = PagedListDisplayMode.Never, 302 | DisplayLinkToPreviousPage = PagedListDisplayMode.Always, 303 | DisplayLinkToNextPage = PagedListDisplayMode.Always, 304 | DisplayLinkToIndividualPages = false, 305 | DisplayItemSliceAndTotal = true 306 | }; 307 | 308 | /// 309 | /// Shows only links to each individual page. 310 | /// 311 | public static PagedListRenderOptions PageNumbersOnly => new PagedListRenderOptions 312 | { 313 | DisplayLinkToFirstPage = PagedListDisplayMode.Never, 314 | DisplayLinkToLastPage = PagedListDisplayMode.Never, 315 | DisplayLinkToPreviousPage = PagedListDisplayMode.Never, 316 | DisplayLinkToNextPage = PagedListDisplayMode.Never, 317 | DisplayEllipsesWhenNotShowingAllPageNumbers = false 318 | }; 319 | 320 | /// 321 | /// Shows Next and Previous while limiting to a max of 5 page numbers at a time. 322 | /// 323 | public static PagedListRenderOptions OnlyShowFivePagesAtATime => new PagedListRenderOptions 324 | { 325 | DisplayLinkToFirstPage = PagedListDisplayMode.Never, 326 | DisplayLinkToLastPage = PagedListDisplayMode.Never, 327 | DisplayLinkToPreviousPage = PagedListDisplayMode.Always, 328 | DisplayLinkToNextPage = PagedListDisplayMode.Always, 329 | MaximumPageNumbersToDisplay = 5 330 | }; 331 | 332 | /// 333 | /// Twitter Bootstrap 2's basic pager format (just Previous and Next links). 334 | /// 335 | public static PagedListRenderOptions TwitterBootstrapPager => new PagedListRenderOptions 336 | { 337 | DisplayLinkToFirstPage = PagedListDisplayMode.Never, 338 | DisplayLinkToLastPage = PagedListDisplayMode.Never, 339 | DisplayLinkToPreviousPage = PagedListDisplayMode.Always, 340 | DisplayLinkToNextPage = PagedListDisplayMode.Always, 341 | DisplayLinkToIndividualPages = false, 342 | ContainerDivClasses = null, 343 | UlElementClasses = new[] { "pager" }, 344 | ClassToApplyToFirstListItemInPager = null, 345 | ClassToApplyToLastListItemInPager = null, 346 | LinkToPreviousPageFormat = "Previous", 347 | LinkToNextPageFormat = "Next" 348 | }; 349 | 350 | /// 351 | /// Twitter Bootstrap 2's basic pager format (just Previous and Next links), with aligned links. 352 | /// 353 | public static PagedListRenderOptions TwitterBootstrapPagerAligned => new PagedListRenderOptions 354 | { 355 | DisplayLinkToFirstPage = PagedListDisplayMode.Never, 356 | DisplayLinkToLastPage = PagedListDisplayMode.Never, 357 | DisplayLinkToPreviousPage = PagedListDisplayMode.Always, 358 | DisplayLinkToNextPage = PagedListDisplayMode.Always, 359 | DisplayLinkToIndividualPages = false, 360 | ContainerDivClasses = null, 361 | UlElementClasses = new[] { "pager" }, 362 | ClassToApplyToFirstListItemInPager = "previous", 363 | ClassToApplyToLastListItemInPager = "next", 364 | LinkToPreviousPageFormat = "← Older", 365 | LinkToNextPageFormat = "Newer →" 366 | }; 367 | 368 | /// 369 | /// An extension point which allows you to fully customize the anchor tags used for clickable pages, as well as navigation features such as Next, Last, etc. 370 | /// 371 | public Func? FunctionToTransformEachPageLink { get; set; } 372 | 373 | /// 374 | /// Enables ASP.NET MVC's unobtrusive AJAX feature. An XHR request will retrieve HTML from the clicked page and replace the innerHtml of the provided element ID. 375 | /// 376 | /// 377 | /// The ajax options that will put into the link 378 | /// The PagedListRenderOptions value passed in, with unobtrusive AJAX attributes added to the page links. 379 | public static PagedListRenderOptions EnableUnobtrusiveAjaxReplacing(PagedListRenderOptions pagedListRenderOptions, AjaxOptions? ajaxOptions) 380 | { 381 | if (pagedListRenderOptions is PagedListRenderOptions renderOptions) 382 | { 383 | renderOptions.FunctionToTransformEachPageLink = (liTagBuilder, aTagBuilder) => 384 | { 385 | var liClass = liTagBuilder.Attributes.ContainsKey("class") 386 | ? liTagBuilder.Attributes["class"] ?? string.Empty 387 | : string.Empty; 388 | 389 | if (ajaxOptions != null && !liClass.Contains("disabled") && !liClass.Contains("active")) 390 | { 391 | foreach (var ajaxOption in ajaxOptions.ToUnobtrusiveHtmlAttributes()) 392 | { 393 | aTagBuilder.Attributes.Add(ajaxOption.Key, ajaxOption.Value?.ToString() ?? ""); 394 | } 395 | } 396 | 397 | liTagBuilder.AppendHtml(aTagBuilder.ToString(TagRenderMode.Normal)); 398 | 399 | return liTagBuilder; 400 | }; 401 | } 402 | 403 | return pagedListRenderOptions; 404 | } 405 | 406 | /// 407 | /// Enables ASP.NET MVC's unobtrusive AJAX feature. An XHR request will retrieve HTML from the clicked page and replace the innerHtml of the provided element ID. 408 | /// 409 | /// The element ID ("my_id") of the element whose innerHtml should be replaced, if # is included at the start this will be removed. 410 | /// A default instance of PagedListRenderOptions value passed in, with unobtrusive AJAX attributes added to the page links. 411 | public static PagedListRenderOptions EnableUnobtrusiveAjaxReplacing(string id) 412 | { 413 | 414 | if (id.StartsWith("#")) 415 | { 416 | id = id.Substring(1); 417 | } 418 | 419 | var ajaxOptions = new AjaxOptions() 420 | { 421 | HttpMethod = "GET", 422 | InsertionMode = InsertionMode.Replace, 423 | UpdateTargetId = id 424 | }; 425 | 426 | return EnableUnobtrusiveAjaxReplacing(new PagedListRenderOptions(), ajaxOptions); 427 | } 428 | 429 | /// 430 | /// Enables ASP.NET MVC's unobtrusive AJAX feature. An XHR request will retrieve HTML from the clicked page and replace the innerHtml of the provided element ID. 431 | /// 432 | /// Ajax options that will be used to generate the unobstrusive tags on the link 433 | /// A default instance of PagedListRenderOptions value passed in, with unobtrusive AJAX attributes added to the page links. 434 | public static PagedListRenderOptions EnableUnobtrusiveAjaxReplacing(AjaxOptions? ajaxOptions) 435 | { 436 | return EnableUnobtrusiveAjaxReplacing(new PagedListRenderOptions(), ajaxOptions); 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/README.md: -------------------------------------------------------------------------------- 1 | # X.Web.PagedList 2 | 3 | [![NuGet Version](http://img.shields.io/nuget/v/X.PagedList.Mvc.Core.svg?style=flat)](https://www.nuget.org/packages/X.PagedList.Mvc.Core/) 4 | [![Twitter URL](https://img.shields.io/twitter/url/https/x.com/andrew_gubskiy.svg?style=social&label=Follow%20me!)](https://x.com/intent/user?screen_name=andrew_gubskiy) 5 | 6 | 7 | ## What is this? 8 | The X.PagedList.Mvc.Core library is a tool designed for ASP.NET Core applications that facilitates pagination of 9 | collections like IEnumerable and IQueryable. 10 | 11 | ## How to use 12 | You can find all information about how to use X.PagedList libraries in [Wiki](https://github.com/dncuug/X.PagedList/wiki) 13 | 14 | ## Get a digital subscription for project news 15 | [Subscribe](https://x.com/intent/user?screen_name=andrew_gubskiy) to my X to keep up-to-date with project news and receive announcements. -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/TagBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.Encodings.Web; 3 | using JetBrains.Annotations; 4 | using Microsoft.AspNetCore.Html; 5 | using Microsoft.AspNetCore.Mvc.Rendering; 6 | 7 | namespace X.PagedList.Mvc.Core; 8 | 9 | [PublicAPI] 10 | public static class TagBuilderExtensions 11 | { 12 | public static void AddCssClass(this TagBuilder tagBuilder, string value) 13 | { 14 | tagBuilder.AddCssClass(value); 15 | } 16 | 17 | public static void AppendHtml(this TagBuilder tagBuilder, string innerHtml) 18 | { 19 | tagBuilder.InnerHtml.AppendHtml(innerHtml); 20 | } 21 | 22 | public static void MergeAttribute(this TagBuilder tagBuilder, string key, string? value) 23 | { 24 | tagBuilder.MergeAttribute(key, value); 25 | } 26 | 27 | public static void SetInnerText(this TagBuilder tagBuilder, string innerText) 28 | { 29 | tagBuilder.InnerHtml.SetContent(innerText); 30 | } 31 | 32 | public static string ToString(this TagBuilder tagBuilder, TagRenderMode renderMode, HtmlEncoder? encoder = null) 33 | { 34 | encoder ??= HtmlEncoder.Create(new TextEncoderSettings()); 35 | 36 | using (var writer = new StringWriter()) 37 | { 38 | tagBuilder.WriteTo(writer, encoder); 39 | 40 | return writer.ToString(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/TagBuilderFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Rendering; 2 | 3 | namespace X.PagedList.Mvc.Core; 4 | 5 | public interface ITagBuilderFactory 6 | { 7 | TagBuilder Create(string tagName); 8 | } 9 | 10 | internal sealed class TagBuilderFactory : ITagBuilderFactory 11 | { 12 | public TagBuilder Create(string tagName) => new(tagName); 13 | } 14 | -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/X.PagedList.Mvc.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | X.PagedList.Mvc.Core 5 | ASP.NET Core extensions for implementing paging in web applications. 6 | 7 | default 8 | net6.0;net7.0;net8.0 9 | 10 | paging pagedlist paged list web mvc 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/X.PagedList.Mvc.Core/xpagedlist.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dncuug/X.PagedList/f68e3e69d25bcbc66a1af4ecf08a52d6c2e1a141/src/X.PagedList.Mvc.Core/xpagedlist.snk -------------------------------------------------------------------------------- /src/X.PagedList/BasePagedList.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | namespace X.PagedList; 7 | 8 | /// 9 | /// Represents a subset of a collection of objects that can be individually accessed by index and containing 10 | /// metadata about the superset collection of objects this subset was created from. 11 | /// 12 | /// 13 | /// Represents a subset of a collection of objects that can be individually accessed by index and containing 14 | /// metadata about the superset collection of objects this subset was created from. 15 | /// 16 | /// The type of object the collection should contain. 17 | /// 18 | /// 19 | [PublicAPI] 20 | public abstract class BasePagedList : IPagedList 21 | { 22 | protected List Subset = new(); 23 | 24 | public const int DefaultPageSize = 100; 25 | 26 | /// 27 | /// Total number of subsets within the superset. 28 | /// 29 | public int PageCount { get; protected set; } 30 | 31 | /// 32 | /// Total number of objects contained within the superset. 33 | /// 34 | public int TotalItemCount { get; protected set; } 35 | 36 | /// 37 | /// One-based index of this subset within the superset, zero if the superset is empty. 38 | /// 39 | public int PageNumber { get; protected set; } 40 | 41 | /// 42 | /// Maximum size any individual subset. 43 | /// 44 | public int PageSize { get; protected set; } 45 | 46 | /// 47 | /// Returns true if the superset is not empty and PageNumber is less than or equal to PageCount and this 48 | /// is NOT the first subset within the superset. 49 | /// 50 | public bool HasPreviousPage { get; protected set; } 51 | 52 | /// 53 | /// Returns true if the superset is not empty and PageNumber is less than or equal to PageCount and this 54 | /// is NOT the last subset within the superset. 55 | /// 56 | public bool HasNextPage { get; protected set; } 57 | 58 | /// 59 | /// Returns true if the superset is not empty and PageNumber is less than or equal to PageCount and this is 60 | /// the first subset within the superset. 61 | /// 62 | public bool IsFirstPage { get; protected set; } 63 | 64 | /// 65 | /// Returns true if the superset is not empty and PageNumber is less than or equal to PageCount and this is 66 | /// the last subset within the superset. 67 | /// 68 | public bool IsLastPage { get; protected set; } 69 | 70 | /// 71 | /// One-based index of the first item in the paged subset, zero if the superset is empty or PageNumber 72 | /// is greater than PageCount. 73 | /// 74 | public int FirstItemOnPage { get; protected set; } 75 | 76 | /// 77 | /// One-based index of the last item in the paged subset, zero if the superset is empty or PageNumber is 78 | /// greater than PageCount. 79 | /// 80 | public int LastItemOnPage { get; protected set; } 81 | 82 | /// 83 | /// Parameterless constructor. 84 | /// 85 | protected internal BasePagedList() 86 | { 87 | } 88 | 89 | /// 90 | /// Initializes a new instance of a type deriving from and sets properties 91 | /// needed to calculate position and size data on the subset and superset. 92 | /// 93 | /// The one-based index of the subset of objects contained by this instance. 94 | /// The maximum size of any individual subset. 95 | /// The size of the superset. 96 | protected internal BasePagedList(int pageNumber, int pageSize, int totalItemCount) 97 | { 98 | if (pageNumber < 1) 99 | { 100 | throw new ArgumentOutOfRangeException($"pageNumber = {pageNumber}. PageNumber cannot be below 1."); 101 | } 102 | 103 | if (pageSize < 1) 104 | { 105 | throw new ArgumentOutOfRangeException($"pageSize = {pageSize}. PageSize cannot be less than 1."); 106 | } 107 | 108 | if (totalItemCount < 0) 109 | { 110 | throw new ArgumentOutOfRangeException( 111 | $"totalItemCount = {totalItemCount}. TotalItemCount cannot be less than 0."); 112 | } 113 | 114 | // set source to blank list if superset is null to prevent exceptions 115 | TotalItemCount = totalItemCount; 116 | PageSize = pageSize; 117 | PageNumber = pageNumber; 118 | 119 | PageCount = TotalItemCount > 0 120 | ? (int)Math.Ceiling(TotalItemCount / (double)PageSize) 121 | : 0; 122 | 123 | bool pageNumberIsGood = PageCount > 0 && PageNumber <= PageCount; 124 | 125 | HasPreviousPage = pageNumberIsGood && PageNumber > 1; 126 | HasNextPage = pageNumberIsGood && PageNumber < PageCount; 127 | IsFirstPage = pageNumberIsGood && PageNumber == 1; 128 | IsLastPage = pageNumberIsGood && PageNumber == PageCount; 129 | 130 | int numberOfFirstItemOnPage = (PageNumber - 1) * PageSize + 1; 131 | 132 | FirstItemOnPage = pageNumberIsGood ? numberOfFirstItemOnPage : 0; 133 | 134 | int numberOfLastItemOnPage = numberOfFirstItemOnPage + PageSize - 1; 135 | 136 | LastItemOnPage = pageNumberIsGood 137 | ? numberOfLastItemOnPage > TotalItemCount ? TotalItemCount : numberOfLastItemOnPage 138 | : 0; 139 | } 140 | 141 | /// 142 | /// Returns an enumerator that iterates through the BasePagedList<T>. 143 | /// 144 | /// A BasePagedList<T>.Enumerator for the BasePagedList<T>. 145 | public IEnumerator GetEnumerator() 146 | { 147 | return Subset.GetEnumerator(); 148 | } 149 | 150 | /// 151 | /// Returns an enumerator that iterates through the BasePagedList<T>. 152 | /// 153 | /// A BasePagedList<T>.Enumerator for the BasePagedList<T>. 154 | IEnumerator IEnumerable.GetEnumerator() 155 | { 156 | return GetEnumerator(); 157 | } 158 | 159 | /// 160 | /// Gets the element at the specified index. 161 | /// 162 | /// The zero-based index of the element to get. 163 | public T this[int index] => Subset[index]; 164 | 165 | /// 166 | /// Gets the number of elements contained on this page. 167 | /// 168 | public virtual int Count => Subset.Count; 169 | 170 | /// 171 | /// Gets a non-enumerable copy of this paged list. 172 | /// 173 | /// A non-enumerable copy of this paged list. 174 | [Obsolete("This method will be removed in future versions")] 175 | public PagedListMetaData GetMetaData() => new(this); 176 | } -------------------------------------------------------------------------------- /src/X.PagedList/Extensions/PagedListExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using JetBrains.Annotations; 6 | 7 | namespace X.PagedList.Extensions; 8 | 9 | /// 10 | /// Container for extension methods designed to simplify the creation of instances of . 11 | /// 12 | [PublicAPI] 13 | public static class PagedListExtensions 14 | { 15 | /// 16 | /// Splits a collection of objects into n pages with an (for example, if I have a list of 45 shoes and 17 | /// say 'shoes.Split(5)' I will now have 4 pages of 10 shoes and 1 page of 5 shoes. 18 | /// 19 | /// The type of object the collection should contain. 20 | /// The collection of objects to be divided into subsets. 21 | /// The number of pages this collection should be split into. 22 | /// A subset of this collection of objects, split into n pages. 23 | public static IEnumerable> Split(this IEnumerable superset, int numberOfPages) 24 | { 25 | if (superset == null) 26 | { 27 | throw new ArgumentNullException(nameof(superset)); 28 | } 29 | 30 | int take = Convert.ToInt32(Math.Ceiling(superset.Count() / (double)numberOfPages)); 31 | var result = new List>(); 32 | 33 | for (int i = 0; i < numberOfPages; i++) 34 | { 35 | var chunk = superset.Skip(i * take).Take(take).ToList(); 36 | 37 | if (chunk.Any()) 38 | { 39 | result.Add(chunk); 40 | }; 41 | } 42 | 43 | return result; 44 | } 45 | 46 | /// 47 | /// Splits a collection of objects into an unknown number of pages with n items per page (for example, 48 | /// if I have a list of 45 shoes and say 'shoes.Partition(10)' I will now have 4 pages of 10 shoes and 49 | /// 1 page of 5 shoes. 50 | /// 51 | /// The type of object the collection should contain. 52 | /// The collection of objects to be divided into subsets. 53 | /// The maximum number of items each page may contain. 54 | /// A subset of this collection of objects, split into pages of maximum size n. 55 | public static IEnumerable> Partition(this IEnumerable superset, int pageSize) 56 | { 57 | if (superset == null) 58 | { 59 | throw new ArgumentNullException(nameof(superset)); 60 | } 61 | 62 | // Cache this to avoid evaluating it twice 63 | int count = superset.Count(); 64 | 65 | if (count < pageSize) 66 | { 67 | yield return superset; 68 | } 69 | else 70 | { 71 | var numberOfPages = Math.Ceiling(count / (double)pageSize); 72 | 73 | for (var i = 0; i < numberOfPages; i++) 74 | { 75 | yield return superset.Skip(pageSize * i).Take(pageSize); 76 | } 77 | } 78 | } 79 | 80 | /// 81 | /// Cast to Custom Type 82 | /// 83 | /// Source 84 | /// Selector 85 | /// Input Type 86 | /// Result Type 87 | /// New PagedList 88 | public static IPagedList Select(this IPagedList source, Func selector) 89 | { 90 | var subset = ((IEnumerable)source).Select(selector); 91 | 92 | return new PagedList(source, subset); 93 | } 94 | 95 | /// 96 | /// Creates a subset of this collection of objects that can be individually accessed by index and containing 97 | /// metadata about the collection of objects the subset was created from. 98 | /// 99 | /// The type of object the collection should contain. 100 | /// 101 | /// The collection of objects to be divided into subsets. If the 102 | /// collection implements , it will be treated as such. 103 | /// 104 | /// A subset of this collection of objects that can be individually accessed by index and containing 105 | /// metadata about the collection of objects the subset was created from. 106 | /// 107 | public static IPagedList ToPagedList(this IEnumerable superset) 108 | { 109 | if (superset == null) 110 | { 111 | throw new ArgumentNullException(nameof(superset)); 112 | } 113 | 114 | int supersetSize = superset.Count(); 115 | int pageSize = supersetSize == 0 ? 1 : supersetSize; 116 | 117 | return new PagedList(superset, 1, pageSize); 118 | } 119 | 120 | /// 121 | /// Creates a subset of this collection of objects that can be individually accessed by index and containing 122 | /// metadata about the collection of objects the subset was created from. 123 | /// 124 | /// The type of object the collection should contain. 125 | /// 126 | /// The collection of objects to be divided into subsets. If the collection 127 | /// implements , it will be treated as such. 128 | /// 129 | /// 130 | /// The one-based index of the subset of objects to be contained by this instance. 131 | /// 132 | /// The maximum size of any individual subset. 133 | /// A subset of this collection of objects that can be individually accessed by index and containing 134 | /// metadata about the collection of objects the subset was created from. 135 | /// 136 | public static IPagedList ToPagedList(this IEnumerable superset, int pageNumber, int pageSize) 137 | { 138 | if (superset == null) 139 | { 140 | throw new ArgumentNullException(nameof(superset)); 141 | } 142 | 143 | return new PagedList(superset, pageNumber, pageSize); 144 | } 145 | 146 | /// 147 | /// Creates a subset of this collection of objects that can be individually accessed by index and containing 148 | /// metadata about the collection of objects the subset was created from. 149 | /// 150 | /// The type of object the collection should contain. 151 | /// 152 | /// The collection of objects to be divided into subsets. If the collection 153 | /// implements , it will be treated as such. 154 | /// 155 | /// The one-based index of the subset of objects to be contained by this instance. 156 | /// The maximum size of any individual subset. 157 | /// The total size of set 158 | /// 159 | /// A subset of this collection of objects that can be individually accessed by index and containing metadata 160 | /// about the collection of objects the subset was created from. 161 | /// 162 | public static IPagedList ToPagedList(this IEnumerable superset, int pageNumber, int pageSize, int? totalSetCount) 163 | { 164 | if (superset == null) 165 | { 166 | throw new ArgumentNullException(nameof(superset)); 167 | } 168 | 169 | return ToPagedList(superset.AsQueryable(), pageNumber, pageSize, totalSetCount); 170 | } 171 | 172 | /// 173 | /// Creates a subset of this collection of objects that can be individually accessed by index and containing 174 | /// metadata about the collection of objects the subset was created from. 175 | /// 176 | /// The type of object the collection should contain. 177 | /// Type For Compare 178 | /// 179 | /// The collection of objects to be divided into subsets. If the collection 180 | /// implements , it will be treated as such. 181 | /// 182 | /// Expression for Order 183 | /// The one-based index of the subset of objects to be contained by this instance. 184 | /// The maximum size of any individual subset. 185 | /// 186 | /// A subset of this collection of objects that can be individually accessed by index and containing metadata 187 | /// about the collection of objects the subset was created from. 188 | /// 189 | public static IPagedList ToPagedList(this IEnumerable superset, Expression> keySelector, int pageNumber, int pageSize) 190 | { 191 | if (superset == null) 192 | { 193 | throw new ArgumentNullException(nameof(superset)); 194 | } 195 | 196 | return new PagedList(superset.AsQueryable(), keySelector, pageNumber, pageSize); 197 | } 198 | 199 | /// 200 | /// Creates a subset of this collection of objects that can be individually accessed by index and containing 201 | /// metadata about the collection of objects the subset was created from. 202 | /// 203 | /// The type of object the collection should contain. 204 | /// 205 | /// The collection of objects to be divided into subsets. If the collection 206 | /// implements , it will be treated as such. 207 | /// 208 | /// 209 | /// The one-based index of the subset of objects to be contained by this instance. 210 | /// 211 | /// The maximum size of any individual subset. 212 | /// The total size of set 213 | /// A subset of this collection of objects that can be individually accessed by index and containing 214 | /// metadata about the collection of objects the subset was created from. 215 | /// 216 | public static IPagedList ToPagedList(this IQueryable superset, int pageNumber, int pageSize, int? totalSetCount) 217 | { 218 | if (superset == null) 219 | { 220 | throw new ArgumentNullException(nameof(superset)); 221 | } 222 | 223 | if (pageNumber < 1) 224 | { 225 | throw new ArgumentOutOfRangeException($"pageNumber = {pageNumber}. PageNumber cannot be below 1."); 226 | } 227 | 228 | if (pageSize < 1) 229 | { 230 | throw new ArgumentOutOfRangeException($"pageSize = {pageSize}. PageSize cannot be less than 1."); 231 | } 232 | 233 | int totalCount = totalSetCount ?? superset.Count(); 234 | 235 | List subset; 236 | 237 | if (totalCount > 0) 238 | { 239 | var skip = (pageNumber - 1) * pageSize; 240 | 241 | subset = superset.Skip(skip).Take(pageSize).ToList(); 242 | } 243 | else 244 | { 245 | subset = new List(); 246 | } 247 | 248 | return new StaticPagedList(subset, pageNumber, pageSize, totalCount); 249 | } 250 | 251 | /// 252 | /// Creates a subset of this collection of objects that can be individually accessed by index and containing 253 | /// metadata about the collection of objects the subset was created from. 254 | /// 255 | /// The type of object the collection should contain. 256 | /// Type For Compare 257 | /// 258 | /// The collection of objects to be divided into subsets. If the collection 259 | /// implements , it will be treated as such. 260 | /// 261 | /// Expression for Order 262 | /// 263 | /// The one-based index of the subset of objects to be contained by this instance. 264 | /// 265 | /// The maximum size of any individual subset. 266 | /// 267 | /// A subset of this collection of objects that can be individually accessed by index and containing 268 | /// metadata about the collection of objects the subset was created from. 269 | /// 270 | public static IPagedList ToPagedList(this IQueryable superset, Expression> keySelector, int pageNumber, int pageSize) 271 | { 272 | if (superset == null) 273 | { 274 | throw new ArgumentNullException(nameof(superset)); 275 | } 276 | 277 | return new PagedList(superset, keySelector, pageNumber, pageSize); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/X.PagedList/IPagedList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | using System.Collections.Generic; 4 | 5 | namespace X.PagedList; 6 | 7 | /// 8 | /// Represents a subset of a collection of objects that can be individually accessed by index and containing 9 | /// metadata about the superset collection of objects this subset was created from. 10 | /// 11 | /// The type of object the collection should contain. 12 | /// 13 | [PublicAPI] 14 | public interface IPagedList : IPagedList, IReadOnlyList 15 | { 16 | /// 17 | /// Gets a non-enumerable copy of this paged list. 18 | /// 19 | /// A non-enumerable copy of this paged list. 20 | [Obsolete("This method will be removed in future versions")] 21 | PagedListMetaData GetMetaData(); 22 | } 23 | 24 | /// 25 | /// Represents a subset of a collection of objects that can be individually accessed by index and containing 26 | /// metadata about the superset collection of objects this subset was created from. 27 | /// 28 | public interface IPagedList 29 | { 30 | /// 31 | /// Total number of subsets within the superset. 32 | /// 33 | int PageCount { get; } 34 | 35 | /// 36 | /// Total number of objects contained within the superset. 37 | /// 38 | int TotalItemCount { get; } 39 | 40 | /// 41 | /// One-based index of this subset within the superset, zero if the superset is empty. 42 | /// 43 | int PageNumber { get; } 44 | 45 | /// 46 | /// Maximum size any individual subset. 47 | /// 48 | int PageSize { get; } 49 | 50 | /// 51 | /// Returns true if the superset is not empty and PageNumber is less than or equal to PageCount and this 52 | /// is NOT the first subset within the superset. 53 | /// 54 | bool HasPreviousPage { get; } 55 | 56 | /// 57 | /// Returns true if the superset is not empty and PageNumber is less than or equal to PageCount and this 58 | /// is NOT the last subset within the superset. 59 | /// 60 | bool HasNextPage { get; } 61 | 62 | /// 63 | /// Returns true if the superset is not empty and PageNumber is less than or equal to PageCount and this 64 | /// is the first subset within the superset. 65 | /// 66 | bool IsFirstPage { get; } 67 | 68 | /// 69 | /// Returns true if the superset is not empty and PageNumber is less than or equal to PageCount and this 70 | /// is the last subset within the superset. 71 | /// 72 | bool IsLastPage { get; } 73 | 74 | /// 75 | /// One-based index of the first item in the paged subset, zero if the superset is empty or PageNumber 76 | /// is greater than PageCount. 77 | /// 78 | int FirstItemOnPage { get; } 79 | 80 | /// 81 | /// One-based index of the last item in the paged subset, zero if the superset is empty or PageNumber 82 | /// is greater than PageCount. 83 | /// 84 | int LastItemOnPage { get; } 85 | } 86 | -------------------------------------------------------------------------------- /src/X.PagedList/OrderedPagedList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using JetBrains.Annotations; 6 | 7 | namespace X.PagedList; 8 | 9 | /// 10 | /// Represents a subset of a collection of objects that can be individually accessed by index and containing 11 | /// metadata about the superset collection of objects this subset was created from. 12 | /// This implementation support ordering by key. 13 | /// 14 | /// 15 | /// Represents a subset of a collection of objects that can be individually accessed by index and containing 16 | /// metadata about the superset collection of objects this subset was created from. 17 | /// 18 | /// The type of object the collection should contain. 19 | /// 20 | [PublicAPI] 21 | public class PagedList : BasePagedList 22 | { 23 | /// 24 | /// Initializes a new instance of the class that divides the supplied superset into 25 | /// subsets the size of the supplied pageSize. The instance then only contains the objects contained in the 26 | /// subset specified by index. 27 | /// 28 | /// 29 | /// The collection of objects to be divided into subsets. If the collection 30 | /// implements , it will be treated as such. 31 | /// 32 | /// Expression for OrderBy 33 | /// 34 | /// The one-based index of the subset of objects to be contained by this instance. 35 | /// 36 | /// The maximum size of any individual subset. 37 | /// 38 | /// The specified index cannot be less than zero. 39 | /// The specified page size cannot be less than one. 40 | public PagedList(IQueryable superset, Expression> keySelectorExpression, int pageNumber, int pageSize) 41 | : base(pageNumber, pageSize, superset.Count()) 42 | { 43 | if (superset is null) 44 | { 45 | throw new ArgumentNullException(nameof(superset)); 46 | } 47 | 48 | // add items to internal list 49 | if (TotalItemCount > 0) 50 | { 51 | var skip = (pageNumber - 1) * pageSize; 52 | 53 | Subset = superset.OrderBy(keySelectorExpression).Skip(skip).Take(pageSize).ToList(); 54 | } 55 | } 56 | 57 | /// 58 | /// Initializes a new instance of the class that divides the supplied superset into 59 | /// subsets the size of the supplied pageSize. The instance then only contains the objects contained in the 60 | /// subset specified by index. 61 | /// 62 | /// 63 | /// The collection of objects to be divided into subsets. If the collection 64 | /// implements , it will be treated as such. 65 | /// 66 | /// Function delegate for OrderBy 67 | /// 68 | /// The one-based index of the subset of objects to be contained by this instance. 69 | /// 70 | /// The maximum size of any individual subset. 71 | /// 72 | /// The specified index cannot be less than zero. 73 | /// The specified page size cannot be less than one. 74 | public PagedList(IEnumerable superset, Func keySelectorMethod, int pageNumber, int pageSize) 75 | : base(pageNumber, pageSize, superset.Count()) 76 | { 77 | if (superset is null) 78 | { 79 | throw new ArgumentNullException(nameof(superset)); 80 | } 81 | 82 | // add items to the internal list 83 | if (TotalItemCount > 0) 84 | { 85 | var skip = (pageNumber - 1) * pageSize; 86 | 87 | Subset = superset.OrderBy(keySelectorMethod).Skip(skip).Take(pageSize).ToList(); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/X.PagedList/PagedList.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace X.PagedList; 7 | 8 | /// 9 | /// Represents a subset of a collection of objects that can be individually accessed by index and containing 10 | /// metadata about the superset collection of objects this subset was created from. 11 | /// 12 | /// The type of object the collection should contain. 13 | /// 14 | /// 15 | /// 16 | /// 17 | public class PagedList : BasePagedList 18 | { 19 | /// 20 | /// Initializes a new instance of the class that divides the supplied superset 21 | /// into subsets the size of the supplied pageSize. The instance then only contains the objects contained 22 | /// in the subset specified by index. 23 | /// 24 | /// 25 | /// The collection of objects to be divided into subsets. If the collection 26 | /// implements , it will be treated as such. 27 | /// 28 | /// 29 | /// The one-based index of the subset of objects to be contained by this instance. 30 | /// 31 | /// The maximum size of any individual subset. 32 | /// 33 | /// The specified index cannot be less than zero. 34 | /// The specified page size cannot be less than one. 35 | [PublicAPI] 36 | public PagedList(IQueryable superset, int pageNumber, int pageSize) 37 | : base(pageNumber, pageSize, superset.Count()) 38 | { 39 | if (superset is null) 40 | { 41 | throw new ArgumentNullException(nameof(superset)); 42 | } 43 | 44 | // add items to internal list 45 | if (TotalItemCount > 0) 46 | { 47 | var skip = (pageNumber - 1) * pageSize; 48 | 49 | Subset = superset.Skip(skip).Take(pageSize).ToList(); 50 | } 51 | } 52 | 53 | /// 54 | /// Initializes a new instance of the class that divides the supplied superset 55 | /// into subsets the size of the supplied pageSize. The instance then only contains the objects contained in 56 | /// the subset specified by index. 57 | /// 58 | /// 59 | /// The collection of objects to be divided into subsets. If the collection 60 | /// implements , it will be treated as such. 61 | /// 62 | /// The one-based index of the subset of objects to be contained by this instance. 63 | /// The maximum size of any individual subset. 64 | /// 65 | /// The specified index cannot be less than zero. 66 | /// The specified page size cannot be less than one. 67 | public PagedList(IEnumerable superset, int pageNumber, int pageSize) 68 | : this(superset.AsQueryable(), pageNumber, pageSize) 69 | { 70 | } 71 | 72 | /// 73 | /// For Clone PagedList 74 | /// 75 | /// Source PagedList 76 | /// Source collection 77 | public PagedList(IPagedList pagedList, IEnumerable collection) 78 | { 79 | TotalItemCount = pagedList.TotalItemCount; 80 | PageSize = pagedList.PageSize; 81 | PageNumber = pagedList.PageNumber; 82 | PageCount = pagedList.PageCount; 83 | HasPreviousPage = pagedList.HasPreviousPage; 84 | HasNextPage = pagedList.HasNextPage; 85 | IsFirstPage = pagedList.IsFirstPage; 86 | IsLastPage = pagedList.IsLastPage; 87 | FirstItemOnPage = pagedList.FirstItemOnPage; 88 | LastItemOnPage = pagedList.LastItemOnPage; 89 | 90 | Subset.AddRange(collection); 91 | 92 | if (base.Count > PageSize) 93 | { 94 | throw new Exception($"{nameof(collection)} size can't be greater than PageSize"); 95 | } 96 | } 97 | 98 | /// 99 | /// Create empty static paged list 100 | /// 101 | /// 102 | /// 103 | [PublicAPI] 104 | public static PagedList Empty(int pageSize = DefaultPageSize) => 105 | new(Array.Empty(), 1, pageSize); 106 | } 107 | -------------------------------------------------------------------------------- /src/X.PagedList/PagedListMetaData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace X.PagedList; 5 | 6 | /// 7 | /// Non-enumerable version of the PagedList class. 8 | /// 9 | [PublicAPI] 10 | [Obsolete("This class will be removed in future versions")] 11 | public class PagedListMetaData : IPagedList 12 | { 13 | /// 14 | /// Protected constructor that allows for instantiation without passing in a separate list. 15 | /// 16 | protected PagedListMetaData() 17 | { 18 | } 19 | 20 | /// 21 | /// Non-enumerable version of the PagedList class. 22 | /// 23 | /// A PagedList (likely enumerable) to copy metadata from. 24 | public PagedListMetaData(IPagedList pagedList) 25 | { 26 | PageCount = pagedList.PageCount; 27 | TotalItemCount = pagedList.TotalItemCount; 28 | PageNumber = pagedList.PageNumber; 29 | PageSize = pagedList.PageSize; 30 | HasPreviousPage = pagedList.HasPreviousPage; 31 | HasNextPage = pagedList.HasNextPage; 32 | IsFirstPage = pagedList.IsFirstPage; 33 | IsLastPage = pagedList.IsLastPage; 34 | FirstItemOnPage = pagedList.FirstItemOnPage; 35 | LastItemOnPage = pagedList.LastItemOnPage; 36 | } 37 | 38 | /// 39 | /// Total number of subsets within the superset. 40 | /// 41 | public int PageCount { get; protected set; } 42 | 43 | /// 44 | /// Total number of objects contained within the superset. 45 | /// 46 | public int TotalItemCount { get; protected set; } 47 | 48 | /// 49 | /// One-based index of this subset within the superset, zero if the superset is empty. 50 | /// 51 | public int PageNumber { get; protected set; } 52 | 53 | /// 54 | /// Maximum size any individual subset. 55 | /// 56 | public int PageSize { get; protected set; } 57 | 58 | /// 59 | /// Returns true if the superset is not empty and PageNumber is less than or equal to PageCount and this 60 | /// is NOT the first subset within the superset. 61 | /// 62 | public bool HasPreviousPage { get; protected set; } 63 | 64 | /// 65 | /// Returns true if the superset is not empty and PageNumber is less than or equal to PageCount and this 66 | /// is NOT the last subset within the superset. 67 | /// 68 | public bool HasNextPage { get; protected set; } 69 | 70 | /// 71 | /// Returns true if the superset is not empty and PageNumber is less than or equal to PageCount and this is 72 | /// the first subset within the superset. 73 | /// 74 | public bool IsFirstPage { get; protected set; } 75 | 76 | /// 77 | /// Returns true if the superset is not empty and PageNumber is less than or equal to PageCount and this is 78 | /// the last subset within the superset. 79 | /// 80 | public bool IsLastPage { get; protected set; } 81 | 82 | /// 83 | /// One-based index of the first item in the paged subset, zero if the superset is empty or PageNumber 84 | /// is greater than PageCount. 85 | /// 86 | public int FirstItemOnPage { get; protected set; } 87 | 88 | /// 89 | /// One-based index of the last item in the paged subset, zero if the superset is empty or PageNumber is 90 | /// greater than PageCount. 91 | /// 92 | public int LastItemOnPage { get; protected set; } 93 | } 94 | -------------------------------------------------------------------------------- /src/X.PagedList/StaticPagedList.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace X.PagedList; 7 | 8 | /// 9 | /// Represents a subset of a collection of objects that can be individually accessed by index and containing 10 | /// metadata about the superset collection of objects this subset was created from. 11 | /// 12 | /// 13 | /// Represents a subset of a collection of objects that can be individually accessed by index and containing 14 | /// metadata about the superset collection of objects this subset was created from. 15 | /// 16 | /// The type of object the collection should contain. 17 | /// 18 | /// 19 | /// 20 | /// 21 | [PublicAPI] 22 | public class StaticPagedList : BasePagedList 23 | { 24 | /// 25 | /// Initializes a new instance of the class that contains the already 26 | /// divided subset and information about the size of the superset and the subset's position within it. 27 | /// 28 | /// The single subset this collection should represent. 29 | /// 30 | /// Supply the ".MetaData" property of an existing IPagedList instance to recreate 31 | /// it here (such as when creating a new instance of a PagedList after having used Automapper to convert 32 | /// its contents to a DTO.) 33 | /// 34 | /// The specified index cannot be less than zero. 35 | /// The specified page size cannot be less than one. 36 | public StaticPagedList(IEnumerable subset, IPagedList metaData) 37 | : this(subset, metaData.PageNumber, metaData.PageSize, metaData.TotalItemCount) 38 | { 39 | } 40 | 41 | /// 42 | /// Initializes a new instance of the class that contains the already 43 | /// divided subset and information about the size of the superset and the subset's position within it. 44 | /// 45 | /// The single subset this collection should represent. 46 | /// The one-based index of the subset of objects contained by this instance. 47 | /// The maximum size of any individual subset. 48 | /// The size of the superset. 49 | /// The specified index cannot be less than zero. 50 | /// The specified page size cannot be less than one. 51 | public StaticPagedList(IEnumerable subset, int pageNumber, int pageSize, int totalItemCount) 52 | : base(pageNumber, pageSize, totalItemCount) 53 | { 54 | Subset = subset.ToList(); 55 | } 56 | 57 | /// 58 | /// Create an empty static paged list 59 | /// 60 | /// 61 | /// 62 | [PublicAPI] 63 | public static StaticPagedList Empty(int pageSize = DefaultPageSize) => 64 | new(Array.Empty(), 1, pageSize, 0); 65 | } 66 | -------------------------------------------------------------------------------- /src/X.PagedList/X.PagedList.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | X.PagedList 5 | Library for easily paging through any IEnumerable/IQueryable in .NET 6 | 7 | default 8 | net6.0;net7.0;net8.0;netstandard2.0;netstandard2.1 9 | 10 | paging pagedlist paged list 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/X.PagedList/xpagedlist.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dncuug/X.PagedList/f68e3e69d25bcbc66a1af4ecf08a52d6c2e1a141/src/X.PagedList/xpagedlist.snk -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/Blog.cs: -------------------------------------------------------------------------------- 1 | namespace X.PagedList.Tests; 2 | 3 | public class Blog 4 | { 5 | public int BlogID { get; set; } 6 | 7 | public string? Name { get; set; } 8 | 9 | public string? Url { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/DbContext.cs: -------------------------------------------------------------------------------- 1 | namespace X.PagedList.Tests; 2 | 3 | public class DbContext { } 4 | -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/DbSet.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace X.PagedList.Tests; 4 | 5 | public class DbSet : List { } 6 | -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/IDbAsyncEnumerable.cs: -------------------------------------------------------------------------------- 1 | namespace X.PagedList.Tests; 2 | 3 | public interface IDbAsyncEnumerable { IDbAsyncEnumerator GetAsyncEnumerator(); } 4 | public interface IDbAsyncEnumerable : IDbAsyncEnumerable { } 5 | -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/IDbAsyncEnumerator.cs: -------------------------------------------------------------------------------- 1 | namespace X.PagedList.Tests; 2 | 3 | public interface IDbAsyncEnumerator : IDbAsyncEnumerator { } 4 | public interface IDbAsyncEnumerator { object? Current { get; } } 5 | -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/IDbAsyncQueryProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace X.PagedList.Tests; 4 | 5 | public interface IDbAsyncQueryProvider : IQueryProvider { } 6 | -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/PagedListExample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using X.PagedList.Extensions; 4 | 5 | namespace X.PagedList.Tests; 6 | 7 | public class PagedListExample 8 | { 9 | public void PagedListExample_Main() 10 | { 11 | // create a list of integers from 1 to 200 12 | var list = Enumerable.Range(1, 200).ToList(); 13 | 14 | var firstPage = list.ToPagedList(1, 20); // first page, page size = 20 15 | Console.WriteLine("Is first page? {0}", firstPage.IsFirstPage); // true 16 | Console.WriteLine("Is last page? {0}", firstPage.IsLastPage); // false 17 | Console.WriteLine("First value on page? {0}", firstPage[0]); // 1 18 | Console.WriteLine(); 19 | 20 | var anotherPage = list.ToPagedList(5, 20); // fifth page, page size = 20 21 | Console.WriteLine("Is first page? {0}", anotherPage.IsFirstPage); // false 22 | Console.WriteLine("Is last page? {0}", anotherPage.IsLastPage); // false 23 | Console.WriteLine("Total integers in list? {0}", anotherPage.TotalItemCount); // 200 24 | Console.WriteLine("Integers on this page? {0}", anotherPage.Count); // 20 25 | Console.WriteLine("First value on page? {0}", anotherPage[0]); // 81 26 | Console.WriteLine(); 27 | 28 | var lastPage = list.ToPagedList(10, 20); // last (tenth) page, page size = 20 29 | Console.WriteLine("Is first page? {0}", lastPage.IsFirstPage); // false 30 | Console.WriteLine("Is last page? {0}", lastPage.IsLastPage); // true 31 | Console.WriteLine("First value on page? {0}", lastPage[0]); // 181 32 | Console.ReadKey(false); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/PagedListFacts.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using X.PagedList.Extensions; 7 | using Xunit; 8 | 9 | namespace X.PagedList.Tests; 10 | 11 | public class PagedListFacts 12 | { 13 | //[Fact] 14 | //public void Null_Data_Set_Doesnt_Throw_Exception() 15 | //{ 16 | // //act 17 | // Assert.ThrowsDelegate act = () => new PagedList(null, 1, 10); 18 | 19 | // //assert 20 | // Assert.DoesNotThrow(act); 21 | //} 22 | 23 | [Fact] 24 | public void PageNumber_Below_One_Throws_ArgumentOutOfRange() 25 | { 26 | //arrange 27 | var data = new[] { 1, 2, 3 }; 28 | 29 | //act 30 | Action action = () => data.ToPagedList(0, 1); 31 | 32 | //assert 33 | Assert.Throws(action); 34 | } 35 | 36 | [Fact] 37 | public void Argument_out_of_range() 38 | { 39 | var queryable = (new List()).AsQueryable(); 40 | var list = queryable.ToList(); 41 | var pagedList = list.ToPagedList(); 42 | 43 | Assert.NotNull(pagedList); 44 | } 45 | 46 | 47 | [Fact] 48 | public void Split_Works() 49 | { 50 | //arrange 51 | var list = Enumerable.Range(1, 47); 52 | 53 | //act 54 | var splitList = list.Split(5).ToList(); 55 | 56 | //assert 57 | Assert.Equal(5, splitList.Count()); 58 | Assert.Equal(10, splitList.ElementAt(0).Count()); 59 | Assert.Equal(10, splitList.ElementAt(1).Count()); 60 | Assert.Equal(10, splitList.ElementAt(2).Count()); 61 | Assert.Equal(10, splitList.ElementAt(3).Count()); 62 | Assert.Equal(7, splitList.ElementAt(4).Count()); 63 | } 64 | 65 | [Fact] 66 | public void Key_Selector_Works() 67 | { 68 | var collection = Enumerable.Range(1, 1000000); 69 | 70 | int pageNumber = 2; 71 | int pageSize = 10; 72 | 73 | Expression> keySelector = i => Order(i); 74 | 75 | var list = collection.ToPagedList(keySelector, pageNumber, pageSize); 76 | 77 | Assert.Equal(22, list.ElementAt(0)); 78 | Assert.Equal(24, list.ElementAt(1)); 79 | Assert.Equal(26, list.ElementAt(2)); 80 | Assert.Equal(28, list.ElementAt(3)); 81 | Assert.Equal(30, list.ElementAt(4)); 82 | Assert.Equal(32, list.ElementAt(5)); 83 | Assert.Equal(34, list.ElementAt(6)); 84 | Assert.Equal(36, list.ElementAt(7)); 85 | Assert.Equal(38, list.ElementAt(8)); 86 | Assert.Equal(40, list.ElementAt(9)); 87 | } 88 | 89 | private static int Order(int i) 90 | { 91 | // 92 | return i % 2 == 0 ? 1 : 10; 93 | } 94 | 95 | [Fact] 96 | public void PageNumber_Above_RecordCount_Returns_Empty_List() 97 | { 98 | //arrange 99 | var data = new[] { 1, 2, 3 }; 100 | 101 | //act 102 | var pagedList = data.ToPagedList(2, 3); 103 | 104 | //assert 105 | Assert.Empty(pagedList); 106 | } 107 | 108 | [Fact] 109 | public void PageSize_Below_One_Throws_ArgumentOutOfRange() 110 | { 111 | //arrange 112 | var data = new[] { 1, 2, 3 }; 113 | 114 | //act 115 | Action action = () => data.ToPagedList(1, 0); 116 | 117 | //assert 118 | Assert.Throws(action); 119 | } 120 | 121 | [Fact] 122 | public void Empty_Data_Set_Doesnt_Return_Null() 123 | { 124 | //act 125 | var pagedList = new PagedList(new List(), 1, 10); 126 | 127 | //assert 128 | Assert.NotNull(pagedList); 129 | } 130 | 131 | [Fact] 132 | public void Empty_Data_Set_Returns_Zero_Pages() 133 | { 134 | //act 135 | var pagedList = new PagedList(new List(), 1, 10); 136 | 137 | //assert 138 | Assert.Equal(0, pagedList.PageCount); 139 | } 140 | 141 | [Fact] 142 | public void Zero_Item_Data_Set_Returns_Zero_Pages() 143 | { 144 | //arrange 145 | var data = new List(); 146 | 147 | //act 148 | var pagedList = data.ToPagedList(1, 10); 149 | 150 | //assert 151 | Assert.Equal(0, pagedList.PageCount); 152 | } 153 | 154 | [Fact] 155 | public void DataSet_Of_One_Through_Five_PageSize_Of_Two_PageNumber_Of_Two_First_Item_Is_Three() 156 | { 157 | //arrange 158 | var data = new[] { 1, 2, 3, 4, 5 }; 159 | 160 | //act 161 | var pagedList = data.ToPagedList(2, 2); 162 | 163 | //assert 164 | Assert.Equal(3, pagedList[0]); 165 | } 166 | 167 | [Fact] 168 | public void TotalCount_Is_Preserved() 169 | { 170 | //arrange 171 | var data = new[] { 1, 2, 3, 4, 5 }; 172 | 173 | //act 174 | var pagedList = data.ToPagedList(2, 2); 175 | 176 | //assert 177 | Assert.Equal(5, pagedList.TotalItemCount); 178 | } 179 | 180 | [Fact] 181 | public void PageSize_Is_Preserved() 182 | { 183 | //arrange 184 | var data = new[] { 1, 2, 3, 4, 5 }; 185 | 186 | //act 187 | var pagedList = data.ToPagedList(2, 2); 188 | 189 | //assert 190 | Assert.Equal(2, pagedList.PageSize); 191 | } 192 | 193 | [Fact] 194 | public void Data_Is_Filtered_By_PageSize() 195 | { 196 | //arrange 197 | var data = new[] { 1, 2, 3, 4, 5 }; 198 | 199 | //act 200 | var pagedList = data.ToPagedList(2, 2); 201 | 202 | //assert 203 | Assert.Equal(2, pagedList.Count); 204 | 205 | //### related test below 206 | 207 | //act 208 | pagedList = data.ToPagedList(3, 2); 209 | 210 | //assert 211 | Assert.Single(pagedList); 212 | } 213 | 214 | [Fact] 215 | public void DataSet_OneThroughSix_PageSize_Three_PageNumber_One_FirstValue_Is_One() 216 | { 217 | //arrange 218 | var data = new[] { 1, 2, 3, 4, 5, 6 }; 219 | 220 | //act 221 | var pagedList = data.ToPagedList(1, 3); 222 | 223 | //assert 224 | Assert.Equal(1, pagedList[0]); 225 | } 226 | 227 | [Fact] 228 | public void DataSet_OneThroughThree_PageSize_One_PageNumber_Three_HasNextPage_False() 229 | { 230 | //arrange 231 | var data = new[] { 1, 2, 3 }; 232 | 233 | //act 234 | var pagedList1 = data.ToPagedList(2, 1); 235 | var pagedList2 = data.ToPagedList(3, 1); 236 | var pagedList3 = data.ToPagedList(4, 1); 237 | 238 | //assert 239 | Assert.True(pagedList1.HasNextPage); 240 | Assert.False(pagedList2.HasNextPage); 241 | Assert.False(pagedList3.HasNextPage); 242 | } 243 | 244 | [Fact] 245 | public void DataSet_OneThroughThree_PageSize_One_PageNumber_Three_IsLastPage_True() 246 | { 247 | //arrange 248 | var data = new[] { 1, 2, 3 }; 249 | 250 | //act 251 | var pagedList1 = data.ToPagedList(2, 1); 252 | var pagedList2 = data.ToPagedList(3, 1); 253 | var pagedList3 = data.ToPagedList(4, 1); 254 | 255 | //assert 256 | Assert.False(pagedList1.IsLastPage); 257 | Assert.True(pagedList2.IsLastPage); 258 | Assert.False(pagedList3.IsLastPage); 259 | } 260 | 261 | [Fact] 262 | public void DataSet_OneAndTwo_PageSize_One_PageNumber_Two_FirstValue_Is_Two() 263 | { 264 | //arrange 265 | var data = new[] { 1, 2 }; 266 | 267 | //act 268 | var pagedList = data.ToPagedList(2, 1); 269 | 270 | //assert 271 | Assert.Equal(2, pagedList[0]); 272 | } 273 | 274 | [Fact] 275 | public void DataSet_OneThroughTen_PageSize_Five_PageNumber_One_FirstItemOnPage_Is_One() 276 | { 277 | //arrange 278 | var data = Enumerable.Range(1, 10); 279 | 280 | //act 281 | var pagedList = data.ToPagedList(1, 5); 282 | 283 | //assert 284 | Assert.Equal(1, pagedList.FirstItemOnPage); 285 | } 286 | 287 | [Fact] 288 | public void DataSet_OneThroughTen_PageSize_Five_PageNumber_Two_FirstItemOnPage_Is_Six() 289 | { 290 | //arrange 291 | var data = Enumerable.Range(1, 10); 292 | 293 | //act 294 | var pagedList = data.ToPagedList(2, 5); 295 | 296 | //assert 297 | Assert.Equal(6, pagedList.FirstItemOnPage); 298 | } 299 | 300 | [Fact] 301 | public void DataSet_OneThroughTen_PageSize_Five_PageNumber_One_LastItemOnPage_Is_Five() 302 | { 303 | //arrange 304 | var data = Enumerable.Range(1, 10); 305 | 306 | //act 307 | var pagedList = data.ToPagedList(1, 5); 308 | 309 | //assert 310 | Assert.Equal(5, pagedList.LastItemOnPage); 311 | } 312 | 313 | [Fact] 314 | public void DataSet_OneThroughTen_PageSize_Five_PageNumber_Two_LastItemOnPage_Is_Ten() 315 | { 316 | //arrange 317 | var data = Enumerable.Range(1, 10); 318 | 319 | //act 320 | var pagedList = data.ToPagedList(2, 5); 321 | 322 | //assert 323 | Assert.Equal(10, pagedList.LastItemOnPage); 324 | } 325 | 326 | [Fact] 327 | public void DataSet_OneThroughEight_PageSize_Five_PageNumber_Two_LastItemOnPage_Is_Eight() 328 | { 329 | //arrange 330 | var data = Enumerable.Range(1, 8); 331 | 332 | //act 333 | var pagedList = data.ToPagedList(2, 5); 334 | 335 | //assert 336 | Assert.Equal(8, pagedList.LastItemOnPage); 337 | } 338 | 339 | [Fact] 340 | public void ToList_Check_CornerCases() 341 | { 342 | int pageNumber = 2; 343 | int pageSize = 10; 344 | int superSetTotalCount = 110; 345 | 346 | var superset = BuildBlogList(50); 347 | var queryable = superset.AsQueryable(); 348 | 349 | var pagedList = queryable.ToPagedList(pageNumber, pageSize, superSetTotalCount); 350 | var pagedListWithoutTotalCount = queryable.ToPagedList(pageNumber, pageSize); 351 | 352 | //test the totalSetCount extension 353 | Assert.Equal(11, pagedList.PageCount); 354 | Assert.Equal(2, pagedList.PageNumber); 355 | Assert.Equal(pageSize, pagedList.PageSize); 356 | Assert.Equal(10, pagedList.Count); 357 | 358 | //test the pagedListWithoutTotalCount extension 359 | Assert.Equal(11, pagedList.PageCount); 360 | Assert.Equal(2, pagedListWithoutTotalCount.PageNumber); 361 | Assert.Equal(pageSize, pagedListWithoutTotalCount.PageSize); 362 | Assert.Equal(10, pagedListWithoutTotalCount.Count); 363 | } 364 | 365 | [Fact] 366 | public void ToList_Check_CornerCases_For_Enumerable() 367 | { 368 | int pageNumber = 2; 369 | int pageSize = 10; 370 | int superSetTotalCount = 110; 371 | 372 | var superset = BuildBlogList(50); 373 | var enumerable = superset.AsEnumerable(); 374 | 375 | var pagedList = enumerable.ToPagedList(pageNumber, pageSize, superSetTotalCount); 376 | var pagedListWithoutTotalCount = enumerable.ToPagedList(pageNumber, pageSize); 377 | 378 | //test the totalSetCount extension 379 | Assert.Equal(11, pagedList.PageCount); 380 | Assert.Equal(2, pagedList.PageNumber); 381 | Assert.Equal(pageSize, pagedList.PageSize); 382 | Assert.Equal(10, pagedList.Count); 383 | 384 | //test the pagedListWithoutTotalCount extension 385 | Assert.Equal(11, pagedList.PageCount); 386 | Assert.Equal(2, pagedListWithoutTotalCount.PageNumber); 387 | Assert.Equal(pageSize, pagedListWithoutTotalCount.PageSize); 388 | Assert.Equal(10, pagedListWithoutTotalCount.Count); 389 | } 390 | 391 | [Fact] 392 | public void ClonePagedList() 393 | { 394 | int pageNumber = 2; 395 | int pageSize = 10; 396 | int superSetTotalCount = 110; 397 | 398 | var superset1 = BuildBlogList(50); 399 | var superset2 = BuildBlogList(10); 400 | var queryable1 = superset1.AsEnumerable(); 401 | 402 | var pagedList = queryable1.ToPagedList(pageNumber, pageSize, superSetTotalCount); 403 | 404 | var clone = new PagedList(pagedList, superset2); 405 | 406 | //test the totalSetCount extension 407 | Assert.Equal(11, clone.PageCount); 408 | Assert.Equal(2, clone.PageNumber); 409 | Assert.Equal(pageSize, clone.PageSize); 410 | Assert.Equal(10, clone.Count); 411 | } 412 | 413 | [Fact] 414 | public void Check_Ctor_With_KeySelectorMethod() 415 | { 416 | int pageSize = 10; 417 | 418 | var collection = BuildBlogList(50); 419 | 420 | Func keySelector = blog => 421 | { 422 | return blog.BlogID; 423 | }; 424 | 425 | var pagedList = new PagedList(collection, keySelector, 2, pageSize); 426 | 427 | 428 | //test the totalSetCount extension 429 | Assert.Equal(5, pagedList.PageCount); 430 | Assert.Equal(2, pagedList.PageNumber); 431 | Assert.Equal(pageSize, pagedList.PageSize); 432 | Assert.Equal(10, pagedList.Count); 433 | } 434 | 435 | [Fact] 436 | public void Check_Empty_Method() 437 | { 438 | var empty1 = PagedList.Empty(); 439 | var empty2 = PagedList.Empty(10); 440 | var empty3 = PagedList.Empty(15); 441 | var empty4 = StaticPagedList.Empty(); 442 | var empty5 = StaticPagedList.Empty(10); 443 | var empty6 = StaticPagedList.Empty(15); 444 | 445 | //test the totalSetCount extension 446 | Assert.Equal(0, empty1.PageCount); 447 | Assert.Equal(0, empty2.PageCount); 448 | Assert.Equal(1, empty3.PageNumber); 449 | Assert.Equal(0, empty4.TotalItemCount); 450 | Assert.Equal(0, empty5.TotalItemCount); 451 | Assert.Equal(0, empty6.TotalItemCount); 452 | } 453 | 454 | private static IQueryable BuildBlogList(int itemCount = 3) 455 | { 456 | var fixture = new Fixture(); 457 | 458 | return fixture.CreateMany(itemCount).OrderByDescending(b => b.BlogID).AsQueryable(); 459 | } 460 | 461 | [Fact] 462 | public void PageCount_Is_Correct_Big() 463 | { 464 | //arrange 465 | var data = Enumerable.Range(1, 100001).ToArray(); 466 | 467 | //act 468 | var pagedList = data.ToPagedList(1, 100000); 469 | 470 | //assert 471 | Assert.Equal(2, pagedList.PageCount); 472 | } 473 | 474 | [Fact] 475 | public void TotalSetCount_Is_Null() 476 | { 477 | //arrange 478 | var data = Enumerable.Range(1, 100001).AsQueryable(); 479 | 480 | //act 481 | var pagedList = data.ToPagedList(1, 10, null); 482 | 483 | //assert 484 | Assert.Equal(10001, pagedList.PageCount); 485 | } 486 | } 487 | -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/PagedListTheories.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using System.Linq; 3 | using X.PagedList.Extensions; 4 | using Xunit; 5 | 6 | namespace X.PagedList.Tests; 7 | 8 | public class PagedListTheories 9 | { 10 | [Theory] 11 | [InlineData(1, 2, 2)] 12 | [InlineData(2, 2, 1)] 13 | public void ToList_ForQuarable_Works(int pageNumber, int pageSize, int expectedCount) 14 | { 15 | var blogs = BuildBlogList(); 16 | var pagedBlogs = blogs.ToPagedList(pageNumber, pageSize); 17 | 18 | Assert.Equal(expectedCount, pagedBlogs.Count); 19 | Assert.Equal(pageNumber, pagedBlogs.PageNumber); 20 | Assert.Equal(pageSize, pagedBlogs.PageSize); 21 | } 22 | 23 | [Theory] 24 | [InlineData(1, 1)] 25 | [InlineData(2, 2)] 26 | [InlineData(3, 3)] 27 | public void ToList_ForQueryable_WithoutPageNumber_Works(int pageSize, int expectedCount) 28 | { 29 | int? pageNumber = null; 30 | var blogs = BuildBlogList(); 31 | var pagedBlogs = blogs.ToPagedList(pageNumber ?? 1, pageSize); 32 | 33 | Assert.Equal(expectedCount, pagedBlogs.Count); 34 | Assert.Equal(1, pagedBlogs.PageNumber); 35 | Assert.Equal(pageSize, pagedBlogs.PageSize); 36 | } 37 | 38 | [Theory] 39 | [InlineData(1000, 100, 10, null)] 40 | [InlineData(1000, 200, 5, 3)] 41 | [InlineData(1000, 300, 4, 4)] 42 | public void ToList_ForQueryable_With_TotalSetCount_Works(int superSetTotalCount, int pageSize, int expectedCount, int? pageNumber = null) 43 | { 44 | pageNumber = pageNumber.HasValue == false ? 0 : pageNumber; 45 | 46 | int listPageNumber = pageNumber != 0 ? (pageNumber ?? 1) - 1 : (pageNumber ?? 1); 47 | int xListPageNumber = pageNumber == 0 ? 1 : (pageNumber ?? 1); 48 | 49 | var superset = BuildBlogList(1000); 50 | 51 | var pageOfSuperSet = superset.Skip(listPageNumber * pageSize).Take(pageSize).ToList(); 52 | var pagedBlogs = superset.AsQueryable().ToPagedList(xListPageNumber, pageSize, superSetTotalCount); 53 | var pagedBlogsWithoutTotalCount = superset.AsQueryable().ToPagedList(xListPageNumber, pageSize); 54 | 55 | //test the totalSetCount extension 56 | Assert.Equal(expectedCount, pagedBlogs.PageCount); 57 | Assert.Equal(xListPageNumber, pagedBlogs.PageNumber); 58 | Assert.Equal(pageSize, pagedBlogs.PageSize); 59 | Assert.Equal(pageOfSuperSet.Count(), pagedBlogs.Count); 60 | Assert.Equal(pageOfSuperSet.First().Name, pagedBlogs.OrderByDescending(b => b.BlogID).First().Name); 61 | 62 | //test the default behaviour 63 | Assert.Equal(expectedCount, pagedBlogsWithoutTotalCount.PageCount); 64 | Assert.Equal(xListPageNumber, pagedBlogsWithoutTotalCount.PageNumber); 65 | Assert.Equal(pageSize, pagedBlogsWithoutTotalCount.PageSize); 66 | Assert.Equal(pageOfSuperSet.Count(), pagedBlogsWithoutTotalCount.Count); 67 | Assert.Equal(pageOfSuperSet.First().Name, pagedBlogsWithoutTotalCount.OrderByDescending(b => b.BlogID).First().Name); 68 | } 69 | 70 | 71 | [Theory] 72 | [InlineData(new[] { 1, 2, 3 }, 1, 1, false, true)] 73 | [InlineData(new[] { 1, 2, 3 }, 2, 1, true, true)] 74 | [InlineData(new[] { 1, 2, 3 }, 3, 1, true, false)] 75 | [InlineData(new[] { 1, 2, 3 }, 1, 3, false, false)] 76 | [InlineData(new[] { 1, 2, 3 }, 2, 3, false, false)] 77 | [InlineData(new int[] { }, 1, 3, false, false)] 78 | public void Theory_HasPreviousPage_And_HasNextPage_Are_Correct(int[] integers, int pageNumber, int pageSize, 79 | bool expectedHasPrevious, bool expectedHasNext) 80 | { 81 | //arrange 82 | int[] data = integers; 83 | 84 | //act 85 | var pagedList = data.ToPagedList(pageNumber, pageSize); 86 | 87 | //assert 88 | Assert.Equal(expectedHasPrevious, pagedList.HasPreviousPage); 89 | Assert.Equal(expectedHasNext, pagedList.HasNextPage); 90 | } 91 | 92 | [Theory] 93 | [InlineData(new[] { 1, 2, 3 }, 1, 1, true, false)] 94 | [InlineData(new[] { 1, 2, 3 }, 2, 1, false, false)] 95 | [InlineData(new[] { 1, 2, 3 }, 3, 1, false, true)] 96 | [InlineData(new[] { 1, 2, 3 }, 1, 3, true, true)] // Page 1 of 1 97 | [InlineData(new[] { 1, 2, 3 }, 2, 3, false, false)] // Page 2 of 1 98 | [InlineData(new int[] { }, 1, 3, false, false)] // Page 1 of 0 99 | public void Theory_IsFirstPage_And_IsLastPage_Are_Correct(int[] integers, int pageNumber, int pageSize, 100 | bool expectedIsFirstPage, bool expectedIsLastPage) 101 | { 102 | //arrange 103 | int[] data = integers; 104 | 105 | //act 106 | var pagedList = data.ToPagedList(pageNumber, pageSize); 107 | 108 | //assert 109 | Assert.Equal(expectedIsFirstPage, pagedList.IsFirstPage); 110 | Assert.Equal(expectedIsLastPage, pagedList.IsLastPage); 111 | } 112 | 113 | [Theory] 114 | [InlineData(new[] { 1, 2, 3 }, 1, 3)] 115 | [InlineData(new[] { 1, 2, 3 }, 3, 1)] 116 | [InlineData(new[] { 1 }, 1, 1)] 117 | [InlineData(new[] { 1, 2, 3 }, 2, 2)] 118 | [InlineData(new[] { 1, 2, 3, 4 }, 2, 2)] 119 | [InlineData(new[] { 1, 2, 3, 4, 5 }, 2, 3)] 120 | [InlineData(new int[] { }, 1, 0)] 121 | public void Theory_PageCount_Is_Correct(int[] integers, int pageSize, int expectedNumberOfPages) 122 | { 123 | //arrange 124 | int[] data = integers; 125 | 126 | //act 127 | var pagedList = data.ToPagedList(1, pageSize); 128 | 129 | //assert 130 | Assert.Equal(expectedNumberOfPages, pagedList.PageCount); 131 | } 132 | 133 | 134 | [Theory] 135 | [InlineData(new[] { 1, 2, 3, 4, 5 }, 1, 2, 1, 2)] 136 | [InlineData(new[] { 1, 2, 3, 4, 5 }, 2, 2, 3, 4)] 137 | [InlineData(new[] { 1, 2, 3, 4, 5 }, 3, 2, 5, 5)] 138 | [InlineData(new[] { 1, 2, 3, 4, 5 }, 4, 2, 0, 0)] 139 | [InlineData(new int[] { }, 1, 2, 0, 0)] 140 | public void Theory_FirstItemOnPage_And_LastItemOnPage_Are_Correct(int[] integers, int pageNumber, int pageSize, int expectedFirstItemOnPage, int expectedLastItemOnPage) 141 | { 142 | //arrange 143 | int[] data = integers; 144 | 145 | //act 146 | var pagedList = data.ToPagedList(pageNumber, pageSize); 147 | 148 | //assert 149 | Assert.Equal(expectedFirstItemOnPage, pagedList.FirstItemOnPage); 150 | Assert.Equal(expectedLastItemOnPage, pagedList.LastItemOnPage); 151 | } 152 | 153 | private static IQueryable BuildBlogList(int itemCount = 3) 154 | { 155 | var fixture = new Fixture(); 156 | 157 | return fixture.CreateMany(itemCount).OrderByDescending(b => b.BlogID).AsQueryable(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/SplitAndPartitionFacts.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using X.PagedList.Extensions; 3 | using Xunit; 4 | 5 | namespace X.PagedList.Tests; 6 | 7 | public class SplitAndPartitionFacts 8 | { 9 | [Fact] 10 | public void Partition_Works() 11 | { 12 | //arrange 13 | var list = Enumerable.Range(1, 9999); 14 | 15 | //act 16 | var partitionList = list.Partition(1000); 17 | 18 | //assert 19 | Assert.Equal(10, partitionList.Count()); 20 | Assert.Equal(1000, partitionList.First().Count()); 21 | Assert.Equal(999, partitionList.Last().Count()); 22 | } 23 | 24 | [Fact] 25 | public void Partition_Returns_Enumerable_With_One_Item_When_Count_Less_Than_Page_Size() 26 | { 27 | //arrange 28 | var list = Enumerable.Range(1, 10); 29 | 30 | //act 31 | var partitionList = list.Partition(1000); 32 | 33 | //assert 34 | Assert.Single(partitionList); 35 | Assert.Equal(10, partitionList.First().Count()); 36 | } 37 | 38 | [Fact] 39 | public void Split_Works() 40 | { 41 | //arrange 42 | var list = Enumerable.Range(1, 9999); 43 | 44 | //act 45 | var splitList = list.Split(10); 46 | 47 | //assert 48 | Assert.Equal(10, splitList.Count()); 49 | Assert.Equal(1000, splitList.First().Count()); 50 | Assert.Equal(999, splitList.Last().Count()); 51 | } 52 | 53 | [Fact] 54 | public void Split_Returns_Pages_Of_One_When_More_Pages_Are_Requested_Than_There_Are_Items_To_Page() 55 | { 56 | //arrange 57 | var list = Enumerable.Range(1, 9); 58 | 59 | //act 60 | var splitList = list.Split(10); 61 | 62 | //assert 63 | Assert.Equal(9, splitList.Count()); 64 | Assert.Single(splitList.First()); 65 | Assert.Single(splitList.Last()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/StaticPagedListExample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace X.PagedList.Tests; 5 | 6 | public class StaticPagedListExample 7 | { 8 | public void StaticPagedListExample_Main(string[] args) 9 | { 10 | // create a list of integers from 1 to 20 11 | var list = Enumerable.Range(1, 20); 12 | 13 | var firstPage = new StaticPagedList(list, 1, 4, 20); // first page 14 | Console.WriteLine("Is first page? {0}", firstPage.IsFirstPage); // true 15 | Console.WriteLine("Is last page? {0}", firstPage.IsLastPage); // false 16 | Console.WriteLine("First value on page? {0}", firstPage[0]); // 1 17 | Console.WriteLine(); 18 | 19 | var middlePage = new StaticPagedList(list, 3, 4, 20); // third page, same values 20 | Console.WriteLine("Is first page? {0}", middlePage.IsFirstPage); // false 21 | Console.WriteLine("Is last page? {0}", middlePage.IsLastPage); // false 22 | Console.WriteLine("First value on page? {0}", middlePage[0]); // 9 23 | Console.WriteLine(); 24 | 25 | var lastPage = new StaticPagedList(list, 5, 4, 20); // fifth page, same values 26 | Console.WriteLine("Is first page? {0}", lastPage.IsFirstPage); // false 27 | Console.WriteLine("Is last page? {0}", lastPage.IsLastPage); // true 28 | Console.WriteLine("First value on page? {0}", lastPage[0]); // 17 29 | Console.ReadKey(false); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/StaticPagedListFacts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace X.PagedList.Tests; 5 | 6 | public class StaticPagedListFacts 7 | { 8 | [Theory, 9 | InlineData(1, true, false), 10 | InlineData(2, false, false), 11 | InlineData(3, false, true), 12 | InlineData(4, false, false)] 13 | public void StaticPagedList_uses_supplied_totalItemCount_to_determine_subsets_position_within_superset(int pageNumber, bool shouldBeFirstPage, bool shouldBeLastPage) 14 | { 15 | //arrange 16 | int[] subset = new[] { 1, 1, 1 }; 17 | 18 | //act 19 | var list = new StaticPagedList(subset, pageNumber, 3, 9); 20 | 21 | //assert 22 | Assert.Equal(pageNumber, list.PageNumber); 23 | Assert.Equal(shouldBeFirstPage, list.IsFirstPage); 24 | Assert.Equal(shouldBeLastPage, list.IsLastPage); 25 | } 26 | 27 | [Fact] 28 | public void TotalItemCount_Below_One_Throws_Exception() 29 | { 30 | //arrange 31 | int[] data = new[] { 1, 2, 3, 4, 5 }; 32 | 33 | //assert 34 | Assert.Throws(() => new StaticPagedList(data, 1, 10, -1)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/TestContext.cs: -------------------------------------------------------------------------------- 1 | namespace X.PagedList.Tests; 2 | 3 | public class TestContext : DbContext 4 | { 5 | public virtual DbSet? Blogs { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/TestDbAsyncQueryProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace X.PagedList.Tests; 9 | 10 | public class TestDbAsyncQueryProvider { } 11 | internal class TestDbAsyncQueryProvider : IDbAsyncQueryProvider 12 | { 13 | private readonly IQueryProvider _inner; 14 | 15 | internal TestDbAsyncQueryProvider(IQueryProvider inner) 16 | { 17 | _inner = inner; 18 | } 19 | 20 | public IQueryable CreateQuery(Expression expression) 21 | { 22 | return new TestDbAsyncEnumerable(expression); 23 | } 24 | 25 | public IQueryable CreateQuery(Expression expression) 26 | { 27 | return new TestDbAsyncEnumerable(expression); 28 | } 29 | 30 | public object? Execute(Expression expression) 31 | { 32 | return _inner.Execute(expression); 33 | } 34 | 35 | public TResult Execute(Expression expression) 36 | { 37 | return _inner.Execute(expression); 38 | } 39 | 40 | public Task ExecuteAsync(Expression expression, CancellationToken cancellationToken) 41 | { 42 | return Task.FromResult(Execute(expression)); 43 | } 44 | 45 | public Task ExecuteAsync(Expression expression, CancellationToken cancellationToken) 46 | { 47 | return Task.FromResult(Execute(expression)); 48 | } 49 | 50 | internal class TestDbAsyncEnumerable : EnumerableQuery, IDbAsyncEnumerable, IQueryable 51 | { 52 | public TestDbAsyncEnumerable(IEnumerable enumerable) 53 | : base(enumerable) 54 | { } 55 | 56 | public TestDbAsyncEnumerable(Expression expression) 57 | : base(expression) 58 | { } 59 | 60 | public IDbAsyncEnumerator GetAsyncEnumerator() 61 | { 62 | return new TestDbAsyncEnumerator(this.AsEnumerable().GetEnumerator()); 63 | } 64 | 65 | IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() 66 | { 67 | return GetAsyncEnumerator(); 68 | } 69 | 70 | IQueryProvider IQueryable.Provider 71 | { 72 | get { return new TestDbAsyncQueryProvider(this); } 73 | } 74 | } 75 | 76 | internal class TestDbAsyncEnumerator : IDbAsyncEnumerator, IDisposable 77 | { 78 | private readonly IEnumerator _inner; 79 | 80 | public TestDbAsyncEnumerator(IEnumerator inner) 81 | { 82 | _inner = inner; 83 | } 84 | 85 | public void Dispose() 86 | { 87 | _inner.Dispose(); 88 | } 89 | 90 | public T Current => _inner.Current; 91 | 92 | object? IDbAsyncEnumerator.Current => Current; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /tests/X.PagedList.Tests/X.PagedList.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0;net8.0 5 | 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /x-pagedlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dncuug/X.PagedList/f68e3e69d25bcbc66a1af4ecf08a52d6c2e1a141/x-pagedlist.png --------------------------------------------------------------------------------