├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── questions.md └── workflows │ └── dotnetcore.yml ├── .gitignore ├── AspNetCoreWeb ├── ActionResults │ ├── JqueryDataTablesCSVResult.cs │ └── JqueryDataTablesExcelResult.cs ├── AspNetCoreWeb.csproj ├── Attributes │ ├── JqueryDataTableColumnAttribute.cs │ ├── NestedSearchableAttribute.cs │ ├── NestedSortableAttribute.cs │ ├── SearchableAttribute.cs │ ├── SearchableDateTimeAttribute.cs │ ├── SearchableDecimalAttribute.cs │ ├── SearchableDoubleAttribute.cs │ ├── SearchableEnumAttribute.cs │ ├── SearchableIntAttribute.cs │ ├── SearchableLongAttribute.cs │ ├── SearchableShortAttribute.cs │ ├── SearchableStringAttribute.cs │ └── SortableAttribute.cs ├── Binders │ └── JqueryDataTablesBinder.cs ├── Contracts │ └── ISearchExpressionProvider.cs ├── Images │ ├── Abdul Linkedin Profile.jpeg │ └── icon.png ├── Infrastructure │ ├── ExpressionHelper.cs │ ├── SearchOptionsProcessor{T,TEntity}.cs │ ├── SearchTerm.cs │ ├── SortOptionsProcessor{T,TEntity}.cs │ └── SortTerm.cs ├── Models │ ├── JqueryDataTablesModel.cs │ ├── JqueryDataTablesPagedResults{T}.cs │ └── TableColumn.cs ├── Providers │ ├── ComparableSearchExpressionProvider.cs │ ├── DateTimeSearchExpressionProvider.cs │ ├── DecimalSearchExpressionProvider.cs │ ├── DefaultSearchExpressionProvider.cs │ ├── DoubleSearchExpressionProvider.cs │ ├── EnumSearchExpressionProvider.cs │ ├── EnumerationSearchExpressionProvider.cs │ ├── IntSearchExpressionProvider.cs │ ├── LongSearchExpressionProvider.cs │ ├── ShortSearchExpressionProvider.cs │ └── StringSearchExpressionProvider.cs └── TagHelpers │ ├── JqueryDataTablesHtmlLocalizedTagHelper.cs │ └── JqueryDataTablesTagHelper.cs ├── JqueryDataTablesServerSide.sln ├── LICENSE.md ├── README.md └── _config.yml /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: ['https://www.paypal.me/arsmsi'] 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: fingers10 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: "[FEATURE]" 5 | labels: enhancement 6 | assignees: fingers10 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/ISSUE_TEMPLATE/questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Questions 3 | about: Describe your query here. 4 | title: "[QUESTION]" 5 | labels: question 6 | assignees: fingers10 7 | 8 | --- 9 | 10 | **Describe the question** 11 | A clear and concise description of what the question is. 12 | 13 | **To Produce** 14 | Steps to produce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. How this can be done? 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expect to happen along with your view model and domain model. Please note, questions without view model and domain model will be closed without any further discussion. Please check the [example project](https://github.com/fingers10/JqueryDataTablesServerSideDemo) before posting. Most of the times, you would find the answer in example solution. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your question. 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 | 40 | 41 | -------------------------------------------------------------------------------- /.github/workflows/dotnetcore.yml: -------------------------------------------------------------------------------- 1 | name: JqueryDataTablesServerSide Build and Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Setup .NET Core 13 | uses: actions/setup-dotnet@v1 14 | with: 15 | dotnet-version: 3.1.100 16 | - name: Build with dotnet 17 | run: dotnet build ./JqueryDataTablesServerSide.sln --configuration Release 18 | - name: Test with dotnet 19 | run: dotnet test ./JqueryDataTablesServerSide.sln --configuration Release 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /AspNetCoreWeb/ActionResults/JqueryDataTablesCSVResult.cs: -------------------------------------------------------------------------------- 1 | using Fingers10.ExcelExport.ActionResults; 2 | using System.Collections.Generic; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.ActionResults 5 | { 6 | public class JqueryDataTablesCSVResult : CSVResult where T : class 7 | { 8 | public JqueryDataTablesCSVResult(IEnumerable data, string fileName) : base(data, fileName) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AspNetCoreWeb/ActionResults/JqueryDataTablesExcelResult.cs: -------------------------------------------------------------------------------- 1 | using Fingers10.ExcelExport.ActionResults; 2 | using System.Collections.Generic; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.ActionResults 5 | { 6 | public class JqueryDataTablesExcelResult : ExcelResult where T : class 7 | { 8 | public JqueryDataTablesExcelResult(IEnumerable data, string sheetName, string fileName) : base(data, sheetName, fileName) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /AspNetCoreWeb/AspNetCoreWeb.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | Abdul Rahman (@fingers10) 6 | Abdul Rahman (@fingers10) 7 | Fingers10.JqueryDataTablesServerSide.AspNetCore 8 | Simple and Powerful Dynamic Multiple Column Searching and Sorting along with Pagination and Excel Export for Jquery DataTables Asp.Net Core Server Side 9 | Copyright (c) 2019 Abdul Rahman (@fingers10) 10 | 11 | https://github.com/fingers10/JqueryDataTablesServerSide 12 | https://github.com/fingers10/JqueryDataTablesServerSide 13 | Public 14 | JqueryDataTables, Asp.Net Core, Server Side, Multiple Column, Dynamic, Searching, Sorting, Paging, Excel Export 15 | JqueryDataTables.ServerSide.AspNetCoreWeb 16 | icon.png 17 | 4.0.0 18 | JqueryDataTables.ServerSide.AspNetCoreWeb 19 | true 20 | 1. Fixed String Search Issues. 21 | 2. Added Localized Jquery DataTables Tag Helper 22 | 3. Added CSV Export 23 | 4. Updated Nuget Dependencies 24 | 5. Added Enum Contains Search 25 | 6. Added Support for HTML5 datatypes in TagHelper 26 | JqueryDataTables.ServerSide.AspNetCoreWeb 27 | 4.0.0.0 28 | 4.0.0.0 29 | true 30 | LICENSE.md 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Attributes/JqueryDataTableColumnAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes 6 | { 7 | [AttributeUsage(AttributeTargets.Property)] 8 | public class JqueryDataTableColumnAttribute : Attribute 9 | { 10 | public int Order { get; set; } 11 | public bool Exclude { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Attributes/NestedSearchableAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes 4 | { 5 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] 6 | public class NestedSearchableAttribute : SearchableAttribute 7 | { 8 | public string ParentEntityProperty { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Attributes/NestedSortableAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes 4 | { 5 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] 6 | public class NestedSortableAttribute : SortableAttribute 7 | { 8 | public string ParentEntityProperty { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Attributes/SearchableAttribute.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Contracts; 2 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Providers; 3 | using System; 4 | 5 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes 6 | { 7 | [AttributeUsage(AttributeTargets.Property)] 8 | public class SearchableAttribute : Attribute 9 | { 10 | public string EntityProperty { get; set; } 11 | 12 | public ISearchExpressionProvider ExpressionProvider { get; set; } 13 | = new DefaultSearchExpressionProvider(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Attributes/SearchableDateTimeAttribute.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Providers; 2 | using System; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Property)] 7 | public class SearchableDateTimeAttribute : SearchableAttribute 8 | { 9 | public SearchableDateTimeAttribute() 10 | { 11 | ExpressionProvider = new DateTimeSearchExpressionProvider(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Attributes/SearchableDecimalAttribute.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Providers; 2 | using System; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Property)] 7 | public class SearchableDecimalAttribute : SearchableAttribute 8 | { 9 | public SearchableDecimalAttribute() 10 | { 11 | ExpressionProvider = new DecimalSearchExpressionProvider(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Attributes/SearchableDoubleAttribute.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Providers; 2 | using System; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Property)] 7 | public class SearchableDoubleAttribute : SearchableAttribute 8 | { 9 | public SearchableDoubleAttribute() 10 | { 11 | ExpressionProvider = new DoubleSearchExpressionProvider(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Attributes/SearchableEnumAttribute.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Providers; 2 | using System; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Property)] 7 | public class SearchableEnumAttribute : SearchableAttribute 8 | { 9 | public SearchableEnumAttribute(Type enumType) 10 | { 11 | ExpressionProvider = (EnumerationSearchExpressionProvider)Activator.CreateInstance( 12 | typeof(EnumSearchExpressionProvider<>).MakeGenericType(enumType)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Attributes/SearchableIntAttribute.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Providers; 2 | using System; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Property)] 7 | public class SearchableIntAttribute : SearchableAttribute 8 | { 9 | public SearchableIntAttribute() 10 | { 11 | ExpressionProvider = new IntSearchExpressionProvider(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Attributes/SearchableLongAttribute.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Providers; 2 | using System; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Property)] 7 | public class SearchableLongAttribute : SearchableAttribute 8 | { 9 | public SearchableLongAttribute() 10 | { 11 | ExpressionProvider = new LongSearchExpressionProvider(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Attributes/SearchableShortAttribute.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Providers; 2 | using System; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Property)] 7 | public class SearchableShortAttribute : SearchableAttribute 8 | { 9 | public SearchableShortAttribute() 10 | { 11 | ExpressionProvider = new ShortSearchExpressionProvider(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Attributes/SearchableStringAttribute.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Providers; 2 | using System; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Property)] 7 | public class SearchableStringAttribute : SearchableAttribute 8 | { 9 | public SearchableStringAttribute() 10 | { 11 | ExpressionProvider = new StringSearchExpressionProvider(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Attributes/SortableAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes 4 | { 5 | [AttributeUsage(AttributeTargets.Property)] 6 | public class SortableAttribute : Attribute 7 | { 8 | public string EntityProperty { get; set; } 9 | 10 | public bool Default { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Binders/JqueryDataTablesBinder.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Models; 2 | using Microsoft.AspNetCore.Mvc.ModelBinding; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Binders 9 | { 10 | public class JqueryDataTablesBinder : IModelBinder 11 | { 12 | public Task BindModelAsync(ModelBindingContext bindingContext) 13 | { 14 | if (bindingContext == null) 15 | { 16 | throw new ArgumentNullException(nameof(bindingContext)); 17 | } 18 | 19 | var allValues = bindingContext.HttpContext.Request.Query; 20 | 21 | // Retrieve request data 22 | var draw = Convert.ToInt32(allValues.FirstOrDefault(a => a.Key == "draw").Value); 23 | var start = Convert.ToInt32(allValues.FirstOrDefault(a => a.Key == "start").Value); 24 | var length = Convert.ToInt32(allValues.FirstOrDefault(a => a.Key == "length").Value); 25 | 26 | // Search 27 | var search = new DTSearch 28 | { 29 | Value = allValues.FirstOrDefault(a => a.Key == "search[value]").Value, 30 | Regex = Convert.ToBoolean(allValues.FirstOrDefault(a => a.Key == "search[regex]").Value) 31 | }; 32 | 33 | // Order 34 | var o = 0; 35 | var order = new List(); 36 | while (allValues.Any(a => a.Key == "order[" + o + "][column]")) 37 | { 38 | Enum.TryParse(allValues.FirstOrDefault(a => a.Key == "order[" + o + "][dir]").Value.ToString().ToUpperInvariant(), 39 | out DTOrderDir dir); 40 | 41 | order.Add(new DTOrder 42 | { 43 | Column = Convert.ToInt32(allValues.FirstOrDefault(a => a.Key == "order[" + o + "][column]").Value), 44 | Dir = dir 45 | }); 46 | o++; 47 | } 48 | 49 | // Columns 50 | var c = 0; 51 | var columns = new List(); 52 | while (allValues.Any(a => a.Key == "columns[" + c + "][name]")) 53 | { 54 | columns.Add(new DTColumn 55 | { 56 | Data = allValues.FirstOrDefault(a => a.Key == "columns[" + c + "][data]").Value, 57 | Name = allValues.FirstOrDefault(a => a.Key == "columns[" + c + "][name]").Value, 58 | Orderable = Convert.ToBoolean(allValues.FirstOrDefault(a => a.Key == "columns[" + c + "][orderable]").Value), 59 | Searchable = Convert.ToBoolean(allValues.FirstOrDefault(a => a.Key == "columns[" + c + "][searchable]").Value), 60 | Search = new DTSearch 61 | { 62 | Value = allValues.FirstOrDefault(a => a.Key == "columns[" + c + "][search][value]").Value, 63 | Regex = Convert.ToBoolean(allValues.FirstOrDefault(a => a.Key == "columns[" + c + "][search][regex]").Value) 64 | } 65 | }); 66 | c++; 67 | } 68 | 69 | // Additional Values 70 | var p = 0; 71 | var additionalValues = new List(); 72 | while (allValues.Any(a => a.Key == "additionalValues[" + p + "]")) 73 | { 74 | additionalValues.Add(allValues.FirstOrDefault(a => a.Key == "additionalValues[" + p + "]").Value); 75 | p++; 76 | } 77 | 78 | var model = new JqueryDataTablesParameters 79 | { 80 | Draw = draw, 81 | Start = start, 82 | Length = length, 83 | Search = search, 84 | Order = order.ToArray(), 85 | Columns = columns.ToArray(), 86 | AdditionalValues = additionalValues.ToArray() 87 | }; 88 | 89 | bindingContext.Result = ModelBindingResult.Success(model); 90 | return Task.CompletedTask; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Contracts/ISearchExpressionProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq.Expressions; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Contracts 5 | { 6 | public interface ISearchExpressionProvider 7 | { 8 | IEnumerable GetOperators(); 9 | 10 | ConstantExpression GetValue(string input); 11 | 12 | //Expression GetComparison( 13 | // MemberExpression left, 14 | // string op, 15 | // ConstantExpression right); 16 | 17 | Expression GetComparison( 18 | MemberExpression left, 19 | string op, 20 | Expression right); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Images/Abdul Linkedin Profile.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fingers10/JqueryDataTablesServerSide/d8d846f04ba26e3f1a2a87a97957aa3b4f752069/AspNetCoreWeb/Images/Abdul Linkedin Profile.jpeg -------------------------------------------------------------------------------- /AspNetCoreWeb/Images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fingers10/JqueryDataTablesServerSide/d8d846f04ba26e3f1a2a87a97957aa3b4f752069/AspNetCoreWeb/Images/icon.png -------------------------------------------------------------------------------- /AspNetCoreWeb/Infrastructure/ExpressionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | 8 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Infrastructure 9 | { 10 | public static class ExpressionHelper 11 | { 12 | private static readonly MethodInfo _stringTrimMethod = typeof(string).GetMethod("Trim", new Type[0]); 13 | private static readonly MethodInfo _stringToLowerMethod = typeof(string).GetMethod("ToLower", new Type[0]); 14 | 15 | private static readonly MethodInfo LambdaMethod = typeof(Expression) 16 | .GetMethods() 17 | .First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2); 18 | 19 | private static readonly MethodInfo[] QueryableMethods = typeof(Queryable) 20 | .GetMethods() 21 | .ToArray(); 22 | 23 | private static MethodInfo GetLambdaFuncBuilder(Type source, Type dest) 24 | { 25 | var predicate = typeof(Func<,>).MakeGenericType(source, dest); 26 | return LambdaMethod.MakeGenericMethod(predicate); 27 | } 28 | 29 | public static PropertyDescriptor GetPropertyDescriptor(PropertyInfo propertyInfo) 30 | { 31 | return TypeDescriptor.GetProperties(propertyInfo.DeclaringType).Find(propertyInfo.Name, false); 32 | } 33 | 34 | public static string GetPropertyDisplayName(PropertyInfo propertyInfo) 35 | { 36 | var propertyDescriptor = GetPropertyDescriptor(propertyInfo); 37 | var displayName = propertyInfo.IsDefined(typeof(DisplayAttribute), false) ? propertyInfo.GetCustomAttributes(typeof(DisplayAttribute), 38 | false).Cast().Single().Name : null; 39 | 40 | return displayName ?? propertyDescriptor.DisplayName ?? propertyDescriptor.Name; 41 | } 42 | 43 | public static PropertyInfo GetPropertyInfo(Type type, string name) 44 | { 45 | var index = name.IndexOf('.'); 46 | while (index != -1) 47 | { 48 | var propertyInfo = type.GetProperties().Single(p => p.Name == name.Substring(0, index)); 49 | type = propertyInfo.PropertyType; 50 | name = name.Substring(index + 1); 51 | index = name.IndexOf('.'); 52 | } 53 | 54 | return type.GetProperties().Single(p => p.Name == name); 55 | } 56 | 57 | public static ParameterExpression Parameter() 58 | { 59 | return Expression.Parameter(typeof(T), $"{typeof(T).Name}Search"); 60 | } 61 | 62 | public static MemberExpression GetMemberExpression(Expression param, string propertyName) 63 | { 64 | var index = propertyName.IndexOf('.'); 65 | while (index != -1) 66 | { 67 | param = Expression.Property(param, propertyName.Substring(0, index)); 68 | propertyName = propertyName.Substring(index + 1); 69 | index = propertyName.IndexOf('.'); 70 | } 71 | 72 | return Expression.Property(param, propertyName); 73 | } 74 | 75 | public static Expression CastToObjectAndString(this MemberExpression member) 76 | { 77 | var objectMemberConvert = Expression.Convert(member, typeof(object)); 78 | var stringMemberConvert = Expression.Convert(objectMemberConvert, typeof(string)); 79 | return stringMemberConvert as Expression; 80 | } 81 | 82 | public static Expression TrimToLower(this MemberExpression member) 83 | { 84 | var trimMemberCall = Expression.Call(member, _stringTrimMethod); 85 | return Expression.Call(trimMemberCall, _stringToLowerMethod); 86 | } 87 | 88 | public static Expression TrimToLower(this ConstantExpression constant) 89 | { 90 | var trimMemberCall = Expression.Call(constant, _stringTrimMethod); 91 | return Expression.Call(trimMemberCall, _stringToLowerMethod); 92 | } 93 | 94 | public static Expression TrimToLower(this Expression constant) 95 | { 96 | var trimMemberCall = Expression.Call(constant, _stringTrimMethod); 97 | return Expression.Call(trimMemberCall, _stringToLowerMethod); 98 | } 99 | 100 | public static LambdaExpression GetLambda(ParameterExpression obj, Expression arg) 101 | { 102 | return GetLambda(typeof(TSource), typeof(TDest), obj, arg); 103 | } 104 | 105 | public static LambdaExpression GetLambda(Type source, Type dest, ParameterExpression obj, Expression arg) 106 | { 107 | var lambdaBuilder = GetLambdaFuncBuilder(source, dest); 108 | return (LambdaExpression)lambdaBuilder.Invoke(null, new object[] { arg, new[] { obj } }); 109 | } 110 | 111 | public static IQueryable CallWhere(IQueryable query, LambdaExpression predicate) 112 | { 113 | var whereMethodBuilder = QueryableMethods 114 | .First(x => x.Name == "Where" && x.GetParameters().Length == 2) 115 | .MakeGenericMethod(typeof(T)); 116 | 117 | return (IQueryable)whereMethodBuilder 118 | .Invoke(null, new object[] { query, predicate }); 119 | } 120 | 121 | public static IQueryable CallOrderByOrThenBy( 122 | IQueryable modifiedQuery, 123 | bool useThenBy, 124 | bool descending, 125 | Type propertyType, 126 | LambdaExpression keySelector) 127 | { 128 | var methodName = "OrderBy"; 129 | if (useThenBy) 130 | { 131 | methodName = "ThenBy"; 132 | } 133 | 134 | if (descending) 135 | { 136 | methodName += "Descending"; 137 | } 138 | 139 | var method = QueryableMethods 140 | .First(x => x.Name == methodName && x.GetParameters().Length == 2) 141 | .MakeGenericMethod(typeof(TEntity), propertyType); 142 | 143 | return (IQueryable)method.Invoke(null, new object[] { modifiedQuery, keySelector }); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Infrastructure/SearchOptionsProcessor{T,TEntity}.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes; 2 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | 9 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Infrastructure 10 | { 11 | public static class SearchOptionsProcessor 12 | { 13 | private static IEnumerable GetAllTerms(IEnumerable columns) 14 | { 15 | var dtColumns = columns as IList ?? columns.ToList(); 16 | 17 | if (dtColumns.All(x => string.IsNullOrWhiteSpace(x.Search.Value))) 18 | { 19 | yield break; 20 | } 21 | 22 | foreach (var column in dtColumns) 23 | { 24 | if (string.IsNullOrWhiteSpace(column.Search.Value)) 25 | { 26 | continue; 27 | } 28 | 29 | var hasNavigation = column.Data.Contains('.'); 30 | 31 | yield return new SearchTerm 32 | { 33 | ValidSyntax = true, 34 | Name = column.Data, 35 | Operator = string.IsNullOrWhiteSpace(column.Name) ? "eq" : column.Name, 36 | Value = column.Search.Value.ToLower(), 37 | HasNavigation = hasNavigation 38 | }; 39 | } 40 | } 41 | 42 | private static IEnumerable GetValidTerms(IEnumerable columns) 43 | { 44 | var queryTerms = GetAllTerms(columns) 45 | .Where(x => x.ValidSyntax) 46 | .ToArray(); 47 | 48 | if (!queryTerms.Any()) 49 | { 50 | yield break; 51 | } 52 | 53 | var declaredTerms = GetTermsFromModel(typeof(T)).ToList(); 54 | 55 | foreach (var term in queryTerms) 56 | { 57 | var declaredTerm = declaredTerms.SingleOrDefault(x => x.Name.Equals(term.Name, StringComparison.OrdinalIgnoreCase)); 58 | if (declaredTerm == null) 59 | { 60 | continue; 61 | } 62 | 63 | yield return new SearchTerm 64 | { 65 | ValidSyntax = term.ValidSyntax, 66 | Name = declaredTerm.Name, 67 | EntityName = declaredTerm.EntityName, 68 | Operator = term.Operator, 69 | Value = term.Value, 70 | ExpressionProvider = declaredTerm.ExpressionProvider, 71 | HasNavigation = declaredTerm.HasNavigation 72 | }; 73 | } 74 | } 75 | 76 | public static IQueryable Apply(IQueryable query, IEnumerable columns) 77 | { 78 | var terms = GetValidTerms(columns).ToArray(); 79 | 80 | if (!terms.Any()) 81 | { 82 | return query; 83 | } 84 | 85 | var modifiedQuery = query; 86 | var parameterExpression = ExpressionHelper.Parameter(); 87 | Expression finalExpression = Expression.Constant(true); 88 | Expression subExpression = Expression.Constant(false); 89 | 90 | // Build up the LINQ Expression backwards: 91 | // query = query.Where(x => x.Property == "Value" && (x.AnotherProperty == "Value" || x.SomeAnotherProperty == "Value")); 92 | 93 | foreach (var term in terms) 94 | { 95 | var hasMultipleTerms = term.EntityName?.Contains(',') ?? false; 96 | 97 | if (hasMultipleTerms) 98 | { 99 | var entityTerms = term.EntityName.Split(','); 100 | 101 | foreach (var entityTerm in entityTerms) 102 | { 103 | term.EntityName = entityTerm; 104 | 105 | // x => x.Property == "Value" || x.AnotherProperty == "Value" 106 | subExpression = Expression.OrElse(subExpression, GetComparisonExpression(term, parameterExpression)); 107 | } 108 | } 109 | 110 | // x => x.Property == "Value" && x.AnotherProperty == "Value" 111 | finalExpression = Expression.AndAlso(finalExpression, 112 | hasMultipleTerms ? subExpression : GetComparisonExpression(term, parameterExpression)); 113 | } 114 | 115 | // x => x.Property == "Value" && (x.AnotherProperty == "Value" || x.SomeAnotherProperty == "Value") 116 | var lambdaExpression = ExpressionHelper.GetLambda(parameterExpression, finalExpression); 117 | 118 | // query = query.Where... 119 | modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression); 120 | 121 | return modifiedQuery; 122 | } 123 | 124 | private static Expression GetComparisonExpression(SearchTerm term, ParameterExpression obj) 125 | { 126 | // x.Property 127 | var left = ExpressionHelper.GetMemberExpression(obj, term.EntityName ?? term.Name); 128 | 129 | // read this!!!! 130 | // http://askjonskeet.azurewebsites.net/answer/28476847/How-to-get-Expression-for-Nullable-values-(-fields-)-without-converting-from-ExpressionConvert-in-C 131 | 132 | // "Value" 133 | //var right = term.ExpressionProvider.GetValue(term.Value); 134 | var constant = term.ExpressionProvider.GetValue(term.Value); 135 | var right = constant.Type != left.Type && left.Type.BaseType != typeof(Enum) ? Expression.Convert(constant, left.Type) : (Expression)constant; 136 | 137 | // x.Property == "Value" 138 | return term.ExpressionProvider.GetComparison(left, term.Operator, right); 139 | } 140 | 141 | private static IEnumerable GetTermsFromModel( 142 | Type parentSortClass, 143 | string parentsEntityName = null, 144 | string parentsName = null, 145 | bool hasNavigation = false) 146 | { 147 | var properties = parentSortClass.GetTypeInfo() 148 | .DeclaredProperties 149 | .Where(p => p.GetCustomAttributes().Any()); 150 | 151 | foreach (var p in properties) 152 | { 153 | var attribute = p.GetCustomAttribute(); 154 | 155 | yield return new SearchTerm 156 | { 157 | Name = hasNavigation ? $"{parentsName}.{p.Name}" : p.Name, 158 | EntityName = hasNavigation ? $"{parentsEntityName ?? parentsName}.{attribute.EntityProperty ?? p.Name}" : attribute.EntityProperty, 159 | ExpressionProvider = attribute.ExpressionProvider, 160 | HasNavigation = hasNavigation 161 | }; 162 | } 163 | 164 | var complexSearchProperties = parentSortClass.GetTypeInfo() 165 | .DeclaredProperties 166 | .Where(p => p.GetCustomAttributes().Any()); 167 | 168 | foreach (var parentProperty in complexSearchProperties) 169 | { 170 | var parentType = parentProperty.PropertyType; 171 | var parentAttribute = parentProperty.GetCustomAttribute(); 172 | 173 | var complexProperties = GetTermsFromModel( 174 | parentType, 175 | string.IsNullOrWhiteSpace(parentsEntityName) ? parentAttribute.ParentEntityProperty ?? parentProperty.Name : $"{parentsEntityName}.{parentAttribute.ParentEntityProperty ?? parentProperty.Name}", 176 | string.IsNullOrWhiteSpace(parentsName) ? parentProperty.Name : $"{parentsName}.{parentProperty.Name}", 177 | true); 178 | 179 | foreach (var complexProperty in complexProperties) 180 | { 181 | yield return complexProperty; 182 | } 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Infrastructure/SearchTerm.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Contracts; 2 | 3 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Infrastructure 4 | { 5 | public class SearchTerm 6 | { 7 | public string Name { get; set; } 8 | public string EntityName { get; set; } 9 | public bool HasNavigation { get; set; } 10 | public string Operator { get; set; } 11 | public string Value { get; set; } 12 | public bool ValidSyntax { get; set; } 13 | public ISearchExpressionProvider ExpressionProvider { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Infrastructure/SortOptionsProcessor{T,TEntity}.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes; 2 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | 8 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Infrastructure 9 | { 10 | public static class SortOptionsProcessor 11 | { 12 | private static IEnumerable GetAllTerms(JqueryDataTablesParameters table) 13 | { 14 | var dtColumns = table.Columns as IList ?? table.Columns.ToList(); 15 | 16 | if (dtColumns.All(x => !x.Orderable)) 17 | { 18 | yield break; 19 | } 20 | 21 | foreach (var term in table.Order) 22 | { 23 | var column = dtColumns[term.Column]; 24 | if (!column.Orderable) 25 | { 26 | continue; 27 | } 28 | 29 | var hasNavigation = column.Data.Contains('.'); 30 | 31 | yield return new SortTerm 32 | { 33 | Name = column.Data, 34 | Descending = term.Dir.Equals(DTOrderDir.DESC), 35 | HasNavigation = hasNavigation 36 | }; 37 | } 38 | } 39 | 40 | private static IEnumerable GetValidTerms(JqueryDataTablesParameters table) 41 | { 42 | var queryTerms = GetAllTerms(table).ToArray(); 43 | if (!queryTerms.Any()) 44 | { 45 | yield break; 46 | } 47 | 48 | var declaredTerms = GetTermsFromModel(typeof(T)).ToList(); 49 | 50 | foreach (var term in queryTerms) 51 | { 52 | var declaredTerm = declaredTerms.SingleOrDefault(x => x.Name.Equals(term.Name, StringComparison.OrdinalIgnoreCase)); 53 | if (declaredTerm == null) 54 | { 55 | continue; 56 | } 57 | 58 | yield return new SortTerm 59 | { 60 | Name = declaredTerm.Name, 61 | EntityName = declaredTerm.EntityName, 62 | Descending = term.Descending, 63 | Default = declaredTerm.Default, 64 | HasNavigation = declaredTerm.HasNavigation 65 | }; 66 | } 67 | } 68 | 69 | public static IQueryable Apply(IQueryable query, JqueryDataTablesParameters table) 70 | { 71 | var terms = GetValidTerms(table).ToArray(); 72 | if (!terms.Any()) 73 | { 74 | return query; 75 | } 76 | 77 | var modifiedQuery = query; 78 | var useThenBy = false; 79 | 80 | foreach (var term in terms) 81 | { 82 | var hasMultipleTerms = term.EntityName?.Contains(',') ?? false; 83 | 84 | if (hasMultipleTerms) 85 | { 86 | var entityTerms = term.EntityName.Split(','); 87 | 88 | foreach (var entityTerm in entityTerms) 89 | { 90 | term.EntityName = entityTerm; 91 | 92 | modifiedQuery = GetSortQuery(modifiedQuery, ref useThenBy, term); 93 | } 94 | } 95 | else 96 | { 97 | modifiedQuery = GetSortQuery(modifiedQuery, ref useThenBy, term); 98 | } 99 | } 100 | 101 | return modifiedQuery; 102 | } 103 | 104 | private static IQueryable GetSortQuery(IQueryable modifiedQuery, ref bool useThenBy, SortTerm term) 105 | { 106 | var propertyInfo = ExpressionHelper.GetPropertyInfo(typeof(TEntity), term.EntityName ?? term.Name); 107 | var parameterExpression = ExpressionHelper.Parameter(); 108 | 109 | // Build the LINQ expression backwards: 110 | // query = query.OrderBy(x => x.Property); 111 | // If it has navigation then: 112 | // query = query.OrderBy(x => x.ParentProperty.Property) 113 | 114 | // x => x.Property 115 | var memberExpression = ExpressionHelper.GetMemberExpression(parameterExpression, term.EntityName ?? term.Name); 116 | var lambdaExpression = ExpressionHelper.GetLambda(typeof(TEntity), propertyInfo.PropertyType, 117 | parameterExpression, memberExpression); 118 | 119 | // query.OrderBy/ThenBy[Descending](x => x.Property) 120 | modifiedQuery = ExpressionHelper.CallOrderByOrThenBy(modifiedQuery, useThenBy, term.Descending, 121 | propertyInfo.PropertyType, lambdaExpression); 122 | 123 | useThenBy = true; 124 | 125 | return modifiedQuery; 126 | } 127 | 128 | private static IEnumerable GetTermsFromModel( 129 | Type parentSortClass, 130 | string parentsEntityName = null, 131 | string parentsName = null, 132 | bool hasNavigation = false) 133 | { 134 | var properties = parentSortClass.GetTypeInfo() 135 | .DeclaredProperties 136 | .Where(p => p.GetCustomAttributes().Any()); 137 | 138 | foreach (var p in properties) 139 | { 140 | var attribute = p.GetCustomAttribute(); 141 | 142 | yield return new SortTerm 143 | { 144 | Name = hasNavigation ? $"{parentsName}.{p.Name}" : p.Name, 145 | EntityName = hasNavigation ? $"{parentsEntityName ?? parentsName}.{attribute.EntityProperty ?? p.Name}" : attribute.EntityProperty, 146 | Default = attribute.Default, 147 | HasNavigation = hasNavigation 148 | }; 149 | } 150 | 151 | var complexSortProperties = parentSortClass.GetTypeInfo() 152 | .DeclaredProperties 153 | .Where(p => p.GetCustomAttributes().Any()); 154 | 155 | foreach (var parentProperty in complexSortProperties) 156 | { 157 | var parentType = parentProperty.PropertyType; 158 | var parentAttribute = parentProperty.GetCustomAttribute(); 159 | 160 | var complexProperties = GetTermsFromModel( 161 | parentType, 162 | string.IsNullOrWhiteSpace(parentsEntityName) ? parentAttribute.ParentEntityProperty ?? parentProperty.Name : $"{parentsEntityName}.{parentAttribute.ParentEntityProperty ?? parentProperty.Name}", 163 | string.IsNullOrWhiteSpace(parentsName) ? parentProperty.Name : $"{parentsName}.{parentProperty.Name}", 164 | true); 165 | 166 | foreach (var complexProperty in complexProperties) 167 | { 168 | yield return complexProperty; 169 | } 170 | } 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Infrastructure/SortTerm.cs: -------------------------------------------------------------------------------- 1 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Infrastructure 2 | { 3 | public class SortTerm 4 | { 5 | public string Name { get; set; } 6 | public string EntityName { get; set; } 7 | public bool Descending { get; set; } 8 | public bool Default { get; set; } 9 | public bool HasNavigation { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Models/JqueryDataTablesModel.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System.Collections.Generic; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Models 6 | { 7 | ///This view model class has been referred from example created by Marien Monnier at Soft.it. All credits to Marien for this class 8 | 9 | /// 10 | /// A full result, as understood by jQuery DataTables. 11 | /// 12 | /// The data type of each row. 13 | public class JqueryDataTablesResult 14 | { 15 | /// 16 | /// The draw counter that this object is a response to - from the draw parameter sent as part of the data request. 17 | /// Note that it is strongly recommended for security reasons that you cast this parameter to an integer, rather than simply echoing back to the client what it sent in the draw parameter, in order to prevent Cross Site Scripting (XSS) attacks. 18 | /// 19 | [JsonProperty(PropertyName = "draw")] 20 | [JsonPropertyName("draw")] 21 | public int Draw { get; set; } 22 | 23 | /// 24 | /// Total records, before filtering (i.e. the total number of records in the database) 25 | /// 26 | [JsonProperty(PropertyName = "recordsTotal")] 27 | [JsonPropertyName("recordsTotal")] 28 | public int RecordsTotal { get; set; } 29 | 30 | /// 31 | /// Total records, after filtering (i.e. the total number of records after filtering has been applied - not just the number of records being returned for this page of data). 32 | /// 33 | [JsonProperty(PropertyName = "recordsFiltered")] 34 | [JsonPropertyName("recordsFiltered")] 35 | public int RecordsFiltered { get; set; } 36 | 37 | /// 38 | /// The data to be displayed in the table. 39 | /// This is an array of data source objects, one for each row, which will be used by DataTables. 40 | /// Note that this parameter's name can be changed using the ajaxDT option's dataSrc property. 41 | /// 42 | [JsonProperty(PropertyName = "data")] 43 | [JsonPropertyName("data")] 44 | public IEnumerable Data { get; set; } 45 | } 46 | 47 | /// 48 | /// The additional columns that you can send to jQuery DataTables for automatic processing. 49 | /// 50 | public abstract class DTRow 51 | { 52 | /// 53 | /// Set the ID property of the dt-tag tr node to this value 54 | /// 55 | public virtual string DT_RowId => null; 56 | 57 | /// 58 | /// Add this class to the dt-tag tr node 59 | /// 60 | public virtual string DT_RowClass => null; 61 | 62 | /// 63 | /// Add this data property to the row's dt-tag tr node allowing abstract data to be added to the node, using the HTML5 data-* attributes. 64 | /// This uses the jQuery data() method to set the data, which can also then be used for later retrieval (for example on a click event). 65 | /// 66 | public virtual object DT_RowData => null; 67 | } 68 | 69 | /// 70 | /// The parameters sent by jQuery DataTables in AJAX queries. 71 | /// 72 | public class JqueryDataTablesParameters 73 | { 74 | /// 75 | /// Draw counter. 76 | /// This is used by DataTables to ensure that the Ajax returns from server-side processing requests are drawn in sequence by DataTables (Ajax requests are asynchronous and thus can return out of sequence). 77 | /// This is used as part of the draw return parameter (see below). 78 | /// 79 | public int Draw { get; set; } 80 | 81 | /// 82 | /// An array defining all columns in the table. 83 | /// 84 | public DTColumn[] Columns { get; set; } 85 | 86 | /// 87 | /// An array defining how many columns are being ordering upon - i.e. if the array length is 1, then a single column sort is being performed, otherwise a multi-column sort is being performed. 88 | /// 89 | public DTOrder[] Order { get; set; } 90 | 91 | /// 92 | /// Paging first record indicator. 93 | /// This is the start point in the current data set (0 index based - i.e. 0 is the first record). 94 | /// 95 | public int Start { get; set; } 96 | 97 | /// 98 | /// Number of records that the table can display in the current draw. 99 | /// It is expected that the number of records returned will be equal to this number, unless the server has fewer records to return. 100 | /// Note that this can be -1 to indicate that all records should be returned (although that negates any benefits of server-side processing!) 101 | /// 102 | public int Length { get; set; } 103 | 104 | /// 105 | /// Global search value. To be applied to all columns which have searchable as true. 106 | /// 107 | public DTSearch Search { get; set; } 108 | 109 | /// 110 | /// Custom column that is used to further sort on the first Order column. 111 | /// 112 | public string SortOrder => Columns != null && Order != null && Order.Length > 0 113 | ? (Columns[Order[0].Column].Data + (Order[0].Dir == DTOrderDir.DESC ? " " + Order[0].Dir : string.Empty)) 114 | : null; 115 | 116 | /// 117 | /// For Posting Additional Parameters to Server 118 | /// 119 | public IEnumerable AdditionalValues { get; set; } 120 | 121 | } 122 | 123 | /// 124 | /// A jQuery DataTables column. 125 | /// 126 | public class DTColumn 127 | { 128 | /// 129 | /// Column's data source, as defined by columns.data. 130 | /// 131 | public string Data { get; set; } 132 | 133 | /// 134 | /// Column's name, as defined by columns.name. 135 | /// 136 | public string Name { get; set; } 137 | 138 | /// 139 | /// Flag to indicate if this column is searchable (true) or not (false). This is controlled by columns.searchable. 140 | /// 141 | public bool Searchable { get; set; } 142 | 143 | /// 144 | /// Flag to indicate if this column is orderable (true) or not (false). This is controlled by columns.orderable. 145 | /// 146 | public bool Orderable { get; set; } 147 | 148 | /// 149 | /// Specific search value. 150 | /// 151 | public DTSearch Search { get; set; } 152 | } 153 | 154 | /// 155 | /// An order, as sent by jQuery DataTables when doing AJAX queries. 156 | /// 157 | public class DTOrder 158 | { 159 | /// 160 | /// Column to which ordering should be applied. 161 | /// This is an index reference to the columns array of information that is also submitted to the server. 162 | /// 163 | public int Column { get; set; } 164 | 165 | /// 166 | /// Ordering direction for this column. 167 | /// It will be dt-string asc or dt-string desc to indicate ascending ordering or descending ordering, respectively. 168 | /// 169 | public DTOrderDir Dir { get; set; } 170 | } 171 | 172 | /// 173 | /// Sort orders of jQuery DataTables. 174 | /// 175 | public enum DTOrderDir 176 | { 177 | ASC, 178 | DESC 179 | } 180 | 181 | /// 182 | /// A search, as sent by jQuery DataTables when doing AJAX queries. 183 | /// 184 | public class DTSearch 185 | { 186 | /// 187 | /// Global search value. To be applied to all columns which have searchable as true. 188 | /// 189 | public string Value { get; set; } 190 | 191 | /// 192 | /// true if the global filter should be treated as a regular expression for advanced searching, false otherwise. 193 | /// Note that normally server-side processing scripts will not perform regular expression searching for performance reasons on large data sets, but it is technically possible and at the discretion of your script. 194 | /// 195 | public bool Regex { get; set; } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Models/JqueryDataTablesPagedResults{T}.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Models 4 | { 5 | public class JqueryDataTablesPagedResults 6 | { 7 | public IEnumerable Items { get; set; } 8 | 9 | public int TotalSize { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Models/TableColumn.cs: -------------------------------------------------------------------------------- 1 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Models 2 | { 3 | public class TableColumn 4 | { 5 | public string Name { get; set; } 6 | public bool HasSearch { get; set; } 7 | public int Order { get; set; } 8 | public bool Exclude { get; set; } 9 | public string Type { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Providers/ComparableSearchExpressionProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | 5 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Providers 6 | { 7 | public abstract class ComparableSearchExpressionProvider : DefaultSearchExpressionProvider 8 | { 9 | private const string GreaterThanOperator = "gt"; 10 | private const string GreaterThanEqualToOperator = "gte"; 11 | private const string LessThanOperator = "lt"; 12 | private const string LessThanEqualToOperator = "lte"; 13 | 14 | public override IEnumerable GetOperators() 15 | { 16 | return base.GetOperators() 17 | .Concat( 18 | new[] 19 | { 20 | GreaterThanOperator, 21 | GreaterThanEqualToOperator, 22 | LessThanOperator, 23 | LessThanEqualToOperator 24 | }); 25 | } 26 | 27 | public override Expression GetComparison(MemberExpression left, string op, Expression right) 28 | { 29 | switch (op.ToLower()) 30 | { 31 | case GreaterThanOperator: return Expression.GreaterThan(left, right); 32 | case GreaterThanEqualToOperator: return Expression.GreaterThanOrEqual(left, right); 33 | case LessThanOperator: return Expression.LessThan(left, right); 34 | case LessThanEqualToOperator: return Expression.LessThanOrEqual(left, right); 35 | default: return base.GetComparison(left, op, right); 36 | }; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Providers/DateTimeSearchExpressionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Providers 5 | { 6 | public class DateTimeSearchExpressionProvider : ComparableSearchExpressionProvider 7 | { 8 | public override ConstantExpression GetValue(string input) 9 | { 10 | if (!DateTime.TryParse(input, out var value)) 11 | { 12 | throw new ArgumentException("Invalid search value."); 13 | } 14 | 15 | return Expression.Constant(value); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Providers/DecimalSearchExpressionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Providers 5 | { 6 | public class DecimalSearchExpressionProvider : ComparableSearchExpressionProvider 7 | { 8 | public override ConstantExpression GetValue(string input) 9 | { 10 | if (!decimal.TryParse(input, out var value)) 11 | { 12 | throw new ArgumentException("Invalid search value."); 13 | } 14 | 15 | return Expression.Constant(value); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Providers/DefaultSearchExpressionProvider.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Contracts; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | 6 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Providers 7 | { 8 | public class DefaultSearchExpressionProvider : ISearchExpressionProvider 9 | { 10 | protected const string EqualsOperator = "eq"; 11 | 12 | public virtual IEnumerable GetOperators() 13 | { 14 | yield return EqualsOperator; 15 | } 16 | 17 | public virtual ConstantExpression GetValue(string input) 18 | { 19 | return Expression.Constant(input); 20 | } 21 | 22 | public virtual Expression GetComparison(MemberExpression left, string op, Expression right) 23 | { 24 | switch (op.ToLower()) 25 | { 26 | case EqualsOperator: return Expression.Equal(left, right); 27 | default: throw new ArgumentException($"Invalid Operator '{op}'."); 28 | }; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Providers/DoubleSearchExpressionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Providers 5 | { 6 | public class DoubleSearchExpressionProvider : ComparableSearchExpressionProvider 7 | { 8 | public override ConstantExpression GetValue(string input) 9 | { 10 | if (!double.TryParse(input, out var value)) 11 | { 12 | throw new ArgumentException("Invalid search value."); 13 | } 14 | 15 | return Expression.Constant(value); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Providers/EnumSearchExpressionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Providers 5 | { 6 | public class EnumSearchExpressionProvider : EnumerationSearchExpressionProvider where TEnum : struct 7 | { 8 | public override ConstantExpression GetValue(string input) 9 | { 10 | //if (!Enum.TryParse(input.Trim().Replace(" ", string.Empty), true, out var value)) 11 | //{ 12 | // throw new ArgumentException("Invalid Search value."); 13 | //} 14 | 15 | return Expression.Constant(input); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Providers/EnumerationSearchExpressionProvider.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Infrastructure; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | 6 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Providers 7 | { 8 | public class EnumerationSearchExpressionProvider : DefaultSearchExpressionProvider 9 | { 10 | private static readonly MethodInfo _stringContainsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) }); 11 | protected const string ContainsOperator = "co"; 12 | public override IEnumerable GetOperators() 13 | { 14 | return base.GetOperators(); 15 | } 16 | 17 | public override Expression GetComparison(MemberExpression left, string op, Expression right) 18 | { 19 | switch (op.ToLower()) 20 | { 21 | case EqualsOperator: return Expression.Equal(left.CastToObjectAndString().TrimToLower(), right.TrimToLower()); 22 | case ContainsOperator: return Expression.Call(left.CastToObjectAndString().TrimToLower(), _stringContainsMethod, right.TrimToLower()); 23 | default: return base.GetComparison(left, op, right); 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /AspNetCoreWeb/Providers/IntSearchExpressionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Providers 5 | { 6 | public class IntSearchExpressionProvider : ComparableSearchExpressionProvider 7 | { 8 | public override ConstantExpression GetValue(string input) 9 | { 10 | if (!int.TryParse(input, out var value)) 11 | { 12 | throw new ArgumentException("Invalid search value."); 13 | } 14 | 15 | return Expression.Constant(value); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Providers/LongSearchExpressionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Providers 5 | { 6 | public class LongSearchExpressionProvider : ComparableSearchExpressionProvider 7 | { 8 | public override ConstantExpression GetValue(string input) 9 | { 10 | if (!long.TryParse(input, out var value)) 11 | { 12 | throw new ArgumentException("Invalid search value."); 13 | } 14 | 15 | return Expression.Constant(value); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Providers/ShortSearchExpressionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Providers 5 | { 6 | public class ShortSearchExpressionProvider : ComparableSearchExpressionProvider 7 | { 8 | public override ConstantExpression GetValue(string input) 9 | { 10 | if (!short.TryParse(input, out var value)) 11 | { 12 | throw new ArgumentException("Invalid search value."); 13 | } 14 | 15 | return Expression.Constant(value); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AspNetCoreWeb/Providers/StringSearchExpressionProvider.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Infrastructure; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | 8 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.Providers 9 | { 10 | public class StringSearchExpressionProvider : DefaultSearchExpressionProvider 11 | { 12 | private const string StartsWithOperator = "sw"; 13 | private const string ContainsOperator = "co"; 14 | 15 | private static readonly MethodInfo StartsWithMethod = typeof(string) 16 | .GetMethods() 17 | .First(x => x.Name == "StartsWith" && x.GetParameters().Length == 2); 18 | 19 | private static readonly MethodInfo StringEqualsMethod = typeof(string) 20 | .GetMethods() 21 | .First(x => x.Name == "Equals" && x.GetParameters().Length == 2); 22 | 23 | private static readonly MethodInfo ContainsMethod = typeof(string) 24 | .GetMethods() 25 | .First(x => x.Name == "Contains" && x.GetParameters().Length == 1); 26 | 27 | private static readonly MethodInfo ToLowerMethod = typeof(string) 28 | .GetMethods() 29 | .First(x => x.Name == "ToLower"); 30 | 31 | private static readonly MethodInfo _stringContainsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) }); 32 | 33 | private static ConstantExpression IgnoreCase 34 | => Expression.Constant(StringComparison.OrdinalIgnoreCase); 35 | 36 | public override IEnumerable GetOperators() 37 | { 38 | return base.GetOperators() 39 | .Concat( 40 | new[] 41 | { 42 | StartsWithOperator, 43 | ContainsOperator 44 | }); 45 | } 46 | 47 | public override Expression GetComparison(MemberExpression left, string op, Expression right) 48 | { 49 | switch (op.ToLower()) 50 | { 51 | case StartsWithOperator: return Expression.Call(left, StartsWithMethod, right, IgnoreCase); 52 | case ContainsOperator: return Expression.Call(left.TrimToLower(), _stringContainsMethod, right.TrimToLower()); 53 | case EqualsOperator: return Expression.Equal(left.TrimToLower(), right.TrimToLower()); 54 | default: return base.GetComparison(left, op, right); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /AspNetCoreWeb/TagHelpers/JqueryDataTablesHtmlLocalizedTagHelper.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes; 2 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Infrastructure; 3 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Models; 4 | using Microsoft.AspNetCore.Mvc.Localization; 5 | using Microsoft.AspNetCore.Mvc.ViewFeatures; 6 | using Microsoft.AspNetCore.Razor.TagHelpers; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Reflection; 11 | using System.Text; 12 | 13 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.TagHelpers 14 | { 15 | [HtmlTargetElement("jquery-datatables-html-localized", Attributes = "id,class,model")] 16 | public class JqueryDataTablesHtmlLocalizedTagHelper : TagHelper 17 | { 18 | private readonly IHtmlLocalizer _localizer; 19 | private static readonly Dictionary _defaultInputTypes = 20 | new Dictionary(StringComparer.OrdinalIgnoreCase) 21 | { 22 | { "Text", InputType.Text.ToString().ToLowerInvariant() }, 23 | { "PhoneNumber", "tel" }, 24 | { "Url", "url" }, 25 | { "EmailAddress", "email" }, 26 | { "Date", "date" }, 27 | { "DateTime", "datetime-local" }, 28 | { "DateTime-local", "datetime-local" }, 29 | { nameof(DateTimeOffset), "text" }, 30 | { "Time", "time" }, 31 | { "Week", "week" }, 32 | { "Month", "month" }, 33 | { nameof(Byte), "number" }, 34 | { nameof(SByte), "number" }, 35 | { nameof(Int16), "number" }, 36 | { nameof(UInt16), "number" }, 37 | { nameof(Int32), "number" }, 38 | { nameof(UInt32), "number" }, 39 | { nameof(Int64), "number" }, 40 | { nameof(UInt64), "number" }, 41 | { nameof(Single), InputType.Text.ToString().ToLowerInvariant() }, 42 | { nameof(Double), InputType.Text.ToString().ToLowerInvariant() }, 43 | { nameof(Boolean), InputType.CheckBox.ToString().ToLowerInvariant() }, 44 | { nameof(Decimal), InputType.Text.ToString().ToLowerInvariant() }, 45 | { nameof(String), InputType.Text.ToString().ToLowerInvariant() } 46 | }; 47 | 48 | public JqueryDataTablesHtmlLocalizedTagHelper(IHtmlLocalizer localizer) 49 | { 50 | _localizer = localizer; 51 | } 52 | 53 | public string Id { get; set; } 54 | public string Class { get; set; } 55 | public object Model { get; set; } 56 | 57 | [HtmlAttributeName("enable-searching")] 58 | public bool EnableSearching { get; set; } 59 | 60 | [HtmlAttributeName("thead-class")] 61 | public string TheadClass { get; set; } 62 | 63 | [HtmlAttributeName("search-row-th-class")] 64 | public string SearchRowThClass { get; set; } 65 | 66 | [HtmlAttributeName("search-input-class")] 67 | public string SearchInputClass { get; set; } 68 | 69 | [HtmlAttributeName("search-input-style")] 70 | public string SearchInputStyle { get; set; } 71 | 72 | [HtmlAttributeName("search-input-placeholder-prefix")] 73 | public string SearchInputPlaceholderPrefix { get; set; } 74 | 75 | [HtmlAttributeName("use-property-type-as-input-type")] 76 | public bool UsePropertyTypeAsInputType { get; set; } 77 | 78 | public override void Process(TagHelperContext context, TagHelperOutput output) 79 | { 80 | output.TagName = "table"; 81 | output.Attributes.Add("id", Id); 82 | output.Attributes.Add("class", Class); 83 | 84 | output.PreContent.SetHtmlContent($@""); 85 | 86 | var headerRow = new StringBuilder(); 87 | var searchRow = new StringBuilder(); 88 | 89 | headerRow.AppendLine(""); 90 | 91 | if (EnableSearching) 92 | { 93 | searchRow.AppendLine(""); 94 | } 95 | 96 | var columns = GetColumnsFromModel(Model.GetType()).Where(c => !c.Exclude).OrderBy(c => c.Order); 97 | 98 | foreach (var column in columns) 99 | { 100 | headerRow.AppendLine($"{_localizer[column.Name].Value}"); 101 | 102 | if (!EnableSearching) 103 | { 104 | continue; 105 | } 106 | 107 | searchRow.AppendLine($@"{_localizer[column.Name].Value}"); 108 | 109 | if (column.HasSearch) 110 | { 111 | searchRow.AppendLine($@""); 112 | } 113 | 114 | searchRow.AppendLine(""); 115 | } 116 | 117 | headerRow.AppendLine(""); 118 | if (EnableSearching) 119 | { 120 | searchRow.AppendLine(""); 121 | } 122 | 123 | output.Content.SetHtmlContent($"{headerRow}{searchRow}"); 124 | output.PostContent.SetHtmlContent(""); 125 | } 126 | 127 | private static IEnumerable GetColumnsFromModel(Type parentClass) 128 | { 129 | var complexProperties = parentClass.GetProperties() 130 | .Where(p => p.GetCustomAttributes().Any() || p.GetCustomAttributes().Any()); 131 | 132 | var properties = parentClass.GetProperties(); 133 | 134 | foreach (var prop in properties.Except(complexProperties)) 135 | { 136 | var jqueryDataTableColumn = prop.GetCustomAttribute(); 137 | 138 | yield return new TableColumn 139 | { 140 | Name = ExpressionHelper.GetPropertyDisplayName(prop), 141 | HasSearch = prop.GetCustomAttributes().Any(), 142 | Order = jqueryDataTableColumn != null ? jqueryDataTableColumn.Order : 0, 143 | Exclude = jqueryDataTableColumn != null ? jqueryDataTableColumn.Exclude : true, 144 | Type = _defaultInputTypes.TryGetValue(prop.PropertyType.Name, out var inputType) ? inputType : "search" 145 | }; 146 | } 147 | 148 | if (complexProperties.Any()) 149 | { 150 | foreach (var parentProperty in complexProperties) 151 | { 152 | var parentType = parentProperty.PropertyType; 153 | 154 | var nestedProperties = GetColumnsFromModel(parentType); 155 | 156 | foreach (var nestedProperty in nestedProperties) 157 | { 158 | yield return nestedProperty; 159 | } 160 | } 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /AspNetCoreWeb/TagHelpers/JqueryDataTablesTagHelper.cs: -------------------------------------------------------------------------------- 1 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Attributes; 2 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Infrastructure; 3 | using JqueryDataTables.ServerSide.AspNetCoreWeb.Models; 4 | using Microsoft.AspNetCore.Mvc.ViewFeatures; 5 | using Microsoft.AspNetCore.Razor.TagHelpers; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Text; 11 | 12 | namespace JqueryDataTables.ServerSide.AspNetCoreWeb.TagHelpers 13 | { 14 | [HtmlTargetElement("jquery-datatables", Attributes = "id,class,model")] 15 | public class JqueryDataTablesTagHelper : TagHelper 16 | { 17 | private static readonly Dictionary _defaultInputTypes = 18 | new Dictionary(StringComparer.OrdinalIgnoreCase) 19 | { 20 | { "Text", InputType.Text.ToString().ToLowerInvariant() }, 21 | { "PhoneNumber", "tel" }, 22 | { "Url", "url" }, 23 | { "EmailAddress", "email" }, 24 | { "Date", "date" }, 25 | { "DateTime", "datetime-local" }, 26 | { "DateTime-local", "datetime-local" }, 27 | { nameof(DateTimeOffset), "text" }, 28 | { "Time", "time" }, 29 | { "Week", "week" }, 30 | { "Month", "month" }, 31 | { nameof(Byte), "number" }, 32 | { nameof(SByte), "number" }, 33 | { nameof(Int16), "number" }, 34 | { nameof(UInt16), "number" }, 35 | { nameof(Int32), "number" }, 36 | { nameof(UInt32), "number" }, 37 | { nameof(Int64), "number" }, 38 | { nameof(UInt64), "number" }, 39 | { nameof(Single), InputType.Text.ToString().ToLowerInvariant() }, 40 | { nameof(Double), InputType.Text.ToString().ToLowerInvariant() }, 41 | { nameof(Boolean), InputType.CheckBox.ToString().ToLowerInvariant() }, 42 | { nameof(Decimal), InputType.Text.ToString().ToLowerInvariant() }, 43 | { nameof(String), InputType.Text.ToString().ToLowerInvariant() } 44 | }; 45 | 46 | public string Id { get; set; } 47 | public string Class { get; set; } 48 | public object Model { get; set; } 49 | 50 | [HtmlAttributeName("enable-searching")] 51 | public bool EnableSearching { get; set; } 52 | 53 | [HtmlAttributeName("thead-class")] 54 | public string TheadClass { get; set; } 55 | 56 | [HtmlAttributeName("search-row-th-class")] 57 | public string SearchRowThClass { get; set; } 58 | 59 | [HtmlAttributeName("search-input-class")] 60 | public string SearchInputClass { get; set; } 61 | 62 | [HtmlAttributeName("search-input-style")] 63 | public string SearchInputStyle { get; set; } 64 | 65 | [HtmlAttributeName("search-input-placeholder-prefix")] 66 | public string SearchInputPlaceholderPrefix { get; set; } 67 | 68 | [HtmlAttributeName("use-property-type-as-input-type")] 69 | public bool UsePropertyTypeAsInputType { get; set; } 70 | 71 | public override void Process(TagHelperContext context, TagHelperOutput output) 72 | { 73 | output.TagName = "table"; 74 | output.Attributes.Add("id", Id); 75 | output.Attributes.Add("class", Class); 76 | 77 | output.PreContent.SetHtmlContent($@""); 78 | 79 | var headerRow = new StringBuilder(); 80 | var searchRow = new StringBuilder(); 81 | 82 | headerRow.AppendLine(""); 83 | 84 | if (EnableSearching) 85 | { 86 | searchRow.AppendLine(""); 87 | } 88 | 89 | var columns = GetColumnsFromModel(Model.GetType()).Where(c => !c.Exclude).OrderBy(c => c.Order); 90 | 91 | foreach (var column in columns) 92 | { 93 | headerRow.AppendLine($"{column.Name}"); 94 | 95 | if (!EnableSearching) 96 | { 97 | continue; 98 | } 99 | 100 | searchRow.AppendLine($@"{column.Name}"); 101 | 102 | if (column.HasSearch) 103 | { 104 | searchRow.AppendLine($@""); 105 | } 106 | 107 | searchRow.AppendLine(""); 108 | } 109 | 110 | headerRow.AppendLine(""); 111 | if (EnableSearching) 112 | { 113 | searchRow.AppendLine(""); 114 | } 115 | 116 | output.Content.SetHtmlContent($"{headerRow}{searchRow}"); 117 | output.PostContent.SetHtmlContent(""); 118 | } 119 | 120 | private static IEnumerable GetColumnsFromModel(Type parentClass) 121 | { 122 | var complexProperties = parentClass.GetProperties() 123 | .Where(p => p.GetCustomAttributes().Any() || p.GetCustomAttributes().Any()); 124 | 125 | var properties = parentClass.GetProperties(); 126 | 127 | foreach (var prop in properties.Except(complexProperties)) 128 | { 129 | var jqueryDataTableColumn = prop.GetCustomAttribute(); 130 | 131 | yield return new TableColumn 132 | { 133 | Name = ExpressionHelper.GetPropertyDisplayName(prop), 134 | HasSearch = prop.GetCustomAttributes().Any(), 135 | Order = jqueryDataTableColumn != null ? jqueryDataTableColumn.Order : 0, 136 | Exclude = jqueryDataTableColumn != null ? jqueryDataTableColumn.Exclude : true, 137 | Type = _defaultInputTypes.TryGetValue(prop.PropertyType.Name, out var inputType) ? inputType : "search" 138 | }; 139 | } 140 | 141 | if (complexProperties.Any()) 142 | { 143 | foreach (var parentProperty in complexProperties) 144 | { 145 | var parentType = parentProperty.PropertyType; 146 | 147 | var nestedProperties = GetColumnsFromModel(parentType); 148 | 149 | foreach (var nestedProperty in nestedProperties) 150 | { 151 | yield return nestedProperty; 152 | } 153 | } 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /JqueryDataTablesServerSide.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29326.143 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreWeb", "AspNetCoreWeb\AspNetCoreWeb.csproj", "{69C5BFB4-394B-4269-842F-7290FBCF9BBF}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {69C5BFB4-394B-4269-842F-7290FBCF9BBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {69C5BFB4-394B-4269-842F-7290FBCF9BBF}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {69C5BFB4-394B-4269-842F-7290FBCF9BBF}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {69C5BFB4-394B-4269-842F-7290FBCF9BBF}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {2EF15547-44BD-4F5A-A4B1-DE99BA3A956A} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Abdul Rahman (@fingers10) 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](https://github.com/fingers10/JqueryDataTablesServerSide/blob/master/AspNetCoreWeb/Images/icon.png) 2 | 3 | [![NuGet Badge](https://buildstats.info/nuget/jquerydatatables.serverside.aspnetcoreweb)](https://www.nuget.org/packages/JqueryDatatables.ServerSide.AspNetCoreWeb/) 4 | [![Open Source Love svg1](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)](https://github.com/fingers10/open-source-badges/) 5 | 6 | [![GitHub license](https://img.shields.io/github/license/fingers10/JqueryDataTablesServerSide.svg)](https://github.com/fingers10/JqueryDataTablesServerSide/blob/master/LICENSE) 7 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/fingers10/JqueryDataTablesServerSide/graphs/commit-activity) 8 | [![Ask Me Anything !](https://img.shields.io/badge/Ask%20me-anything-1abc9c.svg)](https://GitHub.com/fingers10/JqueryDataTablesServerSide) 9 | [![HitCount](http://hits.dwyl.io/fingers10/badges.svg)](http://hits.dwyl.io/fingers10/badges) 10 | 11 | [![GitHub forks](https://img.shields.io/github/forks/fingers10/JqueryDataTablesServerSide.svg?style=social&label=Fork)](https://GitHub.com/fingers10/JqueryDataTablesServerSide/network/) 12 | [![GitHub stars](https://img.shields.io/github/stars/fingers10/JqueryDataTablesServerSide.svg?style=social&label=Star)](https://GitHub.com/fingers10/JqueryDataTablesServerSide/stargazers/) 13 | [![GitHub followers](https://img.shields.io/github/followers/fingers10.svg?style=social&label=Follow)](https://github.com/fingers10?tab=followers) 14 | 15 | [![GitHub contributors](https://img.shields.io/github/contributors/fingers10/JqueryDataTablesServerSide.svg)](https://GitHub.com/fingers10/JqueryDataTablesServerSide/graphs/contributors/) 16 | [![GitHub issues](https://img.shields.io/github/issues/fingers10/JqueryDataTablesServerSide.svg)](https://GitHub.com/fingers10/JqueryDataTablesServerSide/issues/) 17 | [![GitHub issues-closed](https://img.shields.io/github/issues-closed/fingers10/JqueryDataTablesServerSide.svg)](https://GitHub.com/fingers10/JqueryDataTablesServerSide/issues?q=is%3Aissue+is%3Aclosed) 18 | 19 | # Jquery DataTables Asp.Net Core Server Side 20 | This repository is a Server Side processor for Jquery DataTables with Asp.Net Core as backend. It provides a quick way to implement dynamic multiple column searching and sorting along with pagination and excel export at the server side. This can be done by decorating your model properties with simple attributes. 21 | 22 | ![Demo](https://github.com/fingers10/JqueryDataTablesServerSideDemo/blob/master/AspNetCoreServerSide/wwwroot/images/demo.gif) 23 | 24 | **[Demo Implementation Project URL - Free Download](https://github.com/fingers10/JqueryDataTablesServerSideDemo)** 25 | 26 | >**Note: This tutorial contains example for both AJAX GET and AJAX POST Server Side Configuration.** 27 | 28 | >**Warning: If we are RESTful strict, we should use GET Method to get information not POST but I prefer this way to avoid limitations related to form data through the query string, so up to you if you want to use GET. I recommend using AJAX GET only if your DataTable has very less number of columns. As Jquery DataTables AJAX requests produces too large query string which will be rejected by server.** 29 | 30 | # Wait - Why JqueryDataTablesServerSide ? 31 | Well... there are lots of different approaches how to get a Jquery DataTables with Asp.Net Core app running. I thought it would be nice for .NET devs to use the ASP.NET Core backend and just decorate the model properties with a pretty simple attributes called `[Searchable]` and `[Sortable]`. `[DisplayName(“”)]` as the name suggests, can be used to change the column name in excel export or display name in the table HTML. I just combine ASP.NET Core & Jquery DataTables for easy server side processing. 32 | 33 | # Give a Star ⭐️ 34 | If you liked `JqueryDataTablesServerSide` project or if it helped you, please give a star ⭐️ for this repository. That will not only help strengthen our .NET community but also improve development skills for .NET developers in around the world. Thank you very much 👍 35 | 36 | ## Search 37 | * `[Searchable]` 38 | * `[SearchableString]` 39 | * `[SearchableDateTime]` 40 | * `[SearchableShort]` 41 | * `[SearchableInt]` 42 | * `[SearchableLong]` 43 | * `[SearchableDecimal]` 44 | * `[SearchableDouble]` 45 | * `[SearchableEnum(typeof(TEnum))]` 46 | * `[NestedSearchable]` 47 | 48 | ## Sort 49 | * `[Sortable]` 50 | * `[Sortable(Default = true)]` 51 | * `[NestedSortable]` 52 | 53 | All the above attributes have the following options, 54 | 55 | |Mode |Option |Type |Example |Description| 56 | |-------------|--------------------|--------|-------------------------------------------------------------|-----------| 57 | |Search |EntityProperty |`string`|`[Searchable*(EntityProperty = "EntityPropertyName")]`|To map your view model property with entity property if they have a different name| 58 | |Nested Search|ParentEntityProperty|`string`|`[NestedSearchable(ParentEntityProperty = "ParentEntityPropertyName")]`|To map your view model property with entity property if they have a different name| 59 | |Sort |EntityProperty |`string`|`[Sortable(EntityProperty = "EntityPropertyName")]`|To map your view model property with entity property if they have a different name| 60 | |Sort |Default |`bool` |`[Sortable(Default = true)]`|To indicate your database to do default sort by this property if no sort is specified from client| 61 | |Nested Sort |ParentEntityProperty|`string`|`[NestedSortable(EntityProperty = "EntityPropertyName")]`|To map your view model property with entity property if they have a different name| 62 | 63 | ## Columns 64 | ### Name 65 | Column names in HTML Table can be configured using the below attributes 66 | * `[Display(Name = "")]` 67 | * `[DisplayName(“”)]` 68 | 69 | ### HTML Setup 70 | To customize the HTML Column display in `` Tag Helper, use the following attribute 71 | * `[JqueryDataTableColumn]` 72 | 73 | And here are the options, 74 | 75 | |Option |Type |Example |Description| 76 | |-------|--------|-----------------------------------------|-----------| 77 | |Exclude|`bool` |`[JqueryDataTableColumn(Exclude = true)]`|To exclude the property of your model from being added in HTML| 78 | |Order |`int` |`[JqueryDataTableColumn(Order = N)]` |To control the order of columns in HTML| 79 | 80 | **Please note:** From **v.3.2.0** all the simple properties in your models **must have `[JqueryDataTableColumn]` attribute** for the `` Tag Helper to work. 81 | 82 | # Compatibility Chart 83 | >The following chart describes the operator compatibility with data types with green as compatible and red as not compatible. 84 | 85 | |Operator|Description|`string`|`DateTime`|`short`|`int`|`long`|`decimal`|`double`|`enum`| 86 | |--------|-----------|--------|----------|-------|-----|------|---------|--------|------| 87 | |`co` |Contains |:heavy_check_mark:|:x:|:x:|:x:|:x:|:x:|:x:|:heavy_check_mark:| 88 | |`eq` |Equals | :heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:| 89 | |`gt` |GreaterThan| :x:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:x:| 90 | |`gte` |GreaterThanEqual| :x:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:x:| 91 | |`lt` |LesserThan| :x:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:x:| 92 | |`lte` |LesserThanEqual| :x:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:x:| 93 | 94 | # NuGet: 95 | * [JqueryDataTables.ServerSide.AspNetCoreWeb](https://www.nuget.org/packages/JqueryDataTables.ServerSide.AspNetCoreWeb/) **v4.0.0** 96 | 97 | # Usage: 98 | To activate and make Jquery DataTable communicate with asp.net core backend, 99 | 100 | ## Package Manager: 101 | ```c# 102 | PM> Install-Package JqueryDataTables.ServerSide.AspNetCoreWeb 103 | ``` 104 | 105 | ## .NET CLI: 106 | ```c# 107 | > dotnet add package JqueryDataTables.ServerSide.AspNetCoreWeb 108 | ``` 109 | 110 | # Startup.cs 111 | 112 | ## Asp.Net Core 3.x: 113 | 114 | **Json.NET** has been removed from the ASP.NET Core shared framework. 115 | 116 | The default for ASP.NET Core is now `System.Text.Json`, which is new in .NET Core 3.0. Consider using `System.Text.Json` when possible. It's high-performance and doesn't require an additional library dependency. I prefer to use Miscrosoft's new `System.Text.Json`. 117 | 118 | With **System.Text.Json**, setup your `ConfigureServices` as follows: 119 | 120 | ```c# 121 | public void ConfigureServices(IServiceCollection services) 122 | { 123 | services.AddControllersWithViews() 124 | .AddJsonOptions(options => 125 | { 126 | options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); 127 | options.JsonSerializerOptions.PropertyNamingPolicy = null; 128 | }); 129 | services.AddSession(); 130 | services.AddAutoMapper(typeof(Startup)); 131 | } 132 | ``` 133 | 134 | If your using **Json.Net**, then add a package reference to ` Microsoft.AspNetCore.Mvc.NewtonsoftJson` and then setup your `ConfigureServices` as follows: 135 | 136 | ```c# 137 | public void ConfigureServices(IServiceCollection services) 138 | { 139 | services.AddControllersWithViews() 140 | .AddNewtonsoftJson(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver()); 141 | services.AddSession(); 142 | services.AddAutoMapper(typeof(Startup)); 143 | } 144 | ``` 145 | 146 | ## Asp.Net Core 2.x: 147 | 148 | If you're using Asp.Net Core 2.x, then setup your `ConfigureServices` as follows, 149 | 150 | ```c# 151 | public void ConfigureServices(IServiceCollection services) 152 | { 153 | services.AddMvc() 154 | .AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver()) 155 | services.AddSession(); 156 | services.AddAutoMapper(typeof(Startup)); 157 | } 158 | ``` 159 | 160 | **Please note:** `services.AddSession()` is is required only if you're using excel export functionality in Jquery DataTables. 161 | 162 | # Tag Helpers 163 | Add a **JqueryDataTables TagHelper** reference to your `_ViewImports.cshtml` file as shown below 164 | 165 | ```c# 166 | @addTagHelper *, JqueryDataTables.ServerSide.AspNetCoreWeb 167 | ``` 168 | 169 | # Table HTML 170 | >I have written `` TagHelper to do the heavy lifting works for you. 171 | 172 | ```c# 173 | 182 | 183 | ``` 184 | 185 | **Please note:** If you prefer HTML Localization then, `` Tag Helper is available with all the above properties. 186 | 187 | ## TagHelpers Attributes 188 | | Option | Description | 189 | |-----------------------------------|-------------| 190 | | `id` | to add id to the html table | 191 | | `class` | to apply the given css class to the html table | 192 | | `model` | view model with properties to generate columns for html table | 193 | | `thead-class` | to apply the given css class to the `` in html table | 194 | | `enable-searching` | `true` to add search inputs to the `` and `false` to remove search inputs from the `` | 195 | | `search-row-th-class` | to apply the given css class to the search inputs row of the `` in the html table | 196 | | `search-input-class` | to apply the given css class to the search input controls added in each column inside `` | 197 | | `search-input-style` | to apply the given css styles to the search input controls added in each column inside `` | 198 | | `search-input-placeholder-prefix` | to apply your placeholder text as prefix in search input controls in each column inside `` | 199 | |`use-property-type-as-input-type` | to generate `HTML5` type input controls based on property type | 200 | 201 | # Initialize DataTable 202 | >Add the following code to initialize DataTable. Don't miss to add `orderCellsTop : true`. This makes sure to add sorting functionality to the first row in the table header. For other properties refer Jquery DataTables official documentation. 203 | 204 | >Use `AdditionalValues` to pass extra parameters if required to the server for processing. Configure Column properties and add the required search operator in the `name` property to perform search based on the operator in the `name` property. If name property is `null` or `string.Empty`, the search default's to `Equals` search operation. 205 | 206 | **Please note:** Search Operator must be one among the following `eq | co | gt | gte | lt | lte` based on the above compatibility chart. 207 | 208 | ## AJAX POST Configuration 209 | 210 | ```js 211 | var table = $('#fingers10').DataTable({ 212 | language: { 213 | processing: "Loading Data...", 214 | zeroRecords: "No matching records found" 215 | }, 216 | processing: true, 217 | serverSide: true, 218 | orderCellsTop: true, 219 | autoWidth: true, 220 | deferRender: true, 221 | lengthMenu: [[5, 10, 15, 20, -1], [5, 10, 15, 20, "All"]], 222 | dom: '<"row"<"col-sm-12 col-md-6"B><"col-sm-12 col-md-6 text-right"l>><"row"<"col-sm-12"tr>><"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>', 223 | buttons: [ 224 | { 225 | text: 'Export to Excel', 226 | className: 'btn btn-sm btn-dark', 227 | action: function (e, dt, node, config) { 228 | window.location.href = "/Home/GetExcel"; 229 | }, 230 | init: function (api, node, config) { 231 | $(node).removeClass('dt-button'); 232 | } 233 | } 234 | ], 235 | ajax: { 236 | type: "POST", 237 | url: '/Home/LoadTable/', 238 | contentType: "application/json; charset=utf-8", 239 | async: true, 240 | headers: { 241 | "XSRF-TOKEN": document.querySelector('[name="__RequestVerificationToken"]').value 242 | }, 243 | data: function (data) { 244 | let additionalValues = []; 245 | additionalValues[0] = "Additional Parameters 1"; 246 | additionalValues[1] = "Additional Parameters 2"; 247 | data.AdditionalValues = additionalValues; 248 | 249 | return JSON.stringify(data); 250 | } 251 | }, 252 | columns: [ 253 | { 254 | data: "Id", 255 | name: "eq", 256 | visible: false, 257 | searchable: false 258 | }, 259 | { 260 | data: "Name", 261 | name: "eq" 262 | }, 263 | { 264 | data: "Position", 265 | name: "co" 266 | }, 267 | { 268 | data: "Offices", 269 | name: "eq" 270 | }, 271 | { 272 | data: "DemoNestedLevelOne.Experience", 273 | name: "eq" 274 | }, 275 | { 276 | data: "DemoNestedLevelOne.Extension", 277 | name: "eq" 278 | }, 279 | { 280 | data: "DemoNestedLevelOne.DemoNestedLevelTwos.StartDates", 281 | render: function (data, type, row) { 282 | if (data) 283 | return window.moment(data).format("DD/MM/YYYY"); 284 | else 285 | return null; 286 | }, 287 | name: "gt" 288 | }, 289 | { 290 | data: "DemoNestedLevelOne.DemoNestedLevelTwos.Salary", 291 | name: "lte" 292 | } 293 | ] 294 | }); 295 | ``` 296 | 297 | ## AJAX GET Configuration 298 | 299 | For AJAX GET configuration, simply change the `ajax` and `buttons` options as follows, 300 | 301 | ```js 302 | buttons: [ 303 | { 304 | text: 'Export to Excel', 305 | className: 'btn btn-sm btn-dark', 306 | action: function (e, dt, node, config) { 307 | var data = table.ajax.params(); 308 | var x = JSON.stringify(data, null, 4); 309 | window.location.href = "/Home/GetExcel?" + $.param(data); 310 | }, 311 | init: function (api, node, config) { 312 | $(node).removeClass('dt-button'); 313 | } 314 | } 315 | ], 316 | ajax: { 317 | url: '/Home/LoadTable/', 318 | data: function (data) { 319 | return $.extend({}, data, { 320 | "additionalValues[0]": "Additional Parameters 1", 321 | "additionalValues[1]": "Additional Parameters 2" 322 | }); 323 | } 324 | } 325 | ``` 326 | 327 | # Trigger Search 328 | >Add the following script to trigger search only onpress of **Enter Key**. 329 | 330 | ```js 331 | table.columns().every(function (index) { 332 | $('#fingers10 thead tr:last th:eq(' + index + ') input') 333 | .on('keyup', 334 | function (e) { 335 | if (e.keyCode === 13) { 336 | table.column($(this).parent().index() + ':visible').search(this.value).draw(); 337 | } 338 | }); 339 | }); 340 | ``` 341 | 342 | >Add the following script to trigger search onpress of **Tab Key** 343 | 344 | ```js 345 | $('#fingers10 thead tr:last th:eq(' + index + ') input') 346 | .on('blur', 347 | function () { 348 | table.column($(this).parent().index() + ':visible').search(this.value).draw(); 349 | }); 350 | ``` 351 | 352 | # Model to be passed to DataTable 353 | >Decorate the properties based on their data types. For Nested Complex Properties, decorate them with `[NestedSearchable]`/`[NestedSortable]`/`[NestedIncludeInReport]` attributes upto any level. 354 | 355 | ## Root Model: 356 | 357 | ```c# 358 | public class Demo 359 | { 360 | [JqueryDataTableColumn(Order = 1)] 361 | public int Id { get; set; } 362 | 363 | [IncludeInReport(Order = 1)] 364 | [JqueryDataTableColumn(Order = 2)] 365 | [SearchableString(EntityProperty = "FirstName,LastName")] 366 | [Sortable(EntityProperty = "FirstName,LastName", Default = true)] 367 | public string Name { get => $"{FirstName} {LastName}"; } 368 | 369 | [JqueryDataTableColumn(Exclude = true)] 370 | public string FirstName { get; set; } 371 | 372 | [JqueryDataTableColumn(Exclude = true)] 373 | public string LastName { get; set; } 374 | 375 | [IncludeInReport(Order = 2)] 376 | [JqueryDataTableColumn(Order = 3)] 377 | [SearchableEnum(typeof(Position))] 378 | [Sortable] 379 | public string Position { get; set; } 380 | 381 | [Display(Name = "Office")] 382 | [IncludeInReport(Order = 3)] 383 | [JqueryDataTableColumn(Order = 4)] 384 | [SearchableString(EntityProperty = "Office")] 385 | [Sortable(EntityProperty = "Office")] 386 | public string Offices { get; set; } 387 | 388 | [NestedIncludeInReport] 389 | [NestedSearchable] 390 | [NestedSortable] 391 | public DemoNestedLevelOne DemoNestedLevelOne { get; set; } 392 | } 393 | ``` 394 | 395 | ## Nested Level One Model: 396 | 397 | ```c# 398 | public class DemoNestedLevelOne 399 | { 400 | [IncludeInReport(Order = 4)] 401 | [JqueryDataTableColumn(Order = 5)] 402 | [SearchableShort] 403 | [Sortable] 404 | public short? Experience { get; set; } 405 | 406 | [DisplayName("Extn")] 407 | [IncludeInReport(Order = 5)] 408 | [JqueryDataTableColumn(Order = 6)] 409 | [SearchableInt(EntityProperty = "Extn")] 410 | [Sortable(EntityProperty = "Extn")] 411 | public int? Extension { get; set; } 412 | 413 | [NestedIncludeInReport] 414 | [NestedSearchable(ParentEntityProperty = "DemoNestedLevelTwo")] 415 | [NestedSortable(ParentEntityProperty = "DemoNestedLevelTwo")] 416 | public DemoNestedLevelTwo DemoNestedLevelTwos { get; set; } 417 | } 418 | ``` 419 | 420 | ## Nested Level Two Model: 421 | 422 | ```c# 423 | public class DemoNestedLevelTwo 424 | { 425 | [DisplayName("Start Date")] 426 | [IncludeInReport(Order = 6)] 427 | [JqueryDataTableColumn(Order = 7)] 428 | [SearchableDateTime(EntityProperty = "StartDate")] 429 | [Sortable(EntityProperty = "StartDate")] 430 | public DateTime? StartDates { get; set; } 431 | 432 | [IncludeInReport(Order = 7)] 433 | [JqueryDataTableColumn(Order = 8)] 434 | [SearchableLong] 435 | [Sortable] 436 | public long? Salary { get; set; } 437 | } 438 | ``` 439 | 440 | **Please note:** 441 | * If view model properties have different name than entity model, then you can still do mapping using `(EntityProperty = 'YourEntityPropertyName')`. If they are same then you can ignore this. 442 | * If view model property is a combination of some other properties like `Name` property in the above root model, then you can specify them in `(EntityProperty = 'YourEntityPropertyName,YourSomeOtherEntityPropertyName')`. This will make an implicit `OR` search in database and sort by `YourEntityPropertyName` and then by `YourSomeOtherEntityPropertyName` in database. For Example, take the `Name` property in root model. It has `[SearchableString(EntityProperty = "FirstName,LastName")]`. This will generate a implicit `OR` query like `entity.Where(x => x.FirstName.ToLower().Contains("Name") || x.LastName.ToLower().Contains("Name"))`. Similarly, `[Sortable(EntityProperty = "FirstName,LastName", Default = true)]` will generate query like `entity.OrderBy(x => x.FirstName).ThenBy(x => x.LastName)` for ascending order and `entity.OrderByDescending(x => x.FirstName).ThenByDescending(x => x.LastName)` for descending order. 443 | 444 | # ActionMethod/PageHandler 445 | >On DataTable's AJAX Request, `JqueryDataTablesParameters` will read the DataTable's state and `JqueryDataTablesResult` will accept `IEnumerable` response data to be returned back to table as `JsonResult`. 446 | 447 | ## AJAX POST Configuration 448 | 449 | ### ActionMethod 450 | 451 | ```c# 452 | [HttpPost] 453 | public async Task LoadTable([FromBody]JqueryDataTablesParameters param) 454 | { 455 | try 456 | { 457 | // `param` is stored in session to be used for excel export. This is required only for AJAX POST. 458 | // Below session storage line can be removed if you're not using excel export functionality. 459 | // If you're using Json.Net, then uncomment below line else remove below line 460 | // HttpContext.Session.SetString(nameof(JqueryDataTablesParameters), JsonConvert.SerializeObject(param)); 461 | // If you're using new System.Text.Json then use below line 462 | HttpContext.Session.SetString(nameof(JqueryDataTablesParameters), JsonSerializer.Serialize(param)); 463 | var results = await _demoService.GetDataAsync(param); 464 | 465 | return new JsonResult(new JqueryDataTablesResult { 466 | Draw = param.Draw, 467 | Data = results.Items, 468 | RecordsFiltered = results.TotalSize, 469 | RecordsTotal = results.TotalSize 470 | }); 471 | } catch(Exception e) 472 | { 473 | Console.Write(e.Message); 474 | return new JsonResult(new { error = "Internal Server Error" }); 475 | } 476 | } 477 | ``` 478 | 479 | ### PageHandler 480 | 481 | ```c# 482 | public async Task OnPostLoadTableAsync([FromBody]JqueryDataTablesParameters param) 483 | { 484 | try 485 | { 486 | // `param` is stored in session to be used for excel export. This is required only for AJAX POST. 487 | // Below session storage line can be removed if you're not using excel export functionality. 488 | // If you're using Json.Net, then uncomment below line else remove below line 489 | // HttpContext.Session.SetString(nameof(JqueryDataTablesParameters), JsonConvert.SerializeObject(param)); 490 | // If you're using new System.Text.Json then use below line 491 | HttpContext.Session.SetString(nameof(JqueryDataTablesParameters), JsonSerializer.Serialize(param)); 492 | var results = await _demoService.GetDataAsync(param); 493 | 494 | return new JsonResult(new JqueryDataTablesResult { 495 | Draw = param.Draw, 496 | Data = results.Items, 497 | RecordsFiltered = results.TotalSize, 498 | RecordsTotal = results.TotalSize 499 | }); 500 | } catch(Exception e) 501 | { 502 | Console.Write(e.Message); 503 | return new JsonResult(new { error = "Internal Server Error" }); 504 | } 505 | } 506 | ``` 507 | 508 | ## AJAX GET Configuration 509 | 510 | ### ActionMethod 511 | 512 | ```c# 513 | public async Task LoadTable([ModelBinder(typeof(JqueryDataTablesBinder))] JqueryDataTablesParameters param) 514 | { 515 | try 516 | { 517 | var results = await _demoService.GetDataAsync(param); 518 | 519 | return new JsonResult(new JqueryDataTablesResult { 520 | Draw = param.Draw, 521 | Data = results.Items, 522 | RecordsFiltered = results.TotalSize, 523 | RecordsTotal = results.TotalSize 524 | }); 525 | } catch(Exception e) 526 | { 527 | Console.Write(e.Message); 528 | return new JsonResult(new { error = "Internal Server Error" }); 529 | } 530 | } 531 | ``` 532 | 533 | ### PageHandler 534 | 535 | ```c# 536 | public async Task OnGetLoadTableAsync([ModelBinder(typeof(JqueryDataTablesBinder))] JqueryDataTablesParameters param) 537 | { 538 | try 539 | { 540 | var results = await _demoService.GetDataAsync(param); 541 | 542 | return new JsonResult(new JqueryDataTablesResult { 543 | Draw = param.Draw, 544 | Data = results.Items, 545 | RecordsFiltered = results.TotalSize, 546 | RecordsTotal = results.TotalSize 547 | }); 548 | } catch(Exception e) 549 | { 550 | Console.Write(e.Message); 551 | return new JsonResult(new { error = "Internal Server Error" }); 552 | } 553 | } 554 | ``` 555 | # Multiple Column Searching and Sorting 556 | >Inject Automapper `IConfigurationProvider` to make use of `ProjectTo` before returning the data. Inside the Data Access Method, create `IQueryable` to hold the query. Now, to perform dynamic multiple column **searching** use Static Search Processor `SearchOptionsProcessor` and call the `Apply()` function with query and table columns as parameters. Again for dynamic multiple column **sorting**, use Static Sort Processor `SortOptionsProcessor` and call the `Apply()` function with query and table as parameters. To implement **pagination**, make use of `Start` and `Length` from table parameter and return the result as `JqueryDataTablesPagedResults`. 557 | 558 | ```c# 559 | public class DefaultDemoService:IDemoService 560 | { 561 | private readonly Fingers10DbContext _context; 562 | private readonly IConfigurationProvider _mappingConfiguration; 563 | 564 | public DefaultDemoService(Fingers10DbContext context,IConfigurationProvider mappingConfiguration) 565 | { 566 | _context = context; 567 | _mappingConfiguration = mappingConfiguration; 568 | } 569 | 570 | public async Task> GetDataAsync(JqueryDataTablesParameters table) 571 | { 572 | Demo[] items = null; 573 | IQueryable query = _context.Demos 574 | .AsNoTracking() 575 | .Include(x => x.DemoNestedLevelOne) 576 | .ThenInclude(y => y.DemoNestedLevelTwo); 577 | query = SearchOptionsProcessor.Apply(query,table.Columns); 578 | query = SortOptionsProcessor.Apply(query,table); 579 | 580 | var size = await query.CountAsync(); 581 | 582 | if (table.Length > 0) 583 | { 584 | items = await query 585 | .Skip((table.Start / table.Length) * table.Length) 586 | .Take(table.Length) 587 | .ProjectTo(_mappingConfiguration) 588 | .ToArrayAsync(); 589 | } 590 | else 591 | { 592 | items = await query 593 | .ProjectTo(_mappingConfiguration) 594 | .ToArrayAsync(); 595 | } 596 | 597 | return new JqueryDataTablesPagedResults { 598 | Items = items, 599 | TotalSize = size 600 | }; 601 | } 602 | } 603 | ``` 604 | **Please note:** If you are having DataAccessLogic in a separate project, the create instance of `SearchOptionsProcessor` and `SortOptionsProcessor` inside **ActionMethod/Handler** and pass it as a parameter to Data Access Logic. 605 | 606 | # Report 607 | ## Name 608 | Column names in Report can be configured using the below attributes 609 | * `[Display(Name = "")]` 610 | * `[DisplayName(“”)]` 611 | 612 | ## Report Setup 613 | To customize the Column display in Report, use the following attribute 614 | * `[IncludeInReport]` 615 | 616 | And here are the options, 617 | 618 | |Option |Type |Example |Description| 619 | |-------|--------|-----------------------------------------|-----------| 620 | |Order |`int` |`[IncludeInReport(Order = N)]` |To control the order of columns in Report| 621 | 622 | **Please note:** From **v.4.0.0** all the simple properties in your models **must have `[IncludeInReport]` attribute** for the report export to work. 623 | 624 | 625 | ### Excel Export 626 | To exporting the filtered and sorted data as an excel file, add `GetExcel` action method in your controller as shown below. Return the data as `JqueryDataTablesExcelResult` by passing filtered/ordered data, excel sheet name and excel file name. **JqueryDataTablesExcelResult** Action Result that I have added in the Nuget package. This will take care of converting your data as excel file and return it back to browser. 627 | 628 | >If you want all the results in excel export without pagination, then please write a **separate service method** to retrive data without using `Take()` and `Skip()` 629 | 630 | ## AJAX POST Configuration 631 | 632 | ### Action Method 633 | 634 | ```c# 635 | public async Task GetExcel() 636 | { 637 | // Here we will be getting the param that we have stored in the session in server side action method/page handler 638 | // and deserialize it to get the required data. 639 | var param = HttpContext.Session.GetString(nameof(JqueryDataTablesParameters)); 640 | 641 | // If you're using Json.Net, then uncomment below line else remove below line 642 | // var results = await _demoService.GetDataAsync(JsonConvert.DeserializeObject(param)); 643 | // If you're using new System.Text.Json then use below line 644 | var results = await _demoService.GetDataAsync(JsonSerializer.Deserialize(param)); 645 | return new JqueryDataTablesExcelResult(results.Items,"Demo Sheet Name","Fingers10"); 646 | } 647 | ``` 648 | 649 | ### Page Handler 650 | 651 | ```c# 652 | public async Task OnGetExcelAsync() 653 | { 654 | // Here we will be getting the param that we have stored in the session in server side action method/page handler 655 | // and deserialize it to get the required data. 656 | var param = HttpContext.Session.GetString(nameof(JqueryDataTablesParameters)); 657 | 658 | // If you're using Json.Net, then uncomment below line else remove below line 659 | // var results = await _demoService.GetDataAsync(JsonConvert.DeserializeObject(param)); 660 | // If you're using new System.Text.Json then use below line 661 | var results = await _demoService.GetDataAsync(JsonSerializer.Deserialize(param)); 662 | return new JqueryDataTablesExcelResult(results.Items,"Demo Sheet Name","Fingers10"); 663 | } 664 | ``` 665 | 666 | ## AJAX GET Configuration 667 | 668 | ### Action Method 669 | 670 | ```c# 671 | public async Task GetExcel([ModelBinder(typeof(JqueryDataTablesBinder))] JqueryDataTablesParameters param) 672 | { 673 | var results = await _demoService.GetDataAsync(param); 674 | return new JqueryDataTablesExcelResult(results.Items,"Demo Sheet Name","Fingers10"); 675 | } 676 | ``` 677 | 678 | ### Page Handler 679 | 680 | ```c# 681 | public async Task OnGetExcelAsync([ModelBinder(typeof(JqueryDataTablesBinder))] JqueryDataTablesParameters param) 682 | { 683 | var results = await _demoService.GetDataAsync(param); 684 | return new JqueryDataTablesExcelResult(results.Items,"Demo Sheet Name","Fingers10"); 685 | } 686 | ``` 687 | 688 | ### CSV Export 689 | To exporting the filtered and sorted data as an CSV file, add `GetCSV` action method in your controller as shown below. Return the data as `JqueryDataTablesCSVResult` by passing filtered/ordered data, excel sheet name and excel file name. **JqueryDataTablesCSVResult** Action Result that I have added in the Nuget package. This will take care of converting your data as excel file and return it back to browser. 690 | 691 | >If you want all the results in excel export without pagination, then please write a **separate service method** to retrive data without using `Take()` and `Skip()` 692 | 693 | ## AJAX POST Configuration 694 | 695 | ### Action Method 696 | 697 | ```c# 698 | public async Task GetCSV() 699 | { 700 | // Here we will be getting the param that we have stored in the session in server side action method/page handler 701 | // and deserialize it to get the required data. 702 | var param = HttpContext.Session.GetString(nameof(JqueryDataTablesParameters)); 703 | 704 | // If you're using Json.Net, then uncomment below line else remove below line 705 | // var results = await _demoService.GetDataAsync(JsonConvert.DeserializeObject(param)); 706 | // If you're using new System.Text.Json then use below line 707 | var results = await _demoService.GetDataAsync(JsonSerializer.Deserialize(param)); 708 | return new JqueryDataTablesCSVResult(results.Items,"Fingers10"); 709 | } 710 | ``` 711 | 712 | ### Page Handler 713 | 714 | ```c# 715 | public async Task OnGetCSVAsync() 716 | { 717 | // Here we will be getting the param that we have stored in the session in server side action method/page handler 718 | // and deserialize it to get the required data. 719 | var param = HttpContext.Session.GetString(nameof(JqueryDataTablesParameters)); 720 | 721 | // If you're using Json.Net, then uncomment below line else remove below line 722 | // var results = await _demoService.GetDataAsync(JsonConvert.DeserializeObject(param)); 723 | // If you're using new System.Text.Json then use below line 724 | var results = await _demoService.GetDataAsync(JsonSerializer.Deserialize(param)); 725 | return new JqueryDataTablesCSVResult(results.Items,"Fingers10"); 726 | } 727 | ``` 728 | 729 | ## AJAX GET Configuration 730 | 731 | ### Action Method 732 | 733 | ```c# 734 | public async Task GetCSV([ModelBinder(typeof(JqueryDataTablesBinder))] JqueryDataTablesParameters param) 735 | { 736 | var results = await _demoService.GetDataAsync(param); 737 | return new JqueryDataTablesCSVResult(results.Items,"Fingers10"); 738 | } 739 | ``` 740 | 741 | ### Page Handler 742 | 743 | ```c# 744 | public async Task OnGetCSVAsync([ModelBinder(typeof(JqueryDataTablesBinder))] JqueryDataTablesParameters param) 745 | { 746 | var results = await _demoService.GetDataAsync(param); 747 | return new JqueryDataTablesCSVResult(results.Items,"Fingers10"); 748 | } 749 | ``` 750 | 751 | **Please note:** `GetReport` **ActionMethod/Handler** name must match the name you define in the excel export action click in your Jquery DataTable Initialization script. From `v4.0.0` `[IncludeInReport(Order = N)]` attribute needs to be present in your model to include the field in your excel report. 752 | 753 | # Coming Soon 754 | JqueryDataTablesServerSide is actively under development and I plan to have even more useful features implemented soon, including: 755 | * Dynamic Select 756 | * More Helpers 757 | 758 | Get in touch if there are any features you feel JqueryDataTablesServerSide needs. 759 | 760 | # Target Platform 761 | * .Net Standard 2.0 762 | 763 | # Tools Used 764 | * Visual Studio Community 2019 765 | 766 | # Other Nuget Packages Used 767 | * Fingers10.ExcelExport (3.0.0) - For Generating Excel/CSV Report 768 | * Microsoft.AspNetCore.Razor (2.2.0) - For using TagHelper 769 | * Microsoft.AspNetCore.Mvc.Localization - For HTML Localization in Tag Helper 770 | * Newtonsoft.Json (12.0.3) - For Serialization/Deserialization 771 | * System.Text.Json (4.7.1) - For Serialization/Deserialization 772 | 773 | # Author 774 | * **Abdul Rahman** - Software Developer - from India. Software Consultant, Architect, Freelance Lecturer/Developer and Web Geek. 775 | 776 | # Contributions 777 | Feel free to submit a pull request if you can add additional functionality or find any bugs (to see a list of active issues), visit the Issues section. Please make sure all commits are properly documented. 778 | 779 | Many thanks to the below developers for helping with PR's and suggesting Features: 780 | * [@gaugo123](https://github.com/gaugo123) - gaugo123 781 | * [@cihangll](https://github.com/cihangll) - Cihan Güllü 782 | * [@JudeVajira](https://github.com/JudeVajira) - Jude Vajira Guanasekera 783 | 784 | # License 785 | JqueryDataTablesServerSide is release under the MIT license. You are free to use, modify and distribute this software, as long as the copyright header is left intact. 786 | 787 | Enjoy! 788 | 789 | # Sponsors/Backers 790 | I'm happy to help you with my Nuget Package. Support this project by becoming a sponsor/backer. Your logo will show up here with a link to your website. Sponsor/Back via [![Sponsor via PayPal](https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-100px.png)](https://paypal.me/arsmtb) 791 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman --------------------------------------------------------------------------------