Skip to content

Commit 0f385f1

Browse files
committed
2 parents 50d34b0 + e924833 commit 0f385f1

File tree

1 file changed

+199
-93
lines changed

1 file changed

+199
-93
lines changed

docs/usage/resources/hooks.md

Lines changed: 199 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
12
# Resource Hooks
2-
This section covers the usage of **Resource Hooks**, which is a feature of`ResourceDefinition<T>`. See the [ResourceDefinition usage guide](resource-definitions.md) for a general explanation on how to set up a `ResourceDefinition<T>`.
3+
This section covers the usage of **Resource Hooks**, which is a feature of`ResourceDefinition<T>`. See the [ResourceDefinition usage guide](resource-definitions.md) for a general explanation on how to set up a `ResourceDefinition<T>`. For a quick start, jump right to the [Getting started: most minimal example](#getting-started-most-minimal-example) section.
34

45
By implementing resource hooks on a `ResourceDefintion<T>`, it is possible to intercept the execution of the **Resource Service Layer** (RSL) in various ways. This enables the developer to conveniently define business logic without having to override the RSL. It can be used to implement e.g.
56
* Authorization
@@ -8,20 +9,20 @@ By implementing resource hooks on a `ResourceDefintion<T>`, it is possible to in
89
* Transformation of the served data
910

1011
This usage guide covers the following sections
11-
1. [**Semantics: pipelines, actions and hooks**](#semantics-pipelines-actions-and-hooks).
12+
1. [**Semantics: pipelines, actions and hooks**](#semantics-pipelines-actions-and-hooks)
1213
Understanding the semantics will be helpful in identifying which hooks on `ResourceDefinition<T>` you need to implement for your use-case.
13-
2. [**Hook execution overview**](#hook-execution-overview)
14-
A table overview of all pipelines and involved hooks
15-
3. [**Examples: basic usage**](#examples-basic-usage)
16-
* [**Most minimal example**](#most-minimal-example)
17-
* [**Logging**](#logging)
18-
* [**Transforming data with OnReturn**](#transforming-data-with-onreturn)
19-
5. [**Examples: advanced usage**](#examples-advanced-usage)
20-
* [**Simple authorization: explicitly affected resources**](#simple-authorization-explicitly-affected-resources)
21-
* [**Advanced authorization: implicitly affected resources**](#advanced-authorization-implicitly-affected-resources)
22-
* [**Synchronizing data across microservices**](#synchronizing-data-across-microservices)
23-
* [**Hooks for many-to-many join tables**](#hooks-for-many-to-many-join-tables)
24-
14+
2. [**Basic usage**](#basic-usage)
15+
* [**Getting started: most minimal example**](#getting-started-most-minimal-example)
16+
* [**Logging**](#logging)
17+
* [**Transforming data with OnReturn**](#transforming-data-with-onreturn)
18+
* [**Loading database values**](#loading-database-values)
19+
3. [**Advanced usage**](#advanced-usage)
20+
* [**Simple authorization: explicitly affected resources**](#simple-authorization-explicitly-affected-resources)
21+
* [**Advanced authorization: implicitly affected resources**](#advanced-authorization-implicitly-affected-resources)
22+
* [**Synchronizing data across microservices**](#synchronizing-data-across-microservices)
23+
* [**Hooks for many-to-many join tables**](#hooks-for-many-to-many-join-tables)
24+
4. [**Hook execution overview**](#hook-execution-overview)
25+
A table overview of all pipelines and involved hooks
2526

2627
# 1. Semantics: pipelines, actions and hooks
2728

@@ -89,87 +90,28 @@ Any return content can be intercepted and transformed as desired by implementing
8990
<br><br>
9091
For an overview of all pipelines, hooks and actions, see the table below, and for more detailed information about the available hooks, see the [IResourceHookContainer<T>](https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/ab1f96d8255532461da47d290c5440b9e7e6a4a5/src/JsonApiDotNetCore/Hooks/IResourceHookContainer.cs) interface.
9192

92-
# 2. Hook execution overview
93+
# 2. Basic usage
9394

95+
## Getting started: most minimal example
96+
To use resource hooks, you are required to turn them on in your `startup.cs` configuration
9497

95-
This table below shows the involved hooks per pipeline.
96-
<table>
97-
<tr>
98-
<th rowspan="2">Pipeline</th>
99-
<th colspan="5"><span style="font-style:italic">Execution Flow</span></th>
100-
</tr>
101-
<tr>
102-
<td align="center"><b>Before Hooks</b></td>
103-
<td align="center" colspan="2"><b>Repository Actions</td>
104-
<td align="center"><b>After Hooks</td>
105-
<td align="center"><b>OnReturn</td>
106-
</tr>
107-
<tr>
108-
<td>Get</td>
109-
<td align="center">BeforeRead</td>
110-
<td align="center" colspan="2" rowspan="3">read</td>
111-
<td align="center">AfterRead</td>
112-
<td align="center">✅</td>
113-
</tr>
114-
<tr>
115-
<td>GetSingle</td>
116-
<td align="center">BeforeRead</td>
117-
<td align="center">AfterRead</td>
118-
<td align="center">✅</td>
119-
</tr>
120-
<tr>
121-
<td>GetRelationship</td>
122-
<td align="center">BeforeRead</td>
123-
<td align="center">AfterRead</td>
124-
<td align="center">✅</td>
125-
</tr>
126-
<tr>
127-
<td>Post</td>
128-
<td align="center">BeforeCreate</td>
129-
<td align="center" colspan="2">create<br>update relationship</td>
130-
<td align="center">AfterCreate</td>
131-
<td align="center">✅</td>
132-
</tr>
133-
<tr>
134-
<td>Patch</td>
135-
<td align="center">BeforeUpdate<br>BeforeUpdateRelationship<br>BeforeImplicitUpdateRelationship</td>
136-
<td align="center" colspan="2">update<br>update relationship<br>implicit update relationship</td>
137-
<td align="center">AfterUpdate<br>AfterUpdateRelationship</td>
138-
<td align="center">✅</td>
139-
</tr>
140-
<tr>
141-
<td>PatchRelationship</td>
142-
<td align="center">BeforeUpdate<br>BeforeUpdateRelationship</td>
143-
<td align="center" colspan="2">update<br>update relationship<br>implicit update relationship</td>
144-
<td align="center">AfterUpdate<br>AfterUpdateRelationship</td>
145-
<td align="center">❌</td>
146-
</tr>
147-
<tr>
148-
<td>Delete</td>
149-
<td align="center">BeforeDelete</td>
150-
<td align="center" colspan="2">delete<br>implicit update relationship</td>
151-
<td align="center">AfterDelete</td>
152-
<td align="center">❌</td>
153-
</tr>
154-
<tr>
155-
<td>BulkPost</td>
156-
<td colspan="5" align="center"><i>Not yet supported</i></td>
157-
</tr>
158-
<tr>
159-
<td>BulkPatch</td>
160-
<td colspan="5" align="center"><i>Not yet supported</i></td>
161-
</tr>
162-
<tr>
163-
<td>BulkDelete</td>
164-
<td colspan="5" align="center"><i>Not yet supported</i></td>
165-
</tr>
166-
</table>
167-
168-
169-
# 3. Examples: basic usage
98+
```c#
99+
public void ConfigureServices(IServiceCollection services)
100+
{
101+
...
102+
services.AddJsonApi<ApiDbContext>(
103+
options =>
104+
{
105+
options.EnableResourceHooks = true; // default is false
106+
options.LoadDatabaseValues = false; // default is false
107+
}
108+
);
109+
...
110+
}
111+
```
112+
For this example, we may set `LoadDatabaseValues` to `false`. See the [Loading database values](#loading-database-values) example for more information about this option.
170113

171-
## Most minimal example
172-
The simplest example does not require much explanation. This hook would triggered by any default JsonApiDotNetCore API route for `Article`.
114+
The simplest case of resource hooks we can then implement should not require a lot of explanation. This hook would triggered by any default JsonApiDotNetCore API route for `Article`.
173115
```c#
174116
public class ArticleResource : ResourceDefinition<Article>
175117
{
@@ -294,7 +236,95 @@ public class PersonResource : ResourceDefinition<Person>
294236
```
295237
Note that not only anonymous people will be excluded when directly performing a `GET /people`, but also when included through relationships, like `GET /articles?include=author,reviewers`. Simultaneously, `if` condition that checks for `ResourcePipeline.Get` in the `PersonResource` ensures we still get expected responses from the API when eg. creating a person with `WantsPrivacy` set to true.
296238

297-
# 3. Examples: advanced usage
239+
## Loading database values
240+
When a hook is executed for a particular resource, JsonApiDotNetCore can load the corresponding database values and provide them in the hooks. This can be useful for eg.
241+
* having a diff between a previous and new state of a resource (for example when updating a resource)
242+
* performing authorization rules based on the property of a resource.
243+
244+
For example, consider a scenario in with the following two requirements:
245+
* We need to log all updates on resources revealing their old and new value.
246+
* We need to check if the property `IsLocked` is set is `true`, and if so, cancel the operation.
247+
248+
Consider an `Article` with title *Hello there* and API user trying to update the the title of this article to *Bye bye*. The above requirements could be implemented as follows
249+
```c#
250+
public class ArticleResource : ResourceDefinition<Article>
251+
{
252+
private readonly ILogger _logger;
253+
private readonly IJsonApiContext _context;
254+
public constructor ArticleResource(ILogger logger, IJsonApiContext context)
255+
{
256+
_logger = logger;
257+
_context = context;
258+
}
259+
260+
public override IEnumerable<Article> BeforeUpdate(EntityDiff<Article> entityDiff, ResourcePipeline pipeline)
261+
{
262+
// PropertyGetter is a helper class that takes care of accessing the values on an instance of Article using reflection.
263+
var getter = new PropertyGetter<Article>();
264+
265+
entityDiff.RequestEntities.ForEach( requestEntity =>
266+
{
267+
268+
var databaseEntity = entityDiff.DatabaseEntities.Single( e => e.Id == a.Id);
269+
270+
if (databaseEntity.IsLocked) throw new JsonApiException(403, "Forbidden: this article is locked!")
271+
272+
foreach (var attr in _context.AttributesToUpdate)
273+
{
274+
var oldValue = getter(databaseEntity, attr);
275+
var newValue = getter(requestEntity, attr);
276+
277+
_logger.LogAttributeUpdate(oldValue, newValue)
278+
}
279+
});
280+
return entityDiff.RequestEntities;
281+
}
282+
}
283+
```
284+
285+
Note that database values are turned on by default. They can be turned of globally by configuring the startup as follows:
286+
```c#
287+
public void ConfigureServices(IServiceCollection services)
288+
{
289+
...
290+
services.AddJsonApi<ApiDbContext>(
291+
options =>
292+
{
293+
options.LoadDatabaseValues = false; // default is false
294+
}
295+
);
296+
...
297+
}
298+
```
299+
300+
The global setting can be used together with toggling the option on and off on the level of individual hooks using the `LoadDatabaseValues` attribute:
301+
```c#
302+
public class ArticleResource : ResourceDefinition<Article>
303+
{
304+
[LoadDatabaseValues(true)]
305+
public override IEnumerable<Article> BeforeUpdate(EntityDiff<Article> entityDiff, ResourcePipeline pipeline)
306+
{
307+
....
308+
}
309+
310+
[LoadDatabaseValues(false)]
311+
public override IEnumerable<string> BeforeUpdateRelationships(HashSet<string> ids, IAffectedRelationships<Article> resourcesByRelationship, ResourcePipeline pipeline)
312+
{
313+
// the entities stored in the IAffectedRelationships<Article> instance
314+
// are plain resource identifier objects when LoadDatabaseValues is turned off,
315+
// or objects loaded from the database when LoadDatabaseValues is turned on.
316+
....
317+
}
318+
}
319+
```
320+
321+
Note that there are some hooks that the `LoadDatabaseValues` option and attribute does not affect. The only hooks that are affected are:
322+
* `BeforeUpdate`
323+
* `BeforeUpdateRelationship`
324+
325+
326+
327+
# 3. Advanced usage
298328

299329
## Simple authorization: explicitly affected resources
300330
Resource hooks can be used to easily implement authorization in your application. As an example, consider the case in which an API user is not allowed to see anonymous people, which is reflected by the `Anonymous` property on `Person` being set to true`true`. The API should handle this as follows:
@@ -511,3 +541,79 @@ Then, for the same request `GET /articles?include=tags`, the order of execution
511541
And the included collection of tags per article will only contain tags that were added less than two weeks ago.
512542

513543
Note that the introduced inheritance and added relationship attributes does not further affect the many-to-many relationship internally.
544+
545+
# 4. Hook execution overview
546+
547+
548+
This table below shows the involved hooks per pipeline.
549+
<table>
550+
<tr>
551+
<th rowspan="2">Pipeline</th>
552+
<th colspan="5"><span style="font-style:italic">Execution Flow</span></th>
553+
</tr>
554+
<tr>
555+
<td align="center"><b>Before Hooks</b></td>
556+
<td align="center" colspan="2"><b>Repository Actions</td>
557+
<td align="center"><b>After Hooks</td>
558+
<td align="center"><b>OnReturn</td>
559+
</tr>
560+
<tr>
561+
<td>Get</td>
562+
<td align="center">BeforeRead</td>
563+
<td align="center" colspan="2" rowspan="3">read</td>
564+
<td align="center">AfterRead</td>
565+
<td align="center"></td>
566+
</tr>
567+
<tr>
568+
<td>GetSingle</td>
569+
<td align="center">BeforeRead</td>
570+
<td align="center">AfterRead</td>
571+
<td align="center"></td>
572+
</tr>
573+
<tr>
574+
<td>GetRelationship</td>
575+
<td align="center">BeforeRead</td>
576+
<td align="center">AfterRead</td>
577+
<td align="center"></td>
578+
</tr>
579+
<tr>
580+
<td>Post</td>
581+
<td align="center">BeforeCreate</td>
582+
<td align="center" colspan="2">create<br>update relationship</td>
583+
<td align="center">AfterCreate</td>
584+
<td align="center"></td>
585+
</tr>
586+
<tr>
587+
<td>Patch</td>
588+
<td align="center">BeforeUpdate<br>BeforeUpdateRelationship<br>BeforeImplicitUpdateRelationship</td>
589+
<td align="center" colspan="2">update<br>update relationship<br>implicit update relationship</td>
590+
<td align="center">AfterUpdate<br>AfterUpdateRelationship</td>
591+
<td align="center"></td>
592+
</tr>
593+
<tr>
594+
<td>PatchRelationship</td>
595+
<td align="center">BeforeUpdate<br>BeforeUpdateRelationship</td>
596+
<td align="center" colspan="2">update<br>update relationship<br>implicit update relationship</td>
597+
<td align="center">AfterUpdate<br>AfterUpdateRelationship</td>
598+
<td align="center"></td>
599+
</tr>
600+
<tr>
601+
<td>Delete</td>
602+
<td align="center">BeforeDelete</td>
603+
<td align="center" colspan="2">delete<br>implicit update relationship</td>
604+
<td align="center">AfterDelete</td>
605+
<td align="center"></td>
606+
</tr>
607+
<tr>
608+
<td>BulkPost</td>
609+
<td colspan="5" align="center"><i>Not yet supported</i></td>
610+
</tr>
611+
<tr>
612+
<td>BulkPatch</td>
613+
<td colspan="5" align="center"><i>Not yet supported</i></td>
614+
</tr>
615+
<tr>
616+
<td>BulkDelete</td>
617+
<td colspan="5" align="center"><i>Not yet supported</i></td>
618+
</tr>
619+
</table>

0 commit comments

Comments
 (0)