Skip to content

Commit 9c9a4f2

Browse files
author
Bart Koelman
authored
Auto-generated JSON:API controllers using source generators (#1117)
* Removed Interface target from custom attributes, because it does not work (see https://stackoverflow.com/questions/540749/can-a-c-sharp-class-inherit-attributes-from-its-interface). Fixed detection of attribute usage on base classes. * Auto-generation of JSON:API controllers (using source generators) * Updated integration tests to use auto-generated controllers * Fixed: throw at startup when multiple controllers are registered for the same resource type * Addressed cleanupcode/inspectcode issues * Add dependency from JsonApiDotNetCore to SourceGenerators, so it gets pulled in via NuGet * Added unit tests for controller source generator * Update ROADMAP.md * Updated documentation * Produce NuGet package in cibuild This lets each project opt-in for producing a NuGet package, instead of listing them globally * Addressed review feedback
1 parent e65b65a commit 9c9a4f2

File tree

250 files changed

+2931
-1385
lines changed

Some content is hidden

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

250 files changed

+2931
-1385
lines changed

Build.ps1

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ function CheckLastExitCode {
88

99
function RunInspectCode {
1010
$outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml')
11-
dotnet jb inspectcode JsonApiDotNetCore.sln --no-build --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal
11+
# passing --build instead of --no-build as workaround for https://youtrack.jetbrains.com/issue/RSRP-487054
12+
dotnet jb inspectcode JsonApiDotNetCore.sln --build --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal
1213
CheckLastExitCode
1314

1415
[xml]$xml = Get-Content "$outputPath"
@@ -84,10 +85,10 @@ function CreateNuGetPackage {
8485
}
8586

8687
if ([string]::IsNullOrWhitespace($versionSuffix)) {
87-
dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts
88+
dotnet pack --no-restore --no-build --configuration Release --output .\artifacts
8889
}
8990
else {
90-
dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=$versionSuffix
91+
dotnet pack --no-restore --no-build --configuration Release --output .\artifacts --version-suffix=$versionSuffix
9192
}
9293

9394
CheckLastExitCode

Directory.Build.props

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
<AspNetCoreVersion>5.0.*</AspNetCoreVersion>
55
<EFCoreVersion>5.0.*</EFCoreVersion>
66
<NpgsqlPostgreSQLVersion>5.0.*</NpgsqlPostgreSQLVersion>
7+
<MicrosoftCodeAnalysisVersion>3.*</MicrosoftCodeAnalysisVersion>
8+
<HumanizerVersion>2.11.10</HumanizerVersion>
9+
<JsonApiDotNetCoreVersionPrefix>5.0.0</JsonApiDotNetCoreVersionPrefix>
710
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodingGuidelines.ruleset</CodeAnalysisRuleSet>
811
<WarningLevel>9999</WarningLevel>
912
<Nullable>enable</Nullable>
13+
<IsPackable>false</IsPackable>
14+
<WarnOnPackingNonPackableProject>false</WarnOnPackingNonPackableProject>
1015
</PropertyGroup>
1116

1217
<ItemGroup>

JsonApiDotNetCore.sln

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MultiDbContextTests", "test
4444
EndProject
4545
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestBuildingBlocks", "test\TestBuildingBlocks\TestBuildingBlocks.csproj", "{210FD61E-FF5D-4CEE-8E0D-C739ECCCBA21}"
4646
EndProject
47+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore.SourceGenerators", "src\JsonApiDotNetCore.SourceGenerators\JsonApiDotNetCore.SourceGenerators.csproj", "{952C0FDE-AFC8-455C-986F-6CC882ED8953}"
48+
EndProject
49+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGeneratorDebugger", "test\SourceGeneratorDebugger\SourceGeneratorDebugger.csproj", "{87D066F9-3540-4AC7-A748-134900969EE5}"
50+
EndProject
51+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGeneratorTests", "test\SourceGeneratorTests\SourceGeneratorTests.csproj", "{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}"
52+
EndProject
4753
Global
4854
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4955
Debug|Any CPU = Debug|Any CPU
@@ -54,6 +60,18 @@ Global
5460
Release|x86 = Release|x86
5561
EndGlobalSection
5662
GlobalSection(ProjectConfigurationPlatforms) = postSolution
63+
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
64+
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|Any CPU.Build.0 = Debug|Any CPU
65+
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x64.ActiveCfg = Debug|Any CPU
66+
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x64.Build.0 = Debug|Any CPU
67+
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x86.ActiveCfg = Debug|Any CPU
68+
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x86.Build.0 = Debug|Any CPU
69+
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|Any CPU.ActiveCfg = Release|Any CPU
70+
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|Any CPU.Build.0 = Release|Any CPU
71+
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x64.ActiveCfg = Release|Any CPU
72+
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x64.Build.0 = Release|Any CPU
73+
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x86.ActiveCfg = Release|Any CPU
74+
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x86.Build.0 = Release|Any CPU
5775
{CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
5876
{CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
5977
{CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -162,18 +180,6 @@ Global
162180
{21D27239-138D-4604-8E49-DCBE41BCE4C8}.Release|x64.Build.0 = Release|Any CPU
163181
{21D27239-138D-4604-8E49-DCBE41BCE4C8}.Release|x86.ActiveCfg = Release|Any CPU
164182
{21D27239-138D-4604-8E49-DCBE41BCE4C8}.Release|x86.Build.0 = Release|Any CPU
165-
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
166-
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|Any CPU.Build.0 = Debug|Any CPU
167-
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x64.ActiveCfg = Debug|Any CPU
168-
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x64.Build.0 = Debug|Any CPU
169-
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x86.ActiveCfg = Debug|Any CPU
170-
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x86.Build.0 = Debug|Any CPU
171-
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|Any CPU.ActiveCfg = Release|Any CPU
172-
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|Any CPU.Build.0 = Release|Any CPU
173-
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x64.ActiveCfg = Release|Any CPU
174-
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x64.Build.0 = Release|Any CPU
175-
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x86.ActiveCfg = Release|Any CPU
176-
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x86.Build.0 = Release|Any CPU
177183
{6CAFDDBE-00AB-4784-801B-AB419C3C3A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
178184
{6CAFDDBE-00AB-4784-801B-AB419C3C3A26}.Debug|Any CPU.Build.0 = Debug|Any CPU
179185
{6CAFDDBE-00AB-4784-801B-AB419C3C3A26}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -210,6 +216,42 @@ Global
210216
{210FD61E-FF5D-4CEE-8E0D-C739ECCCBA21}.Release|x64.Build.0 = Release|Any CPU
211217
{210FD61E-FF5D-4CEE-8E0D-C739ECCCBA21}.Release|x86.ActiveCfg = Release|Any CPU
212218
{210FD61E-FF5D-4CEE-8E0D-C739ECCCBA21}.Release|x86.Build.0 = Release|Any CPU
219+
{952C0FDE-AFC8-455C-986F-6CC882ED8953}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
220+
{952C0FDE-AFC8-455C-986F-6CC882ED8953}.Debug|Any CPU.Build.0 = Debug|Any CPU
221+
{952C0FDE-AFC8-455C-986F-6CC882ED8953}.Debug|x64.ActiveCfg = Debug|Any CPU
222+
{952C0FDE-AFC8-455C-986F-6CC882ED8953}.Debug|x64.Build.0 = Debug|Any CPU
223+
{952C0FDE-AFC8-455C-986F-6CC882ED8953}.Debug|x86.ActiveCfg = Debug|Any CPU
224+
{952C0FDE-AFC8-455C-986F-6CC882ED8953}.Debug|x86.Build.0 = Debug|Any CPU
225+
{952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|Any CPU.ActiveCfg = Release|Any CPU
226+
{952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|Any CPU.Build.0 = Release|Any CPU
227+
{952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|x64.ActiveCfg = Release|Any CPU
228+
{952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|x64.Build.0 = Release|Any CPU
229+
{952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|x86.ActiveCfg = Release|Any CPU
230+
{952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|x86.Build.0 = Release|Any CPU
231+
{87D066F9-3540-4AC7-A748-134900969EE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
232+
{87D066F9-3540-4AC7-A748-134900969EE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
233+
{87D066F9-3540-4AC7-A748-134900969EE5}.Debug|x64.ActiveCfg = Debug|Any CPU
234+
{87D066F9-3540-4AC7-A748-134900969EE5}.Debug|x64.Build.0 = Debug|Any CPU
235+
{87D066F9-3540-4AC7-A748-134900969EE5}.Debug|x86.ActiveCfg = Debug|Any CPU
236+
{87D066F9-3540-4AC7-A748-134900969EE5}.Debug|x86.Build.0 = Debug|Any CPU
237+
{87D066F9-3540-4AC7-A748-134900969EE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
238+
{87D066F9-3540-4AC7-A748-134900969EE5}.Release|Any CPU.Build.0 = Release|Any CPU
239+
{87D066F9-3540-4AC7-A748-134900969EE5}.Release|x64.ActiveCfg = Release|Any CPU
240+
{87D066F9-3540-4AC7-A748-134900969EE5}.Release|x64.Build.0 = Release|Any CPU
241+
{87D066F9-3540-4AC7-A748-134900969EE5}.Release|x86.ActiveCfg = Release|Any CPU
242+
{87D066F9-3540-4AC7-A748-134900969EE5}.Release|x86.Build.0 = Release|Any CPU
243+
{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
244+
{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
245+
{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|x64.ActiveCfg = Debug|Any CPU
246+
{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|x64.Build.0 = Debug|Any CPU
247+
{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|x86.ActiveCfg = Debug|Any CPU
248+
{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|x86.Build.0 = Debug|Any CPU
249+
{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
250+
{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Release|Any CPU.Build.0 = Release|Any CPU
251+
{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Release|x64.ActiveCfg = Release|Any CPU
252+
{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Release|x64.Build.0 = Release|Any CPU
253+
{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Release|x86.ActiveCfg = Release|Any CPU
254+
{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Release|x86.Build.0 = Release|Any CPU
213255
EndGlobalSection
214256
GlobalSection(SolutionProperties) = preSolution
215257
HideSolutionNode = FALSE
@@ -228,6 +270,9 @@ Global
228270
{6CAFDDBE-00AB-4784-801B-AB419C3C3A26} = {026FBC6C-AF76-4568-9B87-EC73457899FD}
229271
{EC3202C6-1D4C-4B14-A599-B9D3F27FE3BA} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
230272
{210FD61E-FF5D-4CEE-8E0D-C739ECCCBA21} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
273+
{952C0FDE-AFC8-455C-986F-6CC882ED8953} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}
274+
{87D066F9-3540-4AC7-A748-134900969EE5} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
275+
{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
231276
EndGlobalSection
232277
GlobalSection(ExtensibilityGlobals) = postSolution
233278
SolutionGuid = {A2421882-8F0A-4905-928F-B550B192F9A4}

README.md

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,26 +45,14 @@ See [our documentation](https://www.jsonapi.net/) for detailed usage.
4545
```c#
4646
#nullable enable
4747

48+
[Resource]
4849
public class Article : Identifiable<int>
4950
{
5051
[Attr]
5152
public string Name { get; set; } = null!;
5253
}
5354
```
5455

55-
### Controllers
56-
57-
```c#
58-
public class ArticlesController : JsonApiController<Article, int>
59-
{
60-
public ArticlesController(IJsonApiOptions options, IResourceGraph resourceGraph,
61-
ILoggerFactory loggerFactory, IResourceService<Article, int> resourceService)
62-
: base(options, resourceGraph, loggerFactory, resourceService)
63-
{
64-
}
65-
}
66-
```
67-
6856
### Middleware
6957

7058
```c#

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ The need for breaking changes has blocked several efforts in the v4.x release, s
2424
- [x] Nullable reference types [#1029](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1029)
2525
- [x] Improved paging links [#1010](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1010)
2626
- [x] Configuration validation [#170](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/170)
27+
- [x] Auto-generated controllers [#732](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/732) [#365](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/365)
2728
- [ ] Support .NET 6 with EF Core 6 [#1109](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1109)
2829

2930
Aside from the list above, we have interest in the following topics. It's too soon yet to decide whether they'll make it into v5.x or in a later major version.
3031

31-
- Auto-generated controllers [#732](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/732) [#365](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/365)
3232
- Optimistic concurrency [#1004](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1004)
3333
- Extract annotations into separate package [#730](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/730)
3434
- OpenAPI (Swagger) [#1046](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1046)

docs/getting-started/step-by-step.md

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ The shortest path to a running API looks like:
77
- Install
88
- Define models
99
- Define the DbContext
10-
- Define controllers
1110
- Add Middleware and Services
1211
- Seed the database
1312
- Start the app
@@ -40,6 +39,7 @@ The easiest way to do this is to inherit from `Identifiable<TId>`.
4039
```c#
4140
#nullable enable
4241

42+
[Resource]
4343
public class Person : Identifiable<int>
4444
{
4545
[Attr]
@@ -63,22 +63,6 @@ public class AppDbContext : DbContext
6363
}
6464
```
6565

66-
### Define Controllers
67-
68-
You need to create controllers that inherit from `JsonApiController<TResource, TId>`
69-
where `TResource` is the model that inherits from `Identifiable<TId>`.
70-
71-
```c#
72-
public class PeopleController : JsonApiController<Person, int>
73-
{
74-
public PeopleController(IJsonApiOptions options, IResourceGraph resourceGraph,
75-
ILoggerFactory loggerFactory, IResourceService<Person, int> resourceService)
76-
: base(options, resourceGraph, loggerFactory, resourceService)
77-
{
78-
}
79-
}
80-
```
81-
8266
### Middleware and Services
8367

8468
Finally, add the services by adding the following to your Startup.ConfigureServices:

docs/usage/extensibility/controllers.md

Lines changed: 95 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,99 @@
11
# Controllers
22

3-
You need to create controllers that inherit from `JsonApiController<TResource, TId>`
3+
To expose API endpoints, ASP.NET controllers need to be defined.
4+
5+
_since v5_
6+
7+
Controllers are auto-generated (using [source generators](https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview)) when you add `[Resource]` on your model class:
8+
9+
```c#
10+
[Resource] // Generates ArticlesController.g.cs
11+
public class Article : Identifiable<Guid>
12+
{
13+
// ...
14+
}
15+
```
16+
17+
## Resource Access Control
18+
19+
It is often desirable to limit which endpoints are exposed on your controller.
20+
A subset can be specified too:
21+
22+
```c#
23+
[Resource(GenerateControllerEndpoints =
24+
JsonApiEndpoints.GetCollection | JsonApiEndpoints.GetSingle)]
25+
public class Article : Identifiable<Guid>
26+
{
27+
// ...
28+
}
29+
```
30+
31+
Instead of passing a set of endpoints, you can use `JsonApiEndpoints.Query` to generate all read-only endpoints or `JsonApiEndpoints.Command` for all write-only endpoints.
32+
33+
When an endpoint is blocked, an HTTP 403 Forbidden response is returned.
34+
35+
```http
36+
DELETE http://localhost:14140/articles/1 HTTP/1.1
37+
```
38+
39+
```json
40+
{
41+
"links": {
42+
"self": "/articles"
43+
},
44+
"errors": [
45+
{
46+
"id": "dde7f219-2274-4473-97ef-baac3e7c1487",
47+
"status": "403",
48+
"title": "The requested endpoint is not accessible.",
49+
"detail": "Endpoint '/articles/1' is not accessible for DELETE requests."
50+
}
51+
]
52+
}
53+
```
54+
55+
## Augmenting controllers
56+
57+
Auto-generated controllers can easily be augmented because they are partial classes. For example:
58+
59+
```c#
60+
[DisableRoutingConvention]
61+
[Route("some/custom/route")]
62+
[DisableQueryString(JsonApiQueryStringParameters.Include)]
63+
partial class ArticlesController
64+
{
65+
[HttpPost]
66+
public IActionResult Upload()
67+
{
68+
// ...
69+
}
70+
}
71+
```
72+
73+
If you need to inject extra dependencies, tell the IoC container with `[ActivatorUtilitiesConstructor]` to prefer your constructor:
74+
75+
```c#
76+
partial class ArticlesController
77+
{
78+
private IAuthenticationService _authService;
79+
80+
[ActivatorUtilitiesConstructor]
81+
public ArticlesController(IAuthenticationService authService, IJsonApiOptions options,
82+
IResourceGraph resourceGraph, ILoggerFactory loggerFactory,
83+
IResourceService<Article, Guid> resourceService)
84+
: base(options, resourceGraph, loggerFactory, resourceService)
85+
{
86+
_authService = authService;
87+
}
88+
}
89+
```
90+
91+
In case you don't want to use auto-generated controllers and define them yourself (see below), remove
92+
`[Resource]` from your models or use `[Resource(GenerateControllerEndpoints = JsonApiEndpoints.None)]`.
93+
94+
## Earlier versions
95+
96+
In earlier versions of JsonApiDotNetCore, you needed to create controllers that inherit from `JsonApiController<TResource, TId>`. For example:
497

598
```c#
699
public class ArticlesController : JsonApiController<Article, Guid>
@@ -15,7 +108,7 @@ public class ArticlesController : JsonApiController<Article, Guid>
15108

16109
If you want to setup routes yourself, you can instead inherit from `BaseJsonApiController<TResource, TId>` and override its methods with your own `[HttpGet]`, `[HttpHead]`, `[HttpPost]`, `[HttpPatch]` and `[HttpDelete]` attributes added on them. Don't forget to add `[FromBody]` on parameters where needed.
17110

18-
## Resource Access Control
111+
### Resource Access Control
19112

20113
It is often desirable to limit which routes are exposed on your controller.
21114

@@ -37,25 +130,3 @@ public class ReportsController : JsonApiController<Report, int>
37130
```
38131

39132
For more information about resource service injection, see [Replacing injected services](~/usage/extensibility/layer-overview.md#replacing-injected-services) and [Resource Services](~/usage/extensibility/services.md).
40-
41-
When a route is blocked, an HTTP 403 Forbidden response is returned.
42-
43-
```http
44-
DELETE http://localhost:14140/people/1 HTTP/1.1
45-
```
46-
47-
```json
48-
{
49-
"links": {
50-
"self": "/api/v1/people"
51-
},
52-
"errors": [
53-
{
54-
"id": "dde7f219-2274-4473-97ef-baac3e7c1487",
55-
"status": "403",
56-
"title": "The requested endpoint is not accessible.",
57-
"detail": "Endpoint '/people/1' is not accessible for DELETE requests."
58-
}
59-
]
60-
}
61-
```

docs/usage/extensibility/services.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Resource Services
22

33
The `IResourceService` acts as a service layer between the controller and the data access layer.
4-
This allows you to customize it however you want. This is also a good place to implement custom business logic.
4+
This allows you to customize it however you want. While this is still a potential place to implement custom business logic,
5+
since v4, [Resource Definitions](~/usage/extensibility/resource-definitions.md) are more suitable for that.
56

67
## Supplementing Default Behavior
78

@@ -77,7 +78,7 @@ public class ProductService : IResourceService<Product, int>
7778

7879
## Limited Requirements
7980

80-
In some cases it may be necessary to only expose a few methods on a resource. For this reason, we have created a hierarchy of service interfaces that can be used to get the exact implementation you require.
81+
In some cases it may be necessary to only expose a few actions on a resource. For this reason, we have created a hierarchy of service interfaces that can be used to get the exact implementation you require.
8182

8283
This interface hierarchy is defined by this tree structure.
8384

@@ -152,7 +153,18 @@ public class Startup
152153
}
153154
```
154155

155-
Then in the controller, you should inherit from the JSON:API controller and pass the services into the named, optional base parameters:
156+
Then on your model, pass in the set of endpoints to expose (the ones that you've registered services for):
157+
158+
```c#
159+
[Resource(GenerateControllerEndpoints =
160+
JsonApiEndpoints.Create | JsonApiEndpoints.Delete)]
161+
public class Article : Identifiable<int>
162+
{
163+
// ...
164+
}
165+
```
166+
167+
Alternatively, when using a hand-written controller, you should inherit from the JSON:API controller and pass the services into the named, optional base parameters:
156168

157169
```c#
158170
public class ArticlesController : JsonApiController<Article, int>

0 commit comments

Comments
 (0)