Skip to content

Commit 8d90ac9

Browse files
authored
Merge pull request #574 from json-api-dotnet/feat/queryparams
Query Parameter Services
2 parents 32b3c64 + d1af7cc commit 8d90ac9

File tree

101 files changed

+1707
-1867
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+1707
-1867
lines changed

.editorconfig

+11
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,14 @@ charset = utf-8
1212

1313
[*.{csproj,props}]
1414
indent_size = 2
15+
16+
[*.{cs,vb}]
17+
dotnet_naming_rule.private_members_with_underscore.symbols = private_fields
18+
dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore
19+
dotnet_naming_rule.private_members_with_underscore.severity = suggestion
20+
21+
dotnet_naming_symbols.private_fields.applicable_kinds = field
22+
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
23+
24+
dotnet_naming_style.prefix_underscore.capitalization = camel_case
25+
dotnet_naming_style.prefix_underscore.required_prefix = _

benchmarks/Query/QueryParser_Benchmarks.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ private void Run(int iterations, Action action) {
5757
}
5858

5959
// this facade allows us to expose and micro-benchmark protected methods
60-
private class BenchmarkFacade : QueryParser {
60+
private class BenchmarkFacade : QueryParameterDiscovery {
6161
public BenchmarkFacade(
6262
IRequestContext currentRequest,
6363
JsonApiOptions options) : base(currentRequest, options) { }

benchmarks/RequestMiddleware/ContainsMediaTypeParameters_Benchmarks.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class ContainsMediaTypeParameters_Benchmarks
1414

1515
[Benchmark]
1616
public void Current()
17-
=> JsonApiDotNetCore.Middleware.RequestMiddleware.ContainsMediaTypeParameters(MEDIA_TYPE);
17+
=> JsonApiDotNetCore.Middleware.CurrentRequestMiddleware.ContainsMediaTypeParameters(MEDIA_TYPE);
1818

1919
private bool UsingSplitImpl(string mediaType)
2020
{

src/Examples/JsonApiDotNetCoreExample/Properties/launchSettings.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"launchUrl": "http://localhost:5000/api/values",
2323
"environmentVariables": {
2424
"ASPNETCORE_ENVIRONMENT": "Development"
25-
}
25+
},
26+
"applicationUrl": "http://localhost:5000/"
2627
}
2728
}
2829
}

src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs

+7-7
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ public override QueryFilters GetQueryFilters()
2424

2525
private IQueryable<User> FirstCharacterFilter(IQueryable<User> users, FilterQuery filterQuery)
2626
{
27-
switch(filterQuery.Operation)
28-
{
29-
case "lt":
30-
return users.Where(u => u.Username[0] < filterQuery.Value[0]);
31-
default:
32-
return users.Where(u => u.Username[0] == filterQuery.Value[0]);
33-
}
27+
switch (filterQuery.Operation)
28+
{
29+
case "lt":
30+
return users.Where(u => u.Username[0] < filterQuery.Value[0]);
31+
default:
32+
return users.Where(u => u.Username[0] == filterQuery.Value[0]);
33+
}
3434
}
3535
}
3636
}

src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs

+13-6
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,19 @@ namespace JsonApiDotNetCoreExample.Services
1616
{
1717
public class CustomArticleService : EntityResourceService<Article>
1818
{
19-
public CustomArticleService(IEntityRepository<Article, int> repository, IJsonApiOptions options,
20-
ITargetedFields updatedFields, ICurrentRequest currentRequest,
21-
IIncludeService includeService, ISparseFieldsService sparseFieldsService,
22-
IPageQueryService pageManager, IResourceGraph resourceGraph,
23-
IResourceHookExecutor hookExecutor = null, ILoggerFactory loggerFactory = null)
24-
: base(repository, options, updatedFields, currentRequest, includeService, sparseFieldsService, pageManager, resourceGraph, hookExecutor, loggerFactory)
19+
public CustomArticleService(ISortService sortService,
20+
IFilterService filterService,
21+
IEntityRepository<Article, int> repository,
22+
IJsonApiOptions options,
23+
ICurrentRequest currentRequest,
24+
IIncludeService includeService,
25+
ISparseFieldsService sparseFieldsService,
26+
IPageService pageManager,
27+
IResourceGraph resourceGraph,
28+
IResourceHookExecutor hookExecutor = null,
29+
ILoggerFactory loggerFactory = null)
30+
: base(sortService, filterService, repository, options, currentRequest, includeService, sparseFieldsService,
31+
pageManager, resourceGraph, hookExecutor, loggerFactory)
2532
{
2633
}
2734

src/Examples/NoEntityFrameworkExample/Properties/launchSettings.json

+8-4
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,20 @@
88
}
99
},
1010
"profiles": {
11+
"NoEntityFrameworkExample": {
12+
"commandName": "Project",
13+
"launchBrowser": true,
14+
"environmentVariables": {
15+
"ASPNETCORE_ENVIRONMENT": "Development"
16+
},
17+
"applicationUrl": "http://localhost:5000/"
18+
},
1119
"IIS Express": {
1220
"commandName": "IISExpress",
1321
"launchBrowser": true,
1422
"environmentVariables": {
1523
"ASPNETCORE_ENVIRONMENT": "Development"
1624
}
17-
},
18-
"NoEntityFrameworkExample": {
19-
"commandName": "Project",
20-
"environmentVariables": {}
2125
}
2226
}
2327
}

src/Examples/ReportsExample/Properties/launchSettings.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,20 @@
88
}
99
},
1010
"profiles": {
11-
"IIS Express": {
12-
"commandName": "IISExpress",
13-
"launchBrowser": true,
14-
"environmentVariables": {
15-
"ASPNETCORE_ENVIRONMENT": "Development"
16-
}
17-
},
1811
"ReportsExample": {
1912
"commandName": "Project",
2013
"launchBrowser": true,
2114
"environmentVariables": {
2215
"ASPNETCORE_ENVIRONMENT": "Development"
2316
},
2417
"applicationUrl": "http://localhost:55654/"
18+
},
19+
"IIS Express": {
20+
"commandName": "IISExpress",
21+
"launchBrowser": true,
22+
"environmentVariables": {
23+
"ASPNETCORE_ENVIRONMENT": "Development"
24+
}
2525
}
2626
}
2727
}

src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs

-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Collections.Generic;
21
using System.Threading.Tasks;
32
using JsonApiDotNetCore.Configuration;
43
using JsonApiDotNetCore.Extensions;
@@ -51,13 +50,8 @@ public BaseJsonApiController(
5150
_update = resourceService;
5251
_updateRelationships = resourceService;
5352
_delete = resourceService;
54-
ParseQueryParams();
5553
}
5654

57-
private void ParseQueryParams()
58-
{
59-
60-
}
6155

6256
public BaseJsonApiController(
6357
IJsonApiOptions jsonApiOptions,

src/JsonApiDotNetCore/Controllers/DisableQueryAttribute.cs

+18-3
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,27 @@
33
namespace JsonApiDotNetCore.Controllers
44
{
55
public class DisableQueryAttribute : Attribute
6-
{
6+
{
7+
/// <summary>
8+
/// Disabled one of the native query parameters for a controller.
9+
/// </summary>
10+
/// <param name="queryParams"></param>
711
public DisableQueryAttribute(QueryParams queryParams)
812
{
9-
QueryParams = queryParams;
13+
QueryParams = queryParams.ToString("G").ToLower();
1014
}
1115

12-
public QueryParams QueryParams { get; set; }
16+
/// <summary>
17+
/// It is allowed to use strings to indicate which query parameters
18+
/// should be disabled, because the user may have defined a custom
19+
/// query parameter that is not included in the <see cref="QueryParams"/> enum.
20+
/// </summary>
21+
/// <param name="customQueryParams"></param>
22+
public DisableQueryAttribute(string customQueryParams)
23+
{
24+
QueryParams = customQueryParams.ToLower();
25+
}
26+
27+
public string QueryParams { get; }
1328
}
1429
}

src/JsonApiDotNetCore/Controllers/JsonApiControllerMixin.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using JsonApiDotNetCore.Internal;
2+
using JsonApiDotNetCore.Middleware;
23
using Microsoft.AspNetCore.Mvc;
34

45
namespace JsonApiDotNetCore.Controllers
56
{
7+
[ServiceFilter(typeof(IQueryParameterActionFilter))]
68
public abstract class JsonApiControllerMixin : ControllerBase
79
{
810
protected IActionResult Forbidden()

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

+15-36
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ public DefaultEntityRepository(
6565

6666
/// <inheritdoc />
6767
public virtual IQueryable<TEntity> Get() => _dbSet;
68-
68+
6969
/// <inheritdoc />
70-
public virtual IQueryable<TEntity> Select(IQueryable<TEntity> entities, List<string> fields)
70+
public virtual IQueryable<TEntity> Select(IQueryable<TEntity> entities, List<AttrAttribute> fields)
7171
{
7272
if (fields?.Count > 0)
7373
return entities.Select(fields);
@@ -76,51 +76,36 @@ public virtual IQueryable<TEntity> Select(IQueryable<TEntity> entities, List<str
7676
}
7777

7878
/// <inheritdoc />
79-
public virtual IQueryable<TEntity> Filter(IQueryable<TEntity> entities, FilterQuery filterQuery)
79+
public virtual IQueryable<TEntity> Filter(IQueryable<TEntity> entities, FilterQueryContext filterQueryContext)
8080
{
81-
if (_resourceDefinition != null)
82-
{
81+
if (filterQueryContext.IsCustom)
82+
{ // todo: consider to move this business logic to service layer
83+
var filterQuery = filterQueryContext.Query;
8384
var defaultQueryFilters = _resourceDefinition.GetQueryFilters();
84-
if (defaultQueryFilters != null && defaultQueryFilters.TryGetValue(filterQuery.Attribute, out var defaultQueryFilter) == true)
85-
{
85+
if (defaultQueryFilters != null && defaultQueryFilters.TryGetValue(filterQuery.Target, out var defaultQueryFilter) == true)
8686
return defaultQueryFilter(entities, filterQuery);
87-
}
87+
8888
}
89-
return entities.Filter(new AttrFilterQuery(_currentRequest.GetRequestResource(), _resourceGraph, filterQuery));
89+
return entities.Filter(filterQueryContext);
9090
}
9191

9292
/// <inheritdoc />
93-
public virtual IQueryable<TEntity> Sort(IQueryable<TEntity> entities, List<SortQuery> sortQueries)
93+
public virtual IQueryable<TEntity> Sort(IQueryable<TEntity> entities, SortQueryContext sortQueryContext)
9494
{
95-
if (sortQueries != null && sortQueries.Count > 0)
96-
return entities.Sort(_currentRequest.GetRequestResource(), _resourceGraph, sortQueries);
97-
98-
if (_resourceDefinition != null)
99-
{
100-
var defaultSortOrder = _resourceDefinition.DefaultSort();
101-
if (defaultSortOrder != null && defaultSortOrder.Count > 0)
102-
{
103-
foreach (var sortProp in defaultSortOrder)
104-
{
105-
// this is dumb...add an overload, don't allocate for no reason
106-
entities.Sort(_currentRequest.GetRequestResource(), _resourceGraph, new SortQuery(sortProp.Item2, sortProp.Item1.PublicAttributeName));
107-
}
108-
}
109-
}
110-
return entities;
95+
return entities.Sort(sortQueryContext);
11196
}
11297

11398
/// <inheritdoc />
114-
public virtual async Task<TEntity> GetAsync(TId id)
99+
public virtual async Task<TEntity> GetAsync(TId id, List<AttrAttribute> fields = null)
115100
{
116-
return await Select(Get(), _currentRequest.QuerySet?.Fields).SingleOrDefaultAsync(e => e.Id.Equals(id));
101+
return await Select(Get(), fields).SingleOrDefaultAsync(e => e.Id.Equals(id));
117102
}
118103

119104
/// <inheritdoc />
120-
public virtual async Task<TEntity> GetAndIncludeAsync(TId id, RelationshipAttribute relationship)
105+
public virtual async Task<TEntity> GetAndIncludeAsync(TId id, RelationshipAttribute relationship, List<AttrAttribute> fields = null)
121106
{
122107
_logger?.LogDebug($"[JADN] GetAndIncludeAsync({id}, {relationship.PublicRelationshipName})");
123-
var includedSet = Include(Select(Get(), _currentRequest.QuerySet?.Fields), relationship);
108+
var includedSet = Include(Select(Get(), fields), relationship);
124109
var result = await includedSet.SingleOrDefaultAsync(e => e.Id.Equals(id));
125110
return result;
126111
}
@@ -224,12 +209,6 @@ public void DetachRelationshipPointers(TEntity entity)
224209
}
225210
}
226211

227-
[Obsolete("Use overload UpdateAsync(TEntity updatedEntity): providing parameter ID does no longer add anything relevant")]
228-
public virtual async Task<TEntity> UpdateAsync(TId id, TEntity updatedEntity)
229-
{
230-
return await UpdateAsync(updatedEntity);
231-
}
232-
233212
/// <inheritdoc />
234213
public virtual async Task<TEntity> UpdateAsync(TEntity updatedEntity)
235214
{

src/JsonApiDotNetCore/Data/IEntityReadRepository.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public interface IEntityReadRepository<TEntity, in TId>
2424
/// <summary>
2525
/// Apply fields to the provided queryable
2626
/// </summary>
27-
IQueryable<TEntity> Select(IQueryable<TEntity> entities, List<string> fields);
27+
IQueryable<TEntity> Select(IQueryable<TEntity> entities, List<AttrAttribute> fields);
2828

2929
/// <summary>
3030
/// Include a relationship in the query
@@ -41,12 +41,12 @@ public interface IEntityReadRepository<TEntity, in TId>
4141
/// <summary>
4242
/// Apply a filter to the provided queryable
4343
/// </summary>
44-
IQueryable<TEntity> Filter(IQueryable<TEntity> entities, FilterQuery filterQuery);
44+
IQueryable<TEntity> Filter(IQueryable<TEntity> entities, FilterQueryContext filterQuery);
4545

4646
/// <summary>
4747
/// Apply a sort to the provided queryable
4848
/// </summary>
49-
IQueryable<TEntity> Sort(IQueryable<TEntity> entities, List<SortQuery> sortQueries);
49+
IQueryable<TEntity> Sort(IQueryable<TEntity> entities, SortQueryContext sortQueries);
5050

5151
/// <summary>
5252
/// Paginate the provided queryable
@@ -56,7 +56,7 @@ public interface IEntityReadRepository<TEntity, in TId>
5656
/// <summary>
5757
/// Get the entity by id
5858
/// </summary>
59-
Task<TEntity> GetAsync(TId id);
59+
Task<TEntity> GetAsync(TId id, List<AttrAttribute> fields = null);
6060

6161
/// <summary>
6262
/// Get the entity with the specified id and include the relationship.
@@ -68,7 +68,7 @@ public interface IEntityReadRepository<TEntity, in TId>
6868
/// _todoItemsRepository.GetAndIncludeAsync(1, "achieved-date");
6969
/// </code>
7070
/// </example>
71-
Task<TEntity> GetAndIncludeAsync(TId id, RelationshipAttribute relationship);
71+
Task<TEntity> GetAndIncludeAsync(TId id, RelationshipAttribute relationship, List<AttrAttribute> fields = null);
7272

7373
/// <summary>
7474
/// Count the total number of records

src/JsonApiDotNetCore/Data/IEntityWriteRepository.cs

-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ public interface IEntityWriteRepository<TEntity, in TId>
1717

1818
Task<TEntity> UpdateAsync(TEntity entity);
1919

20-
[Obsolete("Use overload UpdateAsync(TEntity updatedEntity): providing parameter ID does no longer add anything relevant")]
21-
Task<TEntity> UpdateAsync(TId id, TEntity entity);
22-
2320
Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable<string> relationshipIds);
2421

2522
Task<bool> DeleteAsync(TId id);

src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public static IApplicationBuilder UseJsonApi(this IApplicationBuilder app, bool
2121

2222
app.UseEndpointRouting();
2323

24-
app.UseMiddleware<RequestMiddleware>();
24+
app.UseMiddleware<CurrentRequestMiddleware>();
2525

2626
if (useMvc)
2727
{

0 commit comments

Comments
 (0)