Skip to content

Commit ea3a076

Browse files
authored
Merge pull request #83 from Research-Institute/develop
v1.3.0
2 parents 8469d2d + 53cc9db commit ea3a076

20 files changed

+516
-52
lines changed

Diff for: README.md

+28-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ JsonApiDotnetCore provides a framework for building [json:api](http://jsonapi.or
2424
- [Defining Custom Data Access Methods](#defining-custom-data-access-methods)
2525
- [Pagination](#pagination)
2626
- [Filtering](#filtering)
27+
- [Custom Filters](#custom-filters)
2728
- [Sorting](#sorting)
2829
- [Meta](#meta)
2930
- [Client Generated Ids](#client-generated-ids)
@@ -46,14 +47,14 @@ Install-Package JsonApiDotnetCore
4647

4748
- project.json
4849
```json
49-
"JsonApiDotNetCore": "1.2.2"
50+
"JsonApiDotNetCore": "1.3.0"
5051
```
5152

5253
- *.csproj
5354
```xml
5455
<ItemGroup>
5556
<!-- ... -->
56-
<PackageReference Include="JsonApiDotNetCore" Version="1.2.2" />
57+
<PackageReference Include="JsonApiDotNetCore" Version="1.3.0" />
5758
</ItemGroup>
5859
```
5960

@@ -317,6 +318,31 @@ identifier):
317318
?filter[attribute]=like:value
318319
```
319320

321+
#### Custom Filters
322+
323+
You can customize the filter implementation by overriding the method in the `DefaultEntityRepository` like so:
324+
325+
```csharp
326+
public class MyEntityRepository : DefaultEntityRepository<MyEntity>
327+
{
328+
public MyEntityRepository(
329+
AppDbContext context,
330+
ILoggerFactory loggerFactory,
331+
IJsonApiContext jsonApiContext)
332+
: base(context, loggerFactory, jsonApiContext)
333+
{ }
334+
335+
public override IQueryable<TEntity> Filter(IQueryable<TEntity> entities, FilterQuery filterQuery)
336+
{
337+
// use the base filtering method
338+
entities = base.Filter(entities, filterQuery);
339+
340+
// implement custom method
341+
return ApplyMyCustomFilter(entities, filterQuery);
342+
}
343+
}
344+
```
345+
320346
### Sorting
321347

322348
Resources can be sorted by an attribute:

Diff for: src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ private List<DocumentData> GetIncludedEntities(ContextEntity contextEntity, IIde
188188
private void AddIncludedEntity(List<DocumentData> entities, IIdentifiable entity)
189189
{
190190
var includedEntity = GetIncludedEntity(entity);
191-
if(includedEntity != null)
191+
192+
if(includedEntity != null && !entities.Any(doc => doc.Id == includedEntity.Id && doc.Type == includedEntity.Type))
192193
entities.Add(includedEntity);
193194
}
194195

Diff for: src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ public virtual IQueryable<TEntity> Filter(IQueryable<TEntity> entities, FilterQ
5656
if(filterQuery == null)
5757
return entities;
5858

59+
var attributeFilterQuery = new AttrFilterQuery(_jsonApiContext, filterQuery);
60+
5961
return entities
60-
.Filter(filterQuery);
62+
.Filter(attributeFilterQuery);
6163
}
6264

6365
public virtual IQueryable<TEntity> Sort(IQueryable<TEntity> entities, List<SortQuery> sortQueries)

Diff for: src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ private static IOrderedQueryable<TSource> CallGenericOrderMethod<TSource>(IQuery
6363
return (IOrderedQueryable<TSource>)result;
6464
}
6565

66-
public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> source, FilterQuery filterQuery)
66+
public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> source, AttrFilterQuery filterQuery)
6767
{
6868
if (filterQuery == null)
6969
return source;
@@ -78,7 +78,7 @@ public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> sourc
7878
{
7979
// convert the incoming value to the target value type
8080
// "1" -> 1
81-
var convertedValue = Convert.ChangeType(filterQuery.PropertyValue, property.PropertyType);
81+
var convertedValue = TypeHelper.ConvertType(filterQuery.PropertyValue, property.PropertyType);
8282
// {model}
8383
var parameter = Expression.Parameter(concreteType, "model");
8484
// {model.Id}
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System;
2+
using System.Linq;
3+
using JsonApiDotNetCore.Models;
4+
using JsonApiDotNetCore.Services;
5+
6+
namespace JsonApiDotNetCore.Internal.Query
7+
{
8+
public class AttrFilterQuery
9+
{
10+
private readonly IJsonApiContext _jsonApiContext;
11+
12+
public AttrFilterQuery(
13+
IJsonApiContext jsonApiCopntext,
14+
FilterQuery filterQuery)
15+
{
16+
_jsonApiContext = jsonApiCopntext;
17+
18+
var attribute = GetAttribute(filterQuery.Key);
19+
20+
if (attribute == null)
21+
throw new JsonApiException("400", $"{filterQuery.Key} is not a valid property.");
22+
23+
FilteredAttribute = attribute;
24+
PropertyValue = filterQuery.Value;
25+
FilterOperation = GetFilterOperation(filterQuery.Operation);
26+
}
27+
28+
public AttrAttribute FilteredAttribute { get; set; }
29+
public string PropertyValue { get; set; }
30+
public FilterOperations FilterOperation { get; set; }
31+
32+
private FilterOperations GetFilterOperation(string prefix)
33+
{
34+
if (prefix.Length == 0) return FilterOperations.eq;
35+
36+
FilterOperations opertion;
37+
if (!Enum.TryParse<FilterOperations>(prefix, out opertion))
38+
throw new JsonApiException("400", $"Invalid filter prefix '{prefix}'");
39+
40+
return opertion;
41+
}
42+
43+
private AttrAttribute GetAttribute(string propertyName)
44+
{
45+
return _jsonApiContext.RequestEntity.Attributes
46+
.FirstOrDefault(attr =>
47+
attr.InternalAttributeName.ToLower() == propertyName.ToLower()
48+
);
49+
}
50+
}
51+
}

Diff for: src/JsonApiDotNetCore/Internal/Query/FilterQuery.cs

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
using JsonApiDotNetCore.Models;
2-
31
namespace JsonApiDotNetCore.Internal.Query
42
{
53
public class FilterQuery
64
{
7-
public FilterQuery(AttrAttribute filteredAttribute, string propertyValue, FilterOperations filterOperation)
5+
public FilterQuery(string key, string value, string operation)
86
{
9-
FilteredAttribute = filteredAttribute;
10-
PropertyValue = propertyValue;
11-
FilterOperation = filterOperation;
7+
Key = key;
8+
Value = value;
9+
Operation = operation;
1210
}
1311

14-
public AttrAttribute FilteredAttribute { get; set; }
15-
public string PropertyValue { get; set; }
16-
public FilterOperations FilterOperation { get; set; }
12+
public string Key { get; set; }
13+
public string Value { get; set; }
14+
public string Operation { get; set; }
1715
}
1816
}

Diff for: src/JsonApiDotNetCore/Internal/Query/QuerySet.cs

+8-25
Original file line numberDiff line numberDiff line change
@@ -74,49 +74,32 @@ private List<FilterQuery> ParseFilterQuery(string key, string value)
7474
var queries = new List<FilterQuery>();
7575

7676
var propertyName = key.Split('[', ']')[1].ToProperCase();
77-
var attribute = GetAttribute(propertyName);
78-
79-
if (attribute == null)
80-
throw new JsonApiException("400", $"{propertyName} is not a valid property.");
8177

8278
var values = value.Split(',');
8379
foreach(var val in values)
84-
queries.Add(ParseFilterOperation(attribute, val));
80+
{
81+
(var operation, var filterValue) = ParseFilterOperation(val);
82+
queries.Add(new FilterQuery(propertyName, filterValue, operation));
83+
}
8584

8685
return queries;
8786
}
8887

89-
private FilterQuery ParseFilterOperation(AttrAttribute attribute, string value)
88+
private (string operation, string value) ParseFilterOperation(string value)
9089
{
9190
if(value.Length < 3)
92-
return new FilterQuery(attribute, value, FilterOperations.eq);
91+
return (string.Empty, value);
9392

9493
var operation = value.Split(':');
9594

9695
if(operation.Length == 1)
97-
return new FilterQuery(attribute, value, FilterOperations.eq);
96+
return (string.Empty, value);
9897

9998
// remove prefix from value
10099
var prefix = operation[0];
101100
value = operation[1];
102101

103-
switch(prefix)
104-
{
105-
case "eq":
106-
return new FilterQuery(attribute, value, FilterOperations.eq);
107-
case "lt":
108-
return new FilterQuery(attribute, value, FilterOperations.lt);
109-
case "gt":
110-
return new FilterQuery(attribute, value, FilterOperations.gt);
111-
case "le":
112-
return new FilterQuery(attribute, value, FilterOperations.le);
113-
case "ge":
114-
return new FilterQuery(attribute, value, FilterOperations.ge);
115-
case "like":
116-
return new FilterQuery(attribute, value, FilterOperations.like);
117-
}
118-
119-
throw new JsonApiException("400", $"Invalid filter prefix '{prefix}'");
102+
return (prefix, value);;
120103
}
121104

122105
private PageQuery ParsePageQuery(string key, string value)

Diff for: src/JsonApiDotNetCore/JsonApiDotNetCore.csproj

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2-
32
<PropertyGroup>
4-
<VersionPrefix>1.2.2</VersionPrefix>
3+
<VersionPrefix>1.3.0</VersionPrefix>
54
<TargetFramework>netcoreapp1.0</TargetFramework>
65
<AssemblyName>JsonApiDotNetCore</AssemblyName>
76
<PackageId>JsonApiDotNetCore</PackageId>
87
<RuntimeFrameworkVersion>1.1.1</RuntimeFrameworkVersion>
98
<PackageTargetFallback>$(PackageTargetFallback);dnxcore50;portable-net45+win8</PackageTargetFallback>
109
</PropertyGroup>
11-
1210
<ItemGroup>
1311
<PackageReference Include="Microsoft.AspNetCore.Routing" Version="1.1.1" />
1412
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
1513
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" />
1614
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
15+
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
1716
</ItemGroup>
18-
19-
</Project>
17+
</Project>

Diff for: src/JsonApiDotNetCoreExample/Data/AppDbContext.cs

+13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ public AppDbContext(DbContextOptions<AppDbContext> options)
99
: base(options)
1010
{ }
1111

12+
protected override void OnModelCreating(ModelBuilder modelBuilder)
13+
{
14+
modelBuilder.Entity<TodoItem>()
15+
.HasOne(t => t.Assignee)
16+
.WithMany(p => p.AssignedTodoItems)
17+
.HasForeignKey(t => t.AssigneeId);
18+
19+
modelBuilder.Entity<TodoItem>()
20+
.HasOne(t => t.Owner)
21+
.WithMany(p => p.TodoItems)
22+
.HasForeignKey(t => t.OwnerId);
23+
}
24+
1225
public DbSet<TodoItem> TodoItems { get; set; }
1326
public DbSet<Person> People { get; set; }
1427
public DbSet<TodoItemCollection> TodoItemCollections { get; set; }

Diff for: src/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.1" />
2929
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.1" />
3030
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.0.0" />
31+
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.0" />
3132
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="1.1.0" />
3233
<PackageReference Include="DotNetCoreDocs" Version="0.4.0" />
3334
</ItemGroup>

Diff for: src/JsonApiDotNetCoreExample/Migrations/20170330020650_AddAssignedTodoItems.Designer.cs

+100
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)