Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Basic wire-up for write callbacks
  • Loading branch information
Bart Koelman committed May 3, 2021
commit 940db8ceb19c66ecab13131809924b60ea5eec7f
9 changes: 9 additions & 0 deletions src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Queries.Expressions;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
using JsonApiDotNetCore.Services;

namespace JsonApiDotNetCore.Queries
{
Expand Down Expand Up @@ -57,5 +59,12 @@ QueryLayer WrapLayerForSecondaryEndpoint<TId>(QueryLayer secondaryLayer, Resourc
/// Builds a query for a to-many relationship with a filter to match on its left and right resource IDs.
/// </summary>
QueryLayer ComposeForHasMany<TId>(HasManyAttribute hasManyRelationship, TId leftId, ICollection<IIdentifiable> rightResourceIds);

/// <summary>
/// Provides access to the request-scoped <see cref="IResourceDefinitionAccessor" /> instance. This method has been added solely to prevent introducing a
/// breaking change in the <see cref="JsonApiResourceService{TResource,TId}" /> constructor and will be removed in the next major version.
/// </summary>
[Obsolete]
IResourceDefinitionAccessor GetResourceDefinitionAccessor();
}
}
6 changes: 6 additions & 0 deletions src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,12 @@ public QueryLayer ComposeForHasMany<TId>(HasManyAttribute hasManyRelationship, T
};
}

/// <inheritdoc />
public IResourceDefinitionAccessor GetResourceDefinitionAccessor()
{
return _resourceDefinitionAccessor;
}

protected virtual IReadOnlyCollection<IncludeElementExpression> GetIncludeElements(IReadOnlyCollection<IncludeElementExpression> includeElements,
ResourceContext resourceContext)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public class EntityFrameworkCoreRepository<TResource, TId> : IResourceRepository
private readonly IResourceFactory _resourceFactory;
private readonly IEnumerable<IQueryConstraintProvider> _constraintProviders;
private readonly TraceLogWriter<EntityFrameworkCoreRepository<TResource, TId>> _traceWriter;
private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor;

/// <inheritdoc />
public virtual string TransactionId => _dbContext.Database.CurrentTransaction?.TransactionId.ToString();
Expand All @@ -55,6 +56,10 @@ public EntityFrameworkCoreRepository(ITargetedFields targetedFields, IDbContextR
_constraintProviders = constraintProviders;
_dbContext = contextResolver.GetContext();
_traceWriter = new TraceLogWriter<EntityFrameworkCoreRepository<TResource, TId>>(loggerFactory);

#pragma warning disable 612 // Method is obsolete
_resourceDefinitionAccessor = resourceFactory.GetResourceDefinitionAccessor();
#pragma warning restore 612
}

/// <inheritdoc />
Expand Down Expand Up @@ -175,6 +180,8 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r
attribute.SetValue(resourceForDatabase, attribute.GetValue(resourceFromRequest));
}

await _resourceDefinitionAccessor.OnBeforeCreateResourceAsync(resourceForDatabase, cancellationToken);

DbSet<TResource> dbSet = _dbContext.Set<TResource>();
await dbSet.AddAsync(resourceForDatabase, cancellationToken);

Expand Down Expand Up @@ -216,6 +223,8 @@ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource r
attribute.SetValue(resourceFromDatabase, attribute.GetValue(resourceFromRequest));
}

await _resourceDefinitionAccessor.OnBeforeUpdateResourceAsync(resourceFromDatabase, cancellationToken);

await SaveChangesAsync(cancellationToken);
}

Expand Down
104 changes: 104 additions & 0 deletions src/JsonApiDotNetCore/Resources/IResourceDefinition.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using JsonApiDotNetCore.Queries.Expressions;

Expand Down Expand Up @@ -29,6 +31,7 @@ public interface IResourceDefinition<TResource> : IResourceDefinition<TResource,
/// The resource identifier type.
/// </typeparam>
[PublicAPI]
// ReSharper disable once TypeParameterCanBeVariant -- Justification: making TId contravariant is a breaking change.
public interface IResourceDefinition<TResource, TId>
where TResource : class, IIdentifiable<TId>
{
Expand Down Expand Up @@ -129,5 +132,106 @@ public interface IResourceDefinition<TResource, TId>
/// Enables to add JSON:API meta information, specific to this resource.
/// </summary>
IDictionary<string, object> GetMeta(TResource resource);

/// <summary>
/// Enables to execute custom logic to initialize a newly instantiated resource during a POST request. This is typically used to assign default values to
/// properties or to side-load-and-attach required relationships.
/// </summary>
/// <param name="resource">
/// A freshly instantiated resource object.
/// </param>
/// <param name="cancellationToken">
/// Propagates notification that request handling should be canceled.
/// </param>
Task OnInitializeResourceAsync(TResource resource, CancellationToken cancellationToken);

/// <summary>
/// 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
/// overwrite attributes from the incoming request, such as a creation-timestamp. Another use case is to add a notification message to an outbox table,
/// which gets committed along with the resource write in a single transaction (see https://microservices.io/patterns/data/transactional-outbox.html).
/// </summary>
/// <param name="resource">
/// The resource with incoming request data applied on it.
/// </param>
/// <param name="cancellationToken">
/// Propagates notification that request handling should be canceled.
/// </param>
Task OnBeforeCreateResourceAsync(TResource resource, CancellationToken cancellationToken);

/// <summary>
/// Enables to execute custom logic after a resource has been inserted in the underlying data store, during a POST request. A typical use case is to
/// enqueue a notification message on a service bus.
/// </summary>
/// <param name="resource">
/// The re-fetched resource after a successful insertion.
/// </param>
/// <param name="cancellationToken">
/// Propagates notification that request handling should be canceled.
/// </param>
Task OnAfterCreateResourceAsync(TResource resource, CancellationToken cancellationToken);

/// <summary>
/// Enables to execute custom logic to validate if the update request can be processed, based on the currently stored resource. A typical use case is to
/// throw when the resource is soft-deleted or archived.
/// </summary>
/// <param name="resource">
/// The resource as currently stored in the underlying data store.
/// </param>
/// <param name="cancellationToken">
/// Propagates notification that request handling should be canceled.
/// </param>
Task OnAfterGetForUpdateResourceAsync(TResource resource, CancellationToken cancellationToken);

/// <summary>
/// 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
/// overwrite attributes from the incoming request, such as a last-modification-timestamp. Another use case is to add a notification message to an outbox
/// table, which gets committed along with the resource write in a single transaction (see
/// https://microservices.io/patterns/data/transactional-outbox.html).
/// </summary>
/// <param name="resource">
/// The stored resource with incoming request data applied on it.
/// </param>
/// <param name="cancellationToken">
/// Propagates notification that request handling should be canceled.
/// </param>
Task OnBeforeUpdateResourceAsync(TResource resource, CancellationToken cancellationToken);

/// <summary>
/// Enables to execute custom logic after a resource has been updated in the underlying data store, during a PATCH request. A typical use case is to
/// enqueue a notification message on a service bus.
/// </summary>
/// <param name="resource">
/// The re-fetched resource after a successful update.
/// </param>
/// <param name="cancellationToken">
/// Propagates notification that request handling should be canceled.
/// </param>
Task OnAfterUpdateResourceAsync(TResource resource, CancellationToken cancellationToken);

/// <summary>
/// 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
/// case the user does not have permission, an attempt is made to delete an unarchived resource or a non-closed work item etc. Another use case is to add
/// a notification message to an outbox table, which gets committed along with the resource write in a single transaction (see
/// https://microservices.io/patterns/data/transactional-outbox.html).
/// </summary>
/// <param name="id">
/// The identifier of the resource to delete.
/// </param>
/// <param name="cancellationToken">
/// Propagates notification that request handling should be canceled.
/// </param>
Task OnBeforeDeleteResourceAsync(TId id, CancellationToken cancellationToken);

/// <summary>
/// Enables to execute custom logic after a resource has been deleted from the underlying data store, during a DELETE request. A typical use case is to
/// enqueue a notification message on a service bus.
/// </summary>
/// <param name="id">
/// The identifier of the resource to delete.
/// </param>
/// <param name="cancellationToken">
/// Propagates notification that request handling should be canceled.
/// </param>
Task OnAfterDeleteResourceAsync(TId id, CancellationToken cancellationToken);
}
}
50 changes: 50 additions & 0 deletions src/JsonApiDotNetCore/Resources/IResourceDefinitionAccessor.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JsonApiDotNetCore.Queries.Expressions;

namespace JsonApiDotNetCore.Resources
Expand Down Expand Up @@ -45,5 +47,53 @@ public interface IResourceDefinitionAccessor
/// Invokes <see cref="IResourceDefinition{TResource,TId}.GetMeta" /> for the specified resource.
/// </summary>
IDictionary<string, object> GetMeta(Type resourceType, IIdentifiable resourceInstance);

/// <summary>
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnInitializeResourceAsync" /> for the specified resource.
/// </summary>
Task OnInitializeResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
where TResource : class, IIdentifiable;

/// <summary>
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnBeforeCreateResourceAsync" /> for the specified resource.
/// </summary>
Task OnBeforeCreateResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
where TResource : class, IIdentifiable;

/// <summary>
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnAfterCreateResourceAsync" /> for the specified resource.
/// </summary>
Task OnAfterCreateResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
where TResource : class, IIdentifiable;

/// <summary>
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnAfterGetForUpdateResourceAsync" /> for the specified resource.
/// </summary>
Task OnAfterGetForUpdateResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
where TResource : class, IIdentifiable;

/// <summary>
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnBeforeUpdateResourceAsync" /> for the specified resource.
/// </summary>
Task OnBeforeUpdateResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
where TResource : class, IIdentifiable;

/// <summary>
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnAfterUpdateResourceAsync" /> for the specified resource.
/// </summary>
Task OnAfterUpdateResourceAsync<TResource>(TResource resource, CancellationToken cancellationToken)
where TResource : class, IIdentifiable;

/// <summary>
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnBeforeDeleteResourceAsync" /> for the specified resource ID.
/// </summary>
Task OnBeforeDeleteResourceAsync<TResource, TId>(TId id, CancellationToken cancellationToken)
where TResource : class, IIdentifiable<TId>;

/// <summary>
/// Invokes <see cref="IResourceDefinition{TResource,TId}.OnAfterDeleteResourceAsync" /> for the specified resource ID.
/// </summary>
Task OnAfterDeleteResourceAsync<TResource, TId>(TId id, CancellationToken cancellationToken)
where TResource : class, IIdentifiable<TId>;
}
}
8 changes: 8 additions & 0 deletions src/JsonApiDotNetCore/Resources/IResourceFactory.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Linq.Expressions;
using JsonApiDotNetCore.Repositories;

namespace JsonApiDotNetCore.Resources
{
Expand All @@ -23,5 +24,12 @@ public TResource CreateInstance<TResource>()
/// Returns an expression tree that represents creating a new resource object instance.
/// </summary>
public NewExpression CreateNewExpression(Type resourceType);

/// <summary>
/// Provides access to the request-scoped <see cref="IResourceDefinitionAccessor" /> instance. This method has been added solely to prevent introducing a
/// breaking change in the <see cref="EntityFrameworkCoreRepository{TResource,TId}" /> constructor and will be removed in the next major version.
/// </summary>
[Obsolete]
IResourceDefinitionAccessor GetResourceDefinitionAccessor();
}
}
50 changes: 50 additions & 0 deletions src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Queries.Expressions;
Expand Down Expand Up @@ -113,6 +115,54 @@ public virtual IDictionary<string, object> GetMeta(TResource resource)
return null;
}

/// <inheritdoc />
public virtual Task OnInitializeResourceAsync(TResource resource, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

/// <inheritdoc />
public virtual Task OnBeforeCreateResourceAsync(TResource resource, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

/// <inheritdoc />
public virtual Task OnAfterCreateResourceAsync(TResource resource, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

/// <inheritdoc />
public virtual Task OnAfterGetForUpdateResourceAsync(TResource resource, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

/// <inheritdoc />
public virtual Task OnBeforeUpdateResourceAsync(TResource resource, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

/// <inheritdoc />
public virtual Task OnAfterUpdateResourceAsync(TResource resource, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

/// <inheritdoc />
public virtual Task OnBeforeDeleteResourceAsync(TId id, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

/// <inheritdoc />
public virtual Task OnAfterDeleteResourceAsync(TId id, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

/// <summary>
/// This is an alias type intended to simplify the implementation's method signature. See <see cref="CreateSortExpressionFromLambda" /> for usage
/// details.
Expand Down
Loading