Skip to content

Commit dc1fe77

Browse files
committed
feat(paging): add paging via X.PagedList
1 parent 25991d2 commit dc1fe77

File tree

11 files changed

+79
-24
lines changed

11 files changed

+79
-24
lines changed

src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ namespace JsonApiDotNetCore.Configuration
33
public class JsonApiOptions
44
{
55
public string Namespace { get; set; }
6+
public int DefaultPageSize { get; set; }
67
}
78
}

src/JsonApiDotNetCore/Controllers/JsonApiController.cs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,19 @@ public JsonApiController(
5252
}
5353

5454
[HttpGet]
55-
public virtual IActionResult Get()
55+
public virtual async Task<IActionResult> GetAsync()
5656
{
5757
var entities = _entities.Get();
5858

5959
entities = ApplySortAndFilterQuery(entities);
6060

61-
if(_jsonApiContext.QuerySet != null)
61+
if (_jsonApiContext.QuerySet != null)
6262
entities = IncludeRelationships(entities, _jsonApiContext.QuerySet.IncludedRelationships);
6363

64-
return Ok(entities);
64+
// pagination should be done last since it will execute the query
65+
var pagedEntities = await ApplyPageQueryAsync(entities);
66+
67+
return Ok(pagedEntities);
6568
}
6669

6770
[HttpGet("{id}")]
@@ -142,12 +145,6 @@ public virtual async Task<IActionResult> PatchAsync(TId id, [FromBody] T entity)
142145
return Ok(updatedEntity);
143146
}
144147

145-
// [HttpPatch("{id}/{relationship}")]
146-
// public virtual IActionResult PatchRelationship(int id, string relation)
147-
// {
148-
// return Ok("Patch Id/relationship");
149-
// }
150-
151148
[HttpDelete("{id}")]
152149
public virtual async Task<IActionResult> DeleteAsync(TId id)
153150
{
@@ -159,12 +156,6 @@ public virtual async Task<IActionResult> DeleteAsync(TId id)
159156
return Ok();
160157
}
161158

162-
// [HttpDelete("{id}/{relationship}")]
163-
// public virtual IActionResult Delete(int id, string relation)
164-
// {
165-
// return Ok("Delete Id/relationship");
166-
// }
167-
168159
private IQueryable<T> ApplySortAndFilterQuery(IQueryable<T> entities)
169160
{
170161
var query = _jsonApiContext.QuerySet;
@@ -179,6 +170,18 @@ private IQueryable<T> ApplySortAndFilterQuery(IQueryable<T> entities)
179170
return entities;
180171
}
181172

173+
private async Task<IEnumerable<T>> ApplyPageQueryAsync(IQueryable<T> entities)
174+
{
175+
if(_jsonApiContext.Options.DefaultPageSize == 0 && (_jsonApiContext.QuerySet == null || _jsonApiContext.QuerySet.PageQuery.PageSize == 0))
176+
return entities;
177+
178+
var query = _jsonApiContext.QuerySet.PageQuery;
179+
var pageNumber = query.PageOffset > 0 ? query.PageOffset : 1;
180+
var pageSize = query.PageSize > 0 ? query.PageSize : _jsonApiContext.Options.DefaultPageSize;
181+
182+
return await _entities.PageAsync(entities, pageSize, pageNumber);
183+
}
184+
182185
private IQueryable<T> IncludeRelationships(IQueryable<T> entities, List<string> relationships)
183186
{
184187
_jsonApiContext.IncludedRelationships = relationships;

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.Collections.Generic;
32
using System.Linq;
43
using System.Threading.Tasks;
@@ -9,6 +8,7 @@
98
using JsonApiDotNetCore.Services;
109
using Microsoft.EntityFrameworkCore;
1110
using Microsoft.Extensions.Logging;
11+
using X.PagedList;
1212

1313
namespace JsonApiDotNetCore.Data
1414
{
@@ -132,5 +132,10 @@ public IQueryable<TEntity> Include(IQueryable<TEntity> entities, string relation
132132
throw new JsonApiException("400", "Invalid relationship",
133133
$"{entity.EntityName} does not have a relationship named {relationshipName}");
134134
}
135+
136+
public async Task<IEnumerable<TEntity>> PageAsync(IQueryable<TEntity> entities, int pageSize, int pageNumber)
137+
{
138+
return await entities.ToPagedListAsync(pageNumber, pageSize);
139+
}
135140
}
136141
}

