Skip to content

Commit 3aa56a5

Browse files
author
Bart Koelman
committed
**TEMP** Squashed commits from #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 11c1dd7 commit 3aa56a5

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)