Skip to content

Commit 9584565

Browse files
author
Bart Koelman
authored
Generic resource definitions (#832)
* Generic resource definitions Made resource definitions a first-class pluggable extensibility point, similar to resource services and repositories. So developers now can derive from JsonApiResourceDefinition, implement IResourceDefinition, or insert base classes in the type hierarchy to share common logic. Such a base class can then be registered as an open generic, so that all resources will use that (unless the container finds a non-generic class, which is a better match) instead of the built-in JsonApiResourceDefinition. To accomplish this, I had to split up ResourceDefinition into two: - JsonApiResourceDefinition is highly pluggable like described above - ResourceHooksDefinition contains just the hook callbacks This way, the recursive resolve logic for resource hooks remains as-is. Since it did not use IResourceDefinitionProvider, that type has been replaced with IResourceDefinitionAccessor, which invokes the requested callbacks directly on the found class. What used to be IResourceDefinition (an intermediate type to invoke callbacks on) no longer exists. * Only scan for hooks when enabled in options Replaced IHasMeta with IResourceDefinition method * Fix broken cibuild * Review feedback * Made ResourceDefinitionAccessor.GetResourceDefinition protected * Renamed IRequestMeta to IResponseMeta; updated documentation * quotes * Review feedback
1 parent ca717d0 commit 9584565

File tree

65 files changed

+785
-608
lines changed

Some content is hidden

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

65 files changed

+785
-608
lines changed

benchmarks/DependencyFactory.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
using System;
21
using JsonApiDotNetCore.Configuration;
3-
using JsonApiDotNetCore.Resources;
42
using Microsoft.Extensions.Logging.Abstractions;
5-
using Moq;
63

74
namespace Benchmarks
85
{
@@ -14,15 +11,5 @@ public static IResourceGraph CreateResourceGraph(IJsonApiOptions options)
1411
builder.Add<BenchmarkResource>(BenchmarkResourcePublicNames.Type);
1512
return builder.Build();
1613
}
17-
18-
public static IResourceDefinitionProvider CreateResourceDefinitionProvider(IResourceGraph resourceGraph)
19-
{
20-
var resourceDefinition = new ResourceDefinition<BenchmarkResource>(resourceGraph);
21-
22-
var resourceDefinitionProviderMock = new Mock<IResourceDefinitionProvider>();
23-
resourceDefinitionProviderMock.Setup(provider => provider.Get(It.IsAny<Type>())).Returns(resourceDefinition);
24-
25-
return resourceDefinitionProviderMock.Object;
26-
}
2714
}
2815
}

benchmarks/Serialization/JsonApiSerializerBenchmarks.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using JsonApiDotNetCore.Middleware;
55
using JsonApiDotNetCore.Queries;
66
using JsonApiDotNetCore.QueryStrings.Internal;
7+
using JsonApiDotNetCore.Resources;
78
using JsonApiDotNetCore.Serialization;
89
using JsonApiDotNetCore.Serialization.Building;
910
using Moq;
@@ -46,9 +47,9 @@ private static FieldsToSerialize CreateFieldsToSerialize(IResourceGraph resource
4647
new SparseFieldSetQueryStringParameterReader(request, resourceGraph)
4748
};
4849

49-
var resourceDefinitionProvider = DependencyFactory.CreateResourceDefinitionProvider(resourceGraph);
50+
var accessor = new Mock<IResourceDefinitionAccessor>().Object;
5051

51-
return new FieldsToSerialize(resourceGraph, constraintProviders, resourceDefinitionProvider);
52+
return new FieldsToSerialize(resourceGraph, constraintProviders, accessor);
5253
}
5354

5455
[Benchmark]

docs/usage/meta.md

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,60 @@
11
# Metadata
22

3-
Non-standard metadata can be added to your API responses in two ways: Resource and Request Meta. In the event of a key collision, the Request Meta will take precendence.
3+
Top-level metadata can be added to your API responses in two ways: globally and per resource type. In the event of a key collision, the resource meta will take precendence.
44

5-
## Resource Meta
5+
## Global Meta
66

7-
Resource Meta is metadata defined on the resource itself by implementing the `IHasMeta` interface.
7+
Global metadata can be added by registering a service that implements `IResponseMeta`.
8+
This is useful if you need access to other registered services to build the meta object.
89

910
```c#
10-
public class Person : Identifiable, IHasMeta
11+
public class ResponseMetaService : IResponseMeta
1112
{
13+
public ResponseMetaService(/*...other dependencies here */) {
14+
// ...
15+
}
16+
1217
public Dictionary<string, object> GetMeta()
1318
{
1419
return new Dictionary<string, object>
1520
{
1621
{"copyright", "Copyright 2018 Example Corp."},
17-
{"authors", new[] {"John Doe"}}
22+
{"authors", new string[] {"John Doe"}}
1823
};
1924
}
2025
}
2126
```
2227

23-
## Request Meta
28+
```json
29+
{
30+
"meta": {
31+
"copyright": "Copyright 2018 Example Corp.",
32+
"authors": [
33+
"John Doe"
34+
]
35+
},
36+
"data": {
37+
// ...
38+
}
39+
}
40+
```
2441

25-
Request Meta can be added by injecting a service that implements `IRequestMeta`.
26-
This is useful if you need access to other injected services to build the meta object.
42+
## Resource Meta
43+
44+
Resource-specific metadata can be added by implementing `IResourceDefinition<TResource, TId>.GetMeta` (or overriding it on `JsonApiResourceDefinition`):
2745

2846
```c#
29-
public class RequestMetaService : IRequestMeta
47+
public class PersonDefinition : JsonApiResourceDefinition<Person>
3048
{
31-
public RequestMetaService(/*...other dependencies here */) {
32-
// ...
49+
public PersonDefinition(IResourceGraph resourceGraph) : base(resourceGraph)
50+
{
3351
}
3452

35-
public Dictionary<string, object> GetMeta()
53+
public override IReadOnlyDictionary<string, object> GetMeta()
3654
{
3755
return new Dictionary<string, object>
3856
{
39-
{"copyright", "Copyright 2018 Example Corp."},
40-
{"authors", new string[] {"John Doe"}}
57+
["notice"] = "Check our intranet at http://www.example.com for personal details."
4158
};
4259
}
4360
}
@@ -46,10 +63,7 @@ public class RequestMetaService : IRequestMeta
4663
```json
4764
{
4865
"meta": {
49-
"copyright": "Copyright 2018 Example Corp.",
50-
"authors": [
51-
"John Doe"
52-
]
66+
"notice": "Check our intranet at http://www.example.com for personal details."
5367
},
5468
"data": {
5569
// ...

src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleDefinition.cs renamed to src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010

1111
namespace JsonApiDotNetCoreExample.Definitions
1212
{
13-
public class ArticleDefinition : ResourceDefinition<Article>
13+
public class ArticleHooksDefinition : ResourceHooksDefinition<Article>
1414
{
15-
public ArticleDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
15+
public ArticleHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
1616

1717
public override IEnumerable<Article> OnReturn(HashSet<Article> resources, ResourcePipeline pipeline)
1818
{

src/Examples/JsonApiDotNetCoreExample/Definitions/LockableDefinition.cs renamed to src/Examples/JsonApiDotNetCoreExample/Definitions/LockableHooksDefinition.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99

1010
namespace JsonApiDotNetCoreExample.Definitions
1111
{
12-
public abstract class LockableDefinition<T> : ResourceDefinition<T> where T : class, IIsLockable, IIdentifiable
12+
public abstract class LockableHooksDefinition<T> : ResourceHooksDefinition<T> where T : class, IIsLockable, IIdentifiable
1313
{
14-
protected LockableDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
14+
protected LockableHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
1515

1616
protected void DisallowLocked(IEnumerable<T> resources)
1717
{

src/Examples/JsonApiDotNetCoreExample/Definitions/PassportDefinition.cs renamed to src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010

1111
namespace JsonApiDotNetCoreExample.Definitions
1212
{
13-
public class PassportDefinition : ResourceDefinition<Passport>
13+
public class PassportHooksDefinition : ResourceHooksDefinition<Passport>
1414
{
15-
public PassportDefinition(IResourceGraph resourceGraph) : base(resourceGraph)
15+
public PassportHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph)
1616
{
1717
}
1818

src/Examples/JsonApiDotNetCoreExample/Definitions/PersonDefinition.cs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
11
using System.Collections.Generic;
2-
using System.Linq;
32
using JsonApiDotNetCore.Configuration;
4-
using JsonApiDotNetCore.Hooks.Internal.Execution;
53
using JsonApiDotNetCore.Resources;
64
using JsonApiDotNetCoreExample.Models;
75

86
namespace JsonApiDotNetCoreExample.Definitions
97
{
10-
public class PersonDefinition : LockableDefinition<Person>, IHasMeta
8+
public class PersonDefinition : JsonApiResourceDefinition<Person>
119
{
12-
public PersonDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
13-
14-
public override IEnumerable<string> BeforeUpdateRelationship(HashSet<string> ids, IRelationshipsDictionary<Person> resourcesByRelationship, ResourcePipeline pipeline)
15-
{
16-
BeforeImplicitUpdateRelationship(resourcesByRelationship, pipeline);
17-
return ids;
18-
}
19-
20-
public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary<Person> resourcesByRelationship, ResourcePipeline pipeline)
10+
public PersonDefinition(IResourceGraph resourceGraph) : base(resourceGraph)
2111
{
22-
resourcesByRelationship.GetByRelationship<Passport>().ToList().ForEach(kvp => DisallowLocked(kvp.Value));
2312
}
2413

25-
public IReadOnlyDictionary<string, object> GetMeta()
14+
public override IReadOnlyDictionary<string, object> GetMeta()
2615
{
27-
return new Dictionary<string, object> {
28-
{ "copyright", "Copyright 2015 Example Corp." },
29-
{ "authors", new[] { "Jared Nance", "Maurits Moeys", "Harro van der Kroft" } }
16+
return new Dictionary<string, object>
17+
{
18+
["license"] = "MIT",
19+
["projectUrl"] = "https://github.com/json-api-dotnet/JsonApiDotNetCore/",
20+
["versions"] = new[]
21+
{
22+
"v4.0.0",
23+
"v3.1.0",
24+
"v2.5.2",
25+
"v1.3.1"
26+
}
3027
};
3128
}
3229
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using JsonApiDotNetCore.Configuration;
4+
using JsonApiDotNetCore.Hooks.Internal.Execution;
5+
using JsonApiDotNetCoreExample.Models;
6+
7+
namespace JsonApiDotNetCoreExample.Definitions
8+
{
9+
public class PersonHooksDefinition : LockableHooksDefinition<Person>
10+
{
11+
public PersonHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
12+
13+
public override IEnumerable<string> BeforeUpdateRelationship(HashSet<string> ids, IRelationshipsDictionary<Person> resourcesByRelationship, ResourcePipeline pipeline)
14+
{
15+
BeforeImplicitUpdateRelationship(resourcesByRelationship, pipeline);
16+
return ids;
17+
}
18+
19+
public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary<Person> resourcesByRelationship, ResourcePipeline pipeline)
20+
{
21+
resourcesByRelationship.GetByRelationship<Passport>().ToList().ForEach(kvp => DisallowLocked(kvp.Value));
22+
}
23+
}
24+
}

src/Examples/JsonApiDotNetCoreExample/Definitions/TagDefinition.cs renamed to src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
namespace JsonApiDotNetCoreExample.Definitions
99
{
10-
public class TagDefinition : ResourceDefinition<Tag>
10+
public class TagHooksDefinition : ResourceHooksDefinition<Tag>
1111
{
12-
public TagDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
12+
public TagHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
1313

1414
public override IEnumerable<Tag> BeforeCreate(IResourceHashSet<Tag> affected, ResourcePipeline pipeline)
1515
{

src/Examples/JsonApiDotNetCoreExample/Definitions/TodoDefinition.cs renamed to src/Examples/JsonApiDotNetCoreExample/Definitions/TodoHooksDefinition.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99

1010
namespace JsonApiDotNetCoreExample.Definitions
1111
{
12-
public class TodoDefinition : LockableDefinition<TodoItem>
12+
public class TodoHooksDefinition : LockableHooksDefinition<TodoItem>
1313
{
14-
public TodoDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
14+
public TodoHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
1515

1616
public override void BeforeRead(ResourcePipeline pipeline, bool isIncluded = false, string stringId = null)
1717
{

0 commit comments

Comments
 (0)