src/JsonApiDotNetCore/Data/IEntityRepository.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public interface IEntityRepository<TEntity, in TId>
2323

2424
IQueryable<TEntity> Sort(IQueryable<TEntity> entities, List<SortQuery> sortQueries);
2525

26+
Task<IEnumerable<TEntity>> PageAsync(IQueryable<TEntity> entities, int pageSize, int pageNumber);
27+
2628
Task<TEntity> GetAsync(TId id);
2729

2830
Task<TEntity> GetAndIncludeAsync(TId id, string relationshipName);

src/JsonApiDotNetCore/Extensions/ServiceProviderExtensions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,25 @@ public static void AddJsonApi<TContext>(this IServiceCollection services, Action
3030
private static void _addInternals<TContext>(IServiceCollection services, JsonApiOptions jsonApiOptions)
3131
where TContext : DbContext
3232
{
33-
services.AddJsonApiInternals<TContext>();
33+
services.AddJsonApiInternals<TContext>(jsonApiOptions);
3434
services.AddMvc()
3535
.AddMvcOptions(opt => {
3636
opt.Filters.Add(typeof(JsonApiExceptionFilter));
3737
opt.SerializeAsJsonApi(jsonApiOptions);
3838
});
3939
}
4040

41-
public static void AddJsonApiInternals<TContext>(this IServiceCollection services)
41+
public static void AddJsonApiInternals<TContext>(this IServiceCollection services, JsonApiOptions jsonApiOptions)
4242
where TContext : DbContext
4343
{
4444
var contextGraphBuilder = new ContextGraphBuilder<TContext>();
4545
var contextGraph = contextGraphBuilder.Build();
4646

4747
services.AddScoped(typeof(DbContext), typeof(TContext));
48-
4948
services.AddScoped(typeof(IEntityRepository<>), typeof(DefaultEntityRepository<>));
5049
services.AddScoped(typeof(IEntityRepository<,>), typeof(DefaultEntityRepository<,>));
5150

51+
services.AddSingleton<JsonApiOptions>(jsonApiOptions);
5252
services.AddSingleton<IContextGraph>(contextGraph);
5353
services.AddScoped<IJsonApiContext,JsonApiContext>();
5454
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace JsonApiDotNetCore.Internal.Query
2+
{
3+
public class PageQuery
4+
{
5+
public int PageSize { get; set; }
6+
public int PageOffset { get; set; }
7+
}
8+
}

src/JsonApiDotNetCore/Internal/Query/QuerySet.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Linq;
34
using JsonApiDotNetCore.Extensions;
@@ -14,9 +15,11 @@ public QuerySet(IJsonApiContext jsonApiContext, IQueryCollection query)
1415
{
1516
_jsonApiContext = jsonApiContext;
1617
BuildQuerySet(query);
18+
PageQuery = new PageQuery();
1719
}
1820

1921
public FilterQuery Filter { get; set; }
22+
public PageQuery PageQuery { get; set; }
2023
public List<SortQuery> SortParameters { get; set; }
2124
public List<string> IncludedRelationships { get; set; }
2225

@@ -40,6 +43,11 @@ private void BuildQuerySet(IQueryCollection query)
4043
{
4144
IncludedRelationships = ParseIncludedRelationships(pair.Value);
4245
}
46+
47+
if(pair.Key.StartsWith("page"))
48+
{
49+
PageQuery = ParsePageQuery(pair.Key, pair.Value);
50+
}
4351
}
4452
}
4553

@@ -55,6 +63,21 @@ private FilterQuery ParseFilterQuery(string key, string value)
5563
return new FilterQuery(attribute, value);
5664
}
5765

66+
private PageQuery ParsePageQuery(string key, string value)
67+
{
68+
// expected input = page[size]=10
69+
// page[number]=1
70+
71+
var propertyName = key.Split('[', ']')[1];
72+
73+
if(propertyName == "size")
74+
PageQuery.PageSize = Convert.ToInt32(value);
75+
else if (propertyName == "number")
76+
PageQuery.PageOffset = Convert.ToInt32(value);
77+
78+
return PageQuery;
79+
}
80+
5881
// sort=id,name
5982
// sort=-id
6083
private List<SortQuery> ParseSortParameters(string value)

src/JsonApiDotNetCore/Services/IJsonApiContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
using System.Collections.Generic;
2+
using JsonApiDotNetCore.Configuration;
23
using JsonApiDotNetCore.Internal;
34
using JsonApiDotNetCore.Internal.Query;
45

56
namespace JsonApiDotNetCore.Services
67
{
78
public interface IJsonApiContext
89
{
10+
JsonApiOptions Options { get; set; }
911
IJsonApiContext ApplyContext<T>();
1012
IContextGraph ContextGraph { get; set; }
1113
ContextEntity RequestEntity { get; set; }

src/JsonApiDotNetCore/Services/JsonApiContext.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using System.Linq;
33
using JsonApiDotNetCore.Builders;
4+
using JsonApiDotNetCore.Configuration;
45
using JsonApiDotNetCore.Internal;
56
using JsonApiDotNetCore.Internal.Query;
67
using Microsoft.AspNetCore.Http;
@@ -12,12 +13,14 @@ public class JsonApiContext : IJsonApiContext
1213
private IHttpContextAccessor _httpContextAccessor;
1314
public JsonApiContext(
1415
IContextGraph contextGraph,
15-
IHttpContextAccessor httpContextAccessor)
16+
IHttpContextAccessor httpContextAccessor,
17+
JsonApiOptions options)
1618
{
1719
ContextGraph = contextGraph;
1820
_httpContextAccessor = httpContextAccessor;
21+
Options = options;
1922
}
20-
23+
public JsonApiOptions Options { get; set; }
2124
public IContextGraph ContextGraph { get; set; }
2225
public ContextEntity RequestEntity { get; set; }
2326
public string BasePath { get; set; }

src/JsonApiDotNetCore/project.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"Microsoft.AspNetCore.Routing": "1.1.0",
1010
"Microsoft.AspNetCore.Mvc": "1.1.0",
1111
"Microsoft.EntityFrameworkCore": "1.1.0",
12-
"Microsoft.Extensions.Logging": "1.1.0"
12+
"Microsoft.Extensions.Logging": "1.1.0",
13+
"X.PagedList": "5.3.0.5300"
1314
},
1415

1516
"frameworks": {

src/JsonApiDotNetCoreExample/Startup.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
using JsonApiDotNetCoreExample.Data;
88
using Microsoft.EntityFrameworkCore;
99
using JsonApiDotNetCore.Extensions;
10+
using Microsoft.AspNetCore.Http;
11+
using System.Threading.Tasks;
12+
using System.Reflection;
13+
using System;
1014

1115
namespace JsonApiDotNetCoreExample
1216
{
@@ -31,7 +35,10 @@ public void ConfigureServices(IServiceCollection services)
3135
options.UseNpgsql(_getDbConnectionString());
3236
}, ServiceLifetime.Transient);
3337

34-
services.AddJsonApi<AppDbContext>(opt => opt.Namespace = "api/v1");
38+
services.AddJsonApi<AppDbContext>(opt => {
39+
opt.Namespace = "api/v1";
40+
opt.DefaultPageSize = 1;
41+
});
3542
}
3643

3744
public void Configure(
@@ -52,4 +59,4 @@ private string _getDbConnectionString()
5259
return _config["Data:DefaultConnection"];
5360
}
5461
}
55-
}
62+
}

0 commit comments

Comments
 (0)