Skip to content

Commit f8867e4

Browse files
committed
feat(402): adds filter and sort to ResourceDefinition
1 parent be0d510 commit f8867e4

File tree

2 files changed

+87
-2
lines changed

2 files changed

+87
-2
lines changed

src/JsonApiDotNetCore/Models/ResourceDefinition.cs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using JsonApiDotNetCore.Internal;
2+
using JsonApiDotNetCore.Internal.Query;
23
using System;
34
using System.Collections.Generic;
45
using System.Linq;
@@ -13,7 +14,10 @@ public interface IResourceDefinition
1314
}
1415

1516
/// <summary>
16-
/// A scoped service used to...
17+
/// exposes developer friendly hooks into how their resources are exposed.
18+
/// It is intended to improve the experience and reduce boilerplate for commonly required features.
19+
/// The goal of this class is to reduce the frequency with which developers have to override the
20+
/// service and repository layers.
1721
/// </summary>
1822
/// <typeparam name="T">The resource type</typeparam>
1923
public class ResourceDefinition<T> : IResourceDefinition where T : class, IIdentifiable
@@ -47,8 +51,10 @@ private bool InstanceOutputAttrsAreSpecified()
4751
return declaringType == derivedType;
4852
}
4953

54+
public delegate dynamic FilterExpression(T type);
55+
5056
// TODO: need to investigate options for caching these
51-
protected List<AttrAttribute> Remove(Expression<Func<T, dynamic>> filter, List<AttrAttribute> from = null)
57+
protected List<AttrAttribute> Remove(Expression<FilterExpression> filter, List<AttrAttribute> from = null)
5258
{
5359
from = from ?? _contextEntity.Attributes;
5460

@@ -115,5 +121,71 @@ private List<AttrAttribute> GetOutputAttrs()
115121

116122
return _requestCachedAttrs;
117123
}
124+
125+
/// <summary>
126+
/// Define a set of custom query expressions that can be applied
127+
/// instead of the default query behavior. A common use-case for this
128+
/// is including related resources and filtering on them.
129+
/// </summary>
130+
/// <returns>
131+
/// A set of custom queries that will be applied instead of the default
132+
/// queries for the given key. Null will be returned if default behavior
133+
/// is desired.
134+
/// </returns>
135+
/// <example>
136+
/// <code>
137+
/// protected override QueryFilters GetQueryFilters() => {
138+
/// { "facility", (t, value) => t.Include(t => t.Tenant)
139+
/// .Where(t => t.Facility == value) }
140+
/// }
141+
/// </code>
142+
///
143+
/// If the logic is simply too complex for an in-line expression, you can
144+
/// delegate to a private method:
145+
/// <code>
146+
/// protected override QueryFilters GetQueryFilters()
147+
/// => new QueryFilters {
148+
/// { "is-active", FilterIsActive }
149+
/// };
150+
///
151+
/// private IQueryable&lt;Model&gt; FilterIsActive(IQueryable&lt;Model&gt; query, string value)
152+
/// {
153+
/// // some complex logic goes here...
154+
/// return query.Where(x => x.IsActive == computedValue);
155+
/// }
156+
/// </code>
157+
/// </example>
158+
protected virtual QueryFilters GetQueryFilters() => null;
159+
160+
/// <summary>
161+
/// This is an alias type intended to simplify the implementation's
162+
/// method signature.
163+
/// See <see cref="GetQueryFilters" /> for usage details.
164+
/// <summary>
165+
public class QueryFilters : Dictionary<string, Func<IQueryable<T>, string, IQueryable<T>>> { }
166+
167+
/// <summary>
168+
/// Define a the default sort order if no sort key is provided.
169+
/// </summary>
170+
/// <returns>
171+
/// A list of properties and the direction they should be sorted.
172+
/// </returns>
173+
/// <example>
174+
/// <code>
175+
/// protected override PropertySortOrder GetDefaultSortOrder()
176+
/// => new PropertySortOrder {
177+
/// (t => t.Prop1, SortDirection.Ascending),
178+
/// (t => t.Prop2, SortDirection.Descending),
179+
/// };
180+
/// </code>
181+
/// </example>
182+
protected virtual PropertySortOrder GetDefaultSortOrder() => null;
183+
184+
/// <summary>
185+
/// This is an alias type intended to simplify the implementation's
186+
/// method signature.
187+
/// See <see cref="GetQueryFilters" /> for usage details.
188+
/// <summary>
189+
public class PropertySortOrder : List<(Expression<Func<T, dynamic>>, SortDirection)> { }
118190
}
119191
}

test/UnitTests/Models/ResourceDefinitionTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using JsonApiDotNetCore.Builders;
22
using JsonApiDotNetCore.Internal;
3+
using JsonApiDotNetCore.Internal.Query;
34
using JsonApiDotNetCore.Models;
45
using System.Collections.Generic;
6+
using System.Linq;
57
using Xunit;
68

79
namespace UnitTests.Models
@@ -98,6 +100,7 @@ public class Model : Identifiable
98100
{
99101
[Attr("name")] public string AlwaysExcluded { get; set; }
100102
[Attr("password")] public string Password { get; set; }
103+
[Attr("prop")] public string Prop { get; set; }
101104
}
102105

103106
public class RequestFilteredResource : ResourceDefinition<Model>
@@ -116,6 +119,16 @@ protected override List<AttrAttribute> OutputAttrs()
116119
=> _isAdmin
117120
? Remove(m => m.AlwaysExcluded)
118121
: Remove(m => new { m.AlwaysExcluded, m.Password }, from: base.OutputAttrs());
122+
123+
protected override QueryFilters GetQueryFilters()
124+
=> new QueryFilters {
125+
{ "is-active", (query, value) => query.Select(x => x) }
126+
};
127+
128+
protected override PropertySortOrder GetDefaultSortOrder()
129+
=> new PropertySortOrder {
130+
(t => t.Prop, SortDirection.Ascending)
131+
};
119132
}
120133

121134
public class InstanceFilteredResource : ResourceDefinition<Model>

0 commit comments

Comments
 (0)