Skip to content

Commit 2c1f18d

Browse files
author
Bart Koelman
committed
basic wire-up
1 parent 4b426dc commit 2c1f18d

8 files changed

+349
-5
lines changed

src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public class EntityFrameworkCoreRepository<TResource, TId> : IResourceRepository
3535
private readonly IResourceFactory _resourceFactory;
3636
private readonly IEnumerable<IQueryConstraintProvider> _constraintProviders;
3737
private readonly TraceLogWriter<EntityFrameworkCoreRepository<TResource, TId>> _traceWriter;
38+
private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor;
3839

3940
/// <inheritdoc />
4041
public virtual string TransactionId => _dbContext.Database.CurrentTransaction?.TransactionId.ToString();
@@ -55,6 +56,10 @@ public EntityFrameworkCoreRepository(ITargetedFields targetedFields, IDbContextR
5556
_constraintProviders = constraintProviders;
5657
_dbContext = contextResolver.GetContext();
5758
_traceWriter = new TraceLogWriter<EntityFrameworkCoreRepository<TResource, TId>>(loggerFactory);
59+
60+
#pragma warning disable 612 // Method is obsolete
61+
_resourceDefinitionAccessor = _resourceFactory.GetResourceDefinitionAccessor();
62+
#pragma warning restore 612
5863
}
5964

6065
/// <inheritdoc />
@@ -142,12 +147,14 @@ protected virtual IQueryable<TResource> GetAll()
142147
}
143148

144149
/// <inheritdoc />
145-
public virtual Task<TResource> GetForCreateAsync(TId id, CancellationToken cancellationToken)
150+
public virtual async Task<TResource> GetForCreateAsync(TId id, CancellationToken cancellationToken)
146151
{
147152
var resource = _resourceFactory.CreateInstance<TResource>();
148153
resource.Id = id;
149154

150-
return Task.FromResult(resource);
155+
await _resourceDefinitionAccessor.OnInitializeResourceAsync(resource, cancellationToken);
156+
157+
return resource;
151158
}
152159

153160
/// <inheritdoc />
@@ -175,10 +182,14 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r
175182
attribute.SetValue(resourceForDatabase, attribute.GetValue(resourceFromRequest));
176183
}
177184

185+
await _resourceDefinitionAccessor.OnBeforeCreateResourceAsync(resourceForDatabase, cancellationToken);
186+
178187
DbSet<TResource> dbSet = _dbContext.Set<TResource>();
179188
await dbSet.AddAsync(resourceForDatabase, cancellationToken);
180189

181190
await SaveChangesAsync(cancellationToken);
191+
192+
await _resourceDefinitionAccessor.OnAfterCreateResourceAsync(resourceForDatabase, cancellationToken);
182193
}
183194

184195
/// <inheritdoc />
@@ -216,7 +227,11 @@ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource r
216227
attribute.SetValue(resourceFromDatabase, attribute.GetValue(resourceFromRequest));
217228
}
218229

230+
await _resourceDefinitionAccessor.OnBeforeUpdateResourceAsync(resourceFromDatabase, cancellationToken);
231+
219232
await SaveChangesAsync(cancellationToken);
233+
234+
await _resourceDefinitionAccessor.OnAfterUpdateResourceAsync(resourceFromDatabase, cancellationToken);
220235
}
221236

222237
protected void AssertIsNotClearingRequiredRelationship(RelationshipAttribute relationship, TResource leftResource, object rightValue)
@@ -276,9 +291,13 @@ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToke
276291
}
277292
}
278293

294+
await _resourceDefinitionAccessor.OnBeforeDeleteResourceAsync<TResource, TId>(id, cancellationToken);
295+
279296
_dbContext.Remove(resource);
280297

281298
await SaveChangesAsync(cancellationToken);
299+
300+
await _resourceDefinitionAccessor.OnAfterDeleteResourceAsync<TResource, TId>(id, cancellationToken);
282301
}
283302

284303
private NavigationEntry GetNavigationEntry(TResource resource, RelationshipAttribute relationship)

src/JsonApiDotNetCore/Resources/IResourceDefinition.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System.Collections.Generic;
22
using System.Linq;
3+
using System.Threading;
4+
using System.Threading.Tasks;
35
using JetBrains.Annotations;
46
using JsonApiDotNetCore.Queries.Expressions;
57

@@ -129,5 +131,52 @@ public interface IResourceDefinition<TResource, TId>
129131
/// Enables to add JSON:API meta information, specific to this resource.
130132
/// </summary>
131133
IDictionary<string, object> GetMeta(TResource resource);
134+
135+
/// <summary>
136+
/// Enables to execute custom logic to initialize a newly instantiated resource during a POST request. This is typically used to assign default values to
137+
/// properties or to side-load-and-attach required related entities.
138+
/// </summary>
139+
Task OnInitializeResourceAsync(TResource resource, CancellationToken cancellationToken);
140+
141+
/// <summary>
142+
/// Enables to execute custom logic, just before a resource is inserted in the underlying data store, during a POST request. This is typically used to
143+
/// overwrite attributes from the incoming request, such as a creation-timestamp. Another use case is to add a record to an outbox table, which gets
144+
/// committed along with the resource write in a single transaction (see https://microservices.io/patterns/data/transactional-outbox.html).
145+
/// </summary>
146+
Task OnBeforeCreateResourceAsync(TResource resource, CancellationToken cancellationToken);
147+
148+
/// <summary>
149+
/// Enables to execute custom logic, right after a resource has been inserted in the underlying data store, during a POST request. A typical use case is
150+
/// to enqueue a notification message on a service bus.
151+
/// </summary>
152+
Task OnAfterCreateResourceAsync(TResource resource, CancellationToken cancellationToken);
153+
154+
// TODO: How to throw in case the resource is soft-deleted or archived during PATCH? This needs to execute before applying targeted fields.
155+
// OnValidateUpdate?
156+
157+
/// <summary>
158+
/// Enables to execute custom logic, just before a resource is updated in the underlying data store, during a PATCH request. This is typically used to
159+
/// overwrite attributes from the incoming request, such as a last-modification-timestamp. Another use case is to add a record to an outbox table, which
160+
/// gets committed along with the resource write in a single transaction (see https://microservices.io/patterns/data/transactional-outbox.html).
161+
/// </summary>
162+
Task OnBeforeUpdateResourceAsync(TResource resource, CancellationToken cancellationToken);
163+
164+
/// <summary>
165+
/// Enables to execute custom logic, right after a resource has been updated in the underlying data store, during a PATCH request. A typical use case is
166+
/// to enqueue a notification message on a service bus.
167+
/// </summary>
168+
Task OnAfterUpdateResourceAsync(TResource resource, CancellationToken cancellationToken);
169+
170+
/// <summary>
171+
/// Enables to execute custom logic, just before a resource is deleted from the underlying data store, during a DELETE request. This enables to throw in
172+
/// case the user does not have permission, an attempt is made to delete an unarchived resource or a non-closed work item etc.
173+
/// </summary>
174+
Task OnBeforeDeleteResourceAsync(TId id, CancellationToken cancellationToken);
175+
176+
/// <summary>
177+
/// Enables to execute custom logic, right after a resource has been deleted from the underlying data store, during a DELETE request. A typical use case
178+
/// is to enqueue a notification message on a service bus.
179+
/// </summary>
180+
Task OnAfterDeleteResourceAsync(TId id, CancellationToken cancellationToken);
132181
}
133182
}

src/JsonApiDotNetCore/Resources/IResourceDefinitionAccessor.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
46
using JsonApiDotNetCore.Queries.Expressions;
57

68
namespace JsonApiDotNetCore.Resources
@@ -45,5 +47,47 @@ public interface IResourceDefinitionAccessor
4547
/// Invokes <see cref="IResourceDefinition{TResource,TId}.GetMeta" /> for the specified resource.
4648
/// </summary>
4749
IDictionary<string, object> GetMeta(Type resourceType, IIdentifiable resourceInstance);
50+
51+
/// <summary>
52+
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnInitializeResourceAsync" /> for the specified resource.
53+
/// </summary>
54+
Task OnInitializeResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
55+
where TResource : class, IIdentifiable;
56+
57+
/// <summary>
58+
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnBeforeCreateResourceAsync" /> for the specified resource.
59+
/// </summary>
60+
Task OnBeforeCreateResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
61+
where TResource : class, IIdentifiable;
62+
63+
/// <summary>
64+
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnAfterCreateResourceAsync" /> for the specified resource.
65+
/// </summary>
66+
Task OnAfterCreateResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
67+
where TResource : class, IIdentifiable;
68+
69+
/// <summary>
70+
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnBeforeUpdateResourceAsync" /> for the specified resource.
71+
/// </summary>
72+
Task OnBeforeUpdateResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
73+
where TResource : class, IIdentifiable;
74+
75+
/// <summary>
76+
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnAfterUpdateResourceAsync" /> for the specified resource.
77+
/// </summary>
78+
Task OnAfterUpdateResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
79+
where TResource : class, IIdentifiable;
80+
81+
/// <summary>
82+
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnBeforeDeleteResourceAsync" /> for the specified resource ID.
83+
/// </summary>
84+
Task OnBeforeDeleteResourceAsync<TResource, TId>(TId id, CancellationToken cancellationToken)
85+
where TResource : class, IIdentifiable<TId>;
86+
87+
/// <summary>
88+
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnAfterDeleteResourceAsync" /> for the specified resource ID.
89+
/// </summary>
90+
Task OnAfterDeleteResourceAsync<TResource, TId>(TId id, CancellationToken cancellationToken)
91+
where TResource : class, IIdentifiable<TId>;
4892
}
4993
}

src/JsonApiDotNetCore/Resources/IResourceFactory.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Linq.Expressions;
3+
using JsonApiDotNetCore.Repositories;
34

45
namespace JsonApiDotNetCore.Resources
56
{
@@ -23,5 +24,12 @@ public TResource CreateInstance<TResource>()
2324
/// Returns an expression tree that represents creating a new resource object instance.
2425
/// </summary>
2526
public NewExpression CreateNewExpression(Type resourceType);
27+
28+
/// <summary>
29+
/// Provides access to the request-scoped <see cref="IResourceDefinitionAccessor" /> instance. This method has been added solely to prevent introducing a
30+
/// breaking change in the <see cref="EntityFrameworkCoreRepository{TResource,TId}" /> constructor and will be removed in the next major version.
31+
/// </summary>
32+
[Obsolete]
33+
IResourceDefinitionAccessor GetResourceDefinitionAccessor();
2634
}
2735
}

src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.ComponentModel;
44
using System.Linq;
55
using System.Linq.Expressions;
6+
using System.Threading;
7+
using System.Threading.Tasks;
68
using JetBrains.Annotations;
79
using JsonApiDotNetCore.Configuration;
810
using JsonApiDotNetCore.Queries.Expressions;
@@ -113,6 +115,48 @@ public virtual IDictionary<string, object> GetMeta(TResource resource)
113115
return null;
114116
}
115117

118+
/// <inheritdoc />
119+
public virtual Task OnInitializeResourceAsync(TResource resource, CancellationToken cancellationToken)
120+
{
121+
return Task.CompletedTask;
122+
}
123+
124+
/// <inheritdoc />
125+
public virtual Task OnBeforeCreateResourceAsync(TResource resource, CancellationToken cancellationToken)
126+
{
127+
return Task.CompletedTask;
128+
}
129+
130+
/// <inheritdoc />
131+
public virtual Task OnAfterCreateResourceAsync(TResource resource, CancellationToken cancellationToken)
132+
{
133+
return Task.CompletedTask;
134+
}
135+
136+
/// <inheritdoc />
137+
public virtual Task OnBeforeUpdateResourceAsync(TResource resource, CancellationToken cancellationToken)
138+
{
139+
return Task.CompletedTask;
140+
}
141+
142+
/// <inheritdoc />
143+
public virtual Task OnAfterUpdateResourceAsync(TResource resource, CancellationToken cancellationToken)
144+
{
145+
return Task.CompletedTask;
146+
}
147+
148+
/// <inheritdoc />
149+
public virtual Task OnBeforeDeleteResourceAsync(TId id, CancellationToken cancellationToken)
150+
{
151+
return Task.CompletedTask;
152+
}
153+
154+
/// <inheritdoc />
155+
public virtual Task OnAfterDeleteResourceAsync(TId id, CancellationToken cancellationToken)
156+
{
157+
return Task.CompletedTask;
158+
}
159+
116160
/// <summary>
117161
/// This is an alias type intended to simplify the implementation's method signature. See <see cref="CreateSortExpressionFromLambda" /> for usage
118162
/// details.

src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Threading;
4+
using System.Threading.Tasks;
35
using JetBrains.Annotations;
46
using JsonApiDotNetCore.Configuration;
57
using JsonApiDotNetCore.Queries.Expressions;
@@ -89,6 +91,72 @@ public IDictionary<string, object> GetMeta(Type resourceType, IIdentifiable reso
8991
return resourceDefinition.GetMeta((dynamic)resourceInstance);
9092
}
9193

94+
/// <inheritdoc />
95+
public async Task OnInitializeResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
96+
where TResource : class, IIdentifiable
97+
{
98+
ArgumentGuard.NotNull(resource, nameof(resource));
99+
100+
dynamic resourceDefinition = ResolveResourceDefinition(typeof(TResource));
101+
await resourceDefinition.OnInitializeResourceAsync(resource, cancellationToken);
102+
}
103+
104+
/// <inheritdoc />
105+
public async Task OnBeforeCreateResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
106+
where TResource : class, IIdentifiable
107+
{
108+
ArgumentGuard.NotNull(resource, nameof(resource));
109+
110+
dynamic resourceDefinition = ResolveResourceDefinition(typeof(TResource));
111+
await resourceDefinition.OnBeforeCreateResourceAsync(resource, cancellationToken);
112+
}
113+
114+
/// <inheritdoc />
115+
public async Task OnAfterCreateResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
116+
where TResource : class, IIdentifiable
117+
{
118+
ArgumentGuard.NotNull(resource, nameof(resource));
119+
120+
dynamic resourceDefinition = ResolveResourceDefinition(typeof(TResource));
121+
await resourceDefinition.OnAfterCreateResourceAsync(resource, cancellationToken);
122+
}
123+
124+
/// <inheritdoc />
125+
public async Task OnBeforeUpdateResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
126+
where TResource : class, IIdentifiable
127+
{
128+
ArgumentGuard.NotNull(resource, nameof(resource));
129+
130+
dynamic resourceDefinition = ResolveResourceDefinition(typeof(TResource));
131+
await resourceDefinition.OnBeforeUpdateResourceAsync(resource, cancellationToken);
132+
}
133+
134+
/// <inheritdoc />
135+
public async Task OnAfterUpdateResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
136+
where TResource : class, IIdentifiable
137+
{
138+
ArgumentGuard.NotNull(resource, nameof(resource));
139+
140+
dynamic resourceDefinition = ResolveResourceDefinition(typeof(TResource));
141+
await resourceDefinition.OnAfterUpdateResourceAsync(resource, cancellationToken);
142+
}
143+
144+
/// <inheritdoc />
145+
public async Task OnBeforeDeleteResourceAsync<TResource, TId>(TId id, CancellationToken cancellationToken)
146+
where TResource : class, IIdentifiable<TId>
147+
{
148+
dynamic resourceDefinition = ResolveResourceDefinition(typeof(TResource));
149+
await resourceDefinition.OnBeforeDeleteResourceAsync(id, cancellationToken);
150+
}
151+
152+
/// <inheritdoc />
153+
public async Task OnAfterDeleteResourceAsync<TResource, TId>(TId id, CancellationToken cancellationToken)
154+
where TResource : class, IIdentifiable<TId>
155+
{
156+
dynamic resourceDefinition = ResolveResourceDefinition(typeof(TResource));
157+
await resourceDefinition.OnAfterDeleteResourceAsync(id, cancellationToken);
158+
}
159+
92160
protected virtual object ResolveResourceDefinition(Type resourceType)
93161
{
94162
ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resourceType);

src/JsonApiDotNetCore/Resources/ResourceFactory.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ public ResourceFactory(IServiceProvider serviceProvider)
1919
_serviceProvider = serviceProvider;
2020
}
2121

22+
/// <inheritdoc />
23+
public IResourceDefinitionAccessor GetResourceDefinitionAccessor()
24+
{
25+
return _serviceProvider.GetRequiredService<IResourceDefinitionAccessor>();
26+
}
27+
2228
/// <inheritdoc />
2329
public IIdentifiable CreateInstance(Type resourceType)
2430
{

0 commit comments

Comments
 (0)