diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 0a05541b2e..8c08ac9c20 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"jetbrains.resharper.globaltools": {
- "version": "2021.2.2",
+ "version": "2021.3.0",
"commands": [
"jb"
]
@@ -21,7 +21,7 @@
]
},
"dotnet-reportgenerator-globaltool": {
- "version": "4.8.12",
+ "version": "5.0.0",
"commands": [
"reportgenerator"
]
diff --git a/Build.ps1 b/Build.ps1
index a4b930d26c..b02e4bb7bb 100644
--- a/Build.ps1
+++ b/Build.ps1
@@ -8,7 +8,8 @@ function CheckLastExitCode {
function RunInspectCode {
$outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml')
- 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
+ # passing --build instead of --no-build as workaround for https://youtrack.jetbrains.com/issue/RSRP-487054
+ 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
CheckLastExitCode
[xml]$xml = Get-Content "$outputPath"
@@ -84,22 +85,18 @@ function CreateNuGetPackage {
}
if ([string]::IsNullOrWhitespace($versionSuffix)) {
- dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts
- dotnet pack .\src\JsonApiDotNetCore.OpenApi -c Release -o .\artifacts
- dotnet pack .\src\JsonApiDotNetCore.OpenApi.Client -c Release -o .\artifacts
+ dotnet pack --no-restore --no-build --configuration Release --output .\artifacts
}
else {
- dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=$versionSuffix
- dotnet pack .\src\JsonApiDotNetCore.OpenApi -c Release -o .\artifacts --version-suffix=$versionSuffix
- dotnet pack .\src\JsonApiDotNetCore.OpenApi.Client -c Release -o .\artifacts --version-suffix=$versionSuffix
+ dotnet pack --no-restore --no-build --configuration Release --output .\artifacts --version-suffix=$versionSuffix
}
CheckLastExitCode
}
# In a PR the base branch needs to be fetched in order for regitlint to work.
-function FetchBaseBranchIfNotMaster(){
- if ($env:APPVEYOR_PULL_REQUEST_NUMBER -And $env:APPVEYOR_REPO_BRANCH -ne "master"){
+function FetchBaseBranchIfNotMaster() {
+ if ($env:APPVEYOR_PULL_REQUEST_NUMBER -And $env:APPVEYOR_REPO_BRANCH -ne "master") {
git fetch -q origin ${env:APPVEYOR_REPO_BRANCH}:${env:APPVEYOR_REPO_BRANCH}
}
}
diff --git a/Directory.Build.props b/Directory.Build.props
index 43158313fe..2fd1da959a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,19 +1,24 @@
- net5.0
- 5.0.*
- 5.0.*
- 5.0.*
+ net6.0
+ 6.0.*
+ 6.0.*
+ 6.0.*
+ 4.*
+ 2.*
6.2.*
5.0.0
$(MSBuildThisFileDirectory)CodingGuidelines.ruleset
9999
enable
+ enable
+ false
+ false
-
+
@@ -25,11 +30,8 @@
- 33.1.1
3.1.0
- 6.2.0
4.16.1
- 2.4.*
17.0.0
diff --git a/JsonApiDotNetCore.sln b/JsonApiDotNetCore.sln
index 311a1a586c..e4b74f93aa 100644
--- a/JsonApiDotNetCore.sln
+++ b/JsonApiDotNetCore.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28606.126
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31919.166
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}"
EndProject
@@ -44,15 +44,23 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MultiDbContextTests", "test
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestBuildingBlocks", "test\TestBuildingBlocks\TestBuildingBlocks.csproj", "{210FD61E-FF5D-4CEE-8E0D-C739ECCCBA21}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCore.OpenApi", "src\JsonApiDotNetCore.OpenApi\JsonApiDotNetCore.OpenApi.csproj", "{71287D6F-6C3B-44B4-9FCA-E78FE3F02289}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore.SourceGenerators", "src\JsonApiDotNetCore.SourceGenerators\JsonApiDotNetCore.SourceGenerators.csproj", "{952C0FDE-AFC8-455C-986F-6CC882ED8953}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApiTests", "test\OpenApiTests\OpenApiTests.csproj", "{B693DE14-BB28-496F-AB39-B4E674ABCA80}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGeneratorDebugger", "test\SourceGeneratorDebugger\SourceGeneratorDebugger.csproj", "{87D066F9-3540-4AC7-A748-134900969EE5}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCore.OpenApi.Client", "src\JsonApiDotNetCore.OpenApi.Client\JsonApiDotNetCore.OpenApi.Client.csproj", "{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGeneratorTests", "test\SourceGeneratorTests\SourceGeneratorTests.csproj", "{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCoreExampleClient", "src\Examples\JsonApiDotNetCoreExampleClient\JsonApiDotNetCoreExampleClient.csproj", "{7FC5DFA3-6F66-4FD8-820D-81E93856F252}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore.Annotations", "src\JsonApiDotNetCore.Annotations\JsonApiDotNetCore.Annotations.csproj", "{83FF097C-C8C6-477B-9FAB-DF99B84978B5}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApiClientTests", "test\OpenApiClientTests\OpenApiClientTests.csproj", "{77F98215-3085-422E-B99D-4C404C2114CF}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore.OpenApi", "src\JsonApiDotNetCore.OpenApi\JsonApiDotNetCore.OpenApi.csproj", "{71287D6F-6C3B-44B4-9FCA-E78FE3F02289}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenApiTests", "test\OpenApiTests\OpenApiTests.csproj", "{B693DE14-BB28-496F-AB39-B4E674ABCA80}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore.OpenApi.Client", "src\JsonApiDotNetCore.OpenApi.Client\JsonApiDotNetCore.OpenApi.Client.csproj", "{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCoreExampleClient", "src\Examples\JsonApiDotNetCoreExampleClient\JsonApiDotNetCoreExampleClient.csproj", "{7FC5DFA3-6F66-4FD8-820D-81E93856F252}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenApiClientTests", "test\OpenApiClientTests\OpenApiClientTests.csproj", "{77F98215-3085-422E-B99D-4C404C2114CF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -64,6 +72,18 @@ Global
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x64.Build.0 = Debug|Any CPU
+ {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x86.Build.0 = Debug|Any CPU
+ {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x64.ActiveCfg = Release|Any CPU
+ {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x64.Build.0 = Release|Any CPU
+ {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x86.ActiveCfg = Release|Any CPU
+ {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x86.Build.0 = Release|Any CPU
{CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -172,18 +192,6 @@ Global
{21D27239-138D-4604-8E49-DCBE41BCE4C8}.Release|x64.Build.0 = Release|Any CPU
{21D27239-138D-4604-8E49-DCBE41BCE4C8}.Release|x86.ActiveCfg = Release|Any CPU
{21D27239-138D-4604-8E49-DCBE41BCE4C8}.Release|x86.Build.0 = Release|Any CPU
- {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x64.ActiveCfg = Debug|Any CPU
- {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x64.Build.0 = Debug|Any CPU
- {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x86.ActiveCfg = Debug|Any CPU
- {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Debug|x86.Build.0 = Debug|Any CPU
- {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|Any CPU.Build.0 = Release|Any CPU
- {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x64.ActiveCfg = Release|Any CPU
- {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x64.Build.0 = Release|Any CPU
- {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x86.ActiveCfg = Release|Any CPU
- {067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x86.Build.0 = Release|Any CPU
{6CAFDDBE-00AB-4784-801B-AB419C3C3A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6CAFDDBE-00AB-4784-801B-AB419C3C3A26}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6CAFDDBE-00AB-4784-801B-AB419C3C3A26}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -220,6 +228,54 @@ Global
{210FD61E-FF5D-4CEE-8E0D-C739ECCCBA21}.Release|x64.Build.0 = Release|Any CPU
{210FD61E-FF5D-4CEE-8E0D-C739ECCCBA21}.Release|x86.ActiveCfg = Release|Any CPU
{210FD61E-FF5D-4CEE-8E0D-C739ECCCBA21}.Release|x86.Build.0 = Release|Any CPU
+ {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Debug|x64.Build.0 = Debug|Any CPU
+ {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Debug|x86.Build.0 = Debug|Any CPU
+ {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|Any CPU.Build.0 = Release|Any CPU
+ {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|x64.ActiveCfg = Release|Any CPU
+ {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|x64.Build.0 = Release|Any CPU
+ {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|x86.ActiveCfg = Release|Any CPU
+ {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|x86.Build.0 = Release|Any CPU
+ {87D066F9-3540-4AC7-A748-134900969EE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {87D066F9-3540-4AC7-A748-134900969EE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {87D066F9-3540-4AC7-A748-134900969EE5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {87D066F9-3540-4AC7-A748-134900969EE5}.Debug|x64.Build.0 = Debug|Any CPU
+ {87D066F9-3540-4AC7-A748-134900969EE5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {87D066F9-3540-4AC7-A748-134900969EE5}.Debug|x86.Build.0 = Debug|Any CPU
+ {87D066F9-3540-4AC7-A748-134900969EE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {87D066F9-3540-4AC7-A748-134900969EE5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {87D066F9-3540-4AC7-A748-134900969EE5}.Release|x64.ActiveCfg = Release|Any CPU
+ {87D066F9-3540-4AC7-A748-134900969EE5}.Release|x64.Build.0 = Release|Any CPU
+ {87D066F9-3540-4AC7-A748-134900969EE5}.Release|x86.ActiveCfg = Release|Any CPU
+ {87D066F9-3540-4AC7-A748-134900969EE5}.Release|x86.Build.0 = Release|Any CPU
+ {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|x64.Build.0 = Debug|Any CPU
+ {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|x86.Build.0 = Debug|Any CPU
+ {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Release|x64.ActiveCfg = Release|Any CPU
+ {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Release|x64.Build.0 = Release|Any CPU
+ {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Release|x86.ActiveCfg = Release|Any CPU
+ {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Release|x86.Build.0 = Release|Any CPU
+ {83FF097C-C8C6-477B-9FAB-DF99B84978B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {83FF097C-C8C6-477B-9FAB-DF99B84978B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {83FF097C-C8C6-477B-9FAB-DF99B84978B5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {83FF097C-C8C6-477B-9FAB-DF99B84978B5}.Debug|x64.Build.0 = Debug|Any CPU
+ {83FF097C-C8C6-477B-9FAB-DF99B84978B5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {83FF097C-C8C6-477B-9FAB-DF99B84978B5}.Debug|x86.Build.0 = Debug|Any CPU
+ {83FF097C-C8C6-477B-9FAB-DF99B84978B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {83FF097C-C8C6-477B-9FAB-DF99B84978B5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {83FF097C-C8C6-477B-9FAB-DF99B84978B5}.Release|x64.ActiveCfg = Release|Any CPU
+ {83FF097C-C8C6-477B-9FAB-DF99B84978B5}.Release|x64.Build.0 = Release|Any CPU
+ {83FF097C-C8C6-477B-9FAB-DF99B84978B5}.Release|x86.ActiveCfg = Release|Any CPU
+ {83FF097C-C8C6-477B-9FAB-DF99B84978B5}.Release|x86.Build.0 = Release|Any CPU
{71287D6F-6C3B-44B4-9FCA-E78FE3F02289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71287D6F-6C3B-44B4-9FCA-E78FE3F02289}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71287D6F-6C3B-44B4-9FCA-E78FE3F02289}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -298,6 +354,10 @@ Global
{6CAFDDBE-00AB-4784-801B-AB419C3C3A26} = {026FBC6C-AF76-4568-9B87-EC73457899FD}
{EC3202C6-1D4C-4B14-A599-B9D3F27FE3BA} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
{210FD61E-FF5D-4CEE-8E0D-C739ECCCBA21} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
+ {952C0FDE-AFC8-455C-986F-6CC882ED8953} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}
+ {87D066F9-3540-4AC7-A748-134900969EE5} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
+ {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
+ {83FF097C-C8C6-477B-9FAB-DF99B84978B5} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}
{71287D6F-6C3B-44B4-9FCA-E78FE3F02289} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}
{B693DE14-BB28-496F-AB39-B4E674ABCA80} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}
diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings
index 595cae92d8..6ae5a3a918 100644
--- a/JsonApiDotNetCore.sln.DotSettings
+++ b/JsonApiDotNetCore.sln.DotSettings
@@ -14,6 +14,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
3000
50
False
+ True
SOLUTION
True
True
@@ -27,6 +28,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
SUGGESTION
SUGGESTION
SUGGESTION
+ WARNING
SUGGESTION
SUGGESTION
SUGGESTION
@@ -56,6 +58,8 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
DO_NOT_SHOW
HINT
SUGGESTION
+ WARNING
+ WARNING
WARNING
WARNING
SUGGESTION
@@ -71,6 +75,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
SUGGESTION
SUGGESTION
SUGGESTION
+ WARNING
WARNING
WARNING
WARNING
@@ -83,7 +88,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
WARNING
WARNING
WARNING
- <?xml version="1.0" encoding="utf-16"?><Profile name="JADNC Full Cleanup"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><HtmlReformatCode>True</HtmlReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags></Profile>
+ <?xml version="1.0" encoding="utf-16"?><Profile name="JADNC Full Cleanup"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><HtmlReformatCode>True</HtmlReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags></Profile>
JADNC Full Cleanup
Required
Required
@@ -91,10 +96,12 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
Required
Conditional
False
+ False
1
1
1
1
+ False
True
True
True
diff --git a/README.md b/README.md
index e9670ff393..71e5f523e5 100644
--- a/README.md
+++ b/README.md
@@ -45,6 +45,7 @@ See [our documentation](https://www.jsonapi.net/) for detailed usage.
```c#
#nullable enable
+[Resource]
public class Article : Identifiable
{
[Attr]
@@ -52,36 +53,18 @@ public class Article : Identifiable
}
```
-### Controllers
+### Middleware
```c#
-public class ArticlesController : JsonApiController
-{
- public ArticlesController(IJsonApiOptions options, IResourceGraph resourceGraph,
- ILoggerFactory loggerFactory, IResourceService resourceService)
- : base(options, resourceGraph, loggerFactory, resourceService)
- {
- }
-}
-```
+// Program.cs
-### Middleware
+builder.Services.AddJsonApi();
-```c#
-public class Startup
-{
- public IServiceProvider ConfigureServices(IServiceCollection services)
- {
- services.AddJsonApi();
- }
-
- public void Configure(IApplicationBuilder app)
- {
- app.UseRouting();
- app.UseJsonApi();
- app.UseEndpoints(endpoints => endpoints.MapControllers());
- }
-}
+// ...
+
+app.UseRouting();
+app.UseJsonApi();
+app.MapControllers();
```
## Compatibility
@@ -89,16 +72,14 @@ public class Startup
The following chart should help you pick the best version, based on your environment.
See also our [versioning policy](./VERSIONING_POLICY.md).
-| JsonApiDotNetCore | .NET | Entity Framework Core | Status |
-| ----------------- | -------- | --------------------- | -------------------------- |
-| 3.x | Core 2.x | 2.x | Released |
-| 4.x | Core 3.1 | 3.1 | Released |
-| | Core 3.1 | 5 | |
-| | 5 | 5 | |
-| | 6 | 5 | |
-| v5.x (pending) | 5 | 5 | On AppVeyor, to-be-dropped |
-| | 6 | 5 | On AppVeyor, to-be-dropped |
-| | 6 | 6 | Requires build from master |
+| JsonApiDotNetCore | Status | .NET | Entity Framework Core |
+| ----------------- | ----------- | -------- | --------------------- |
+| 3.x | Stable | Core 2.x | 2.x |
+| 4.x | Stable | Core 3.1 | 3.1 |
+| | | Core 3.1 | 5 |
+| | | 5 | 5 |
+| | | 6 | 5 |
+| v5.x | Pre-release | 6 | 6 |
## Contributing
diff --git a/ROADMAP.md b/ROADMAP.md
index 49f499e79c..559e0bfe7d 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -24,13 +24,13 @@ The need for breaking changes has blocked several efforts in the v4.x release, s
- [x] Nullable reference types [#1029](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1029)
- [x] Improved paging links [#1010](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1010)
- [x] Configuration validation [#170](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/170)
-- [ ] Support .NET 6 with EF Core 6 [#1109](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1109)
+- [x] Auto-generated controllers [#732](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/732) [#365](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/365)
+- [x] Support .NET 6 with EF Core 6 [#1109](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1109)
+- [x] Extract annotations into separate package [#730](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/730)
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.
-- Auto-generated controllers [#732](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/732) [#365](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/365)
- Optimistic concurrency [#1004](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1004)
-- Extract annotations into separate package [#730](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/730)
- OpenAPI (Swagger) [#1046](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1046)
- Fluent API [#776](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/776)
- Resource inheritance [#844](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/844)
diff --git a/benchmarks/Benchmarks.csproj b/benchmarks/Benchmarks.csproj
index 225c3a75d7..4bde435c15 100644
--- a/benchmarks/Benchmarks.csproj
+++ b/benchmarks/Benchmarks.csproj
@@ -1,7 +1,7 @@
Exe
- $(NetCoreAppVersion)
+ $(TargetFrameworkName)
diff --git a/benchmarks/Deserialization/DeserializationBenchmarkBase.cs b/benchmarks/Deserialization/DeserializationBenchmarkBase.cs
index b21d7c85e7..bbf746d1a8 100644
--- a/benchmarks/Deserialization/DeserializationBenchmarkBase.cs
+++ b/benchmarks/Deserialization/DeserializationBenchmarkBase.cs
@@ -1,5 +1,3 @@
-using System;
-using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Text.Json;
using JetBrains.Annotations;
@@ -11,114 +9,111 @@
using JsonApiDotNetCore.Serialization.Request.Adapters;
using Microsoft.Extensions.Logging.Abstractions;
-namespace Benchmarks.Deserialization
-{
- public abstract class DeserializationBenchmarkBase
- {
- protected readonly JsonSerializerOptions SerializerReadOptions;
- protected readonly DocumentAdapter DocumentAdapter;
+namespace Benchmarks.Deserialization;
- protected DeserializationBenchmarkBase()
- {
- var options = new JsonApiOptions();
- IResourceGraph resourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build();
- options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph));
- SerializerReadOptions = ((IJsonApiOptions)options).SerializerReadOptions;
+public abstract class DeserializationBenchmarkBase
+{
+ protected readonly JsonSerializerOptions SerializerReadOptions;
+ protected readonly DocumentAdapter DocumentAdapter;
- var serviceContainer = new ServiceContainer();
- var resourceFactory = new ResourceFactory(serviceContainer);
- var resourceDefinitionAccessor = new ResourceDefinitionAccessor(resourceGraph, serviceContainer);
+ protected DeserializationBenchmarkBase()
+ {
+ var options = new JsonApiOptions();
+ IResourceGraph resourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build();
+ options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph));
+ SerializerReadOptions = ((IJsonApiOptions)options).SerializerReadOptions;
- serviceContainer.AddService(typeof(IResourceDefinitionAccessor), resourceDefinitionAccessor);
+ var serviceContainer = new ServiceContainer();
+ var resourceFactory = new ResourceFactory(serviceContainer);
+ var resourceDefinitionAccessor = new ResourceDefinitionAccessor(resourceGraph, serviceContainer);
- serviceContainer.AddService(typeof(IResourceDefinition),
- new JsonApiResourceDefinition(resourceGraph));
+ serviceContainer.AddService(typeof(IResourceDefinitionAccessor), resourceDefinitionAccessor);
+ serviceContainer.AddService(typeof(IResourceDefinition), new JsonApiResourceDefinition(resourceGraph));
- // ReSharper disable once VirtualMemberCallInConstructor
- JsonApiRequest request = CreateJsonApiRequest(resourceGraph);
- var targetedFields = new TargetedFields();
+ // ReSharper disable once VirtualMemberCallInConstructor
+ JsonApiRequest request = CreateJsonApiRequest(resourceGraph);
+ var targetedFields = new TargetedFields();
- var resourceIdentifierObjectAdapter = new ResourceIdentifierObjectAdapter(resourceGraph, resourceFactory);
- var relationshipDataAdapter = new RelationshipDataAdapter(resourceIdentifierObjectAdapter);
- var resourceObjectAdapter = new ResourceObjectAdapter(resourceGraph, resourceFactory, options, relationshipDataAdapter);
- var resourceDataAdapter = new ResourceDataAdapter(resourceDefinitionAccessor, resourceObjectAdapter);
+ var resourceIdentifierObjectAdapter = new ResourceIdentifierObjectAdapter(resourceGraph, resourceFactory);
+ var relationshipDataAdapter = new RelationshipDataAdapter(resourceIdentifierObjectAdapter);
+ var resourceObjectAdapter = new ResourceObjectAdapter(resourceGraph, resourceFactory, options, relationshipDataAdapter);
+ var resourceDataAdapter = new ResourceDataAdapter(resourceDefinitionAccessor, resourceObjectAdapter);
- var atomicReferenceAdapter = new AtomicReferenceAdapter(resourceGraph, resourceFactory);
- var atomicOperationResourceDataAdapter = new ResourceDataInOperationsRequestAdapter(resourceDefinitionAccessor, resourceObjectAdapter);
+ var atomicReferenceAdapter = new AtomicReferenceAdapter(resourceGraph, resourceFactory);
+ var atomicOperationResourceDataAdapter = new ResourceDataInOperationsRequestAdapter(resourceDefinitionAccessor, resourceObjectAdapter);
- var atomicOperationObjectAdapter = new AtomicOperationObjectAdapter(options, atomicReferenceAdapter,
- atomicOperationResourceDataAdapter, relationshipDataAdapter);
+ var atomicOperationObjectAdapter = new AtomicOperationObjectAdapter(options, atomicReferenceAdapter,
+ atomicOperationResourceDataAdapter, relationshipDataAdapter);
- var resourceDocumentAdapter = new DocumentInResourceOrRelationshipRequestAdapter(options, resourceDataAdapter, relationshipDataAdapter);
- var operationsDocumentAdapter = new DocumentInOperationsRequestAdapter(options, atomicOperationObjectAdapter);
+ var resourceDocumentAdapter = new DocumentInResourceOrRelationshipRequestAdapter(options, resourceDataAdapter, relationshipDataAdapter);
+ var operationsDocumentAdapter = new DocumentInOperationsRequestAdapter(options, atomicOperationObjectAdapter);
- DocumentAdapter = new DocumentAdapter(request, targetedFields, resourceDocumentAdapter, operationsDocumentAdapter);
- }
+ DocumentAdapter = new DocumentAdapter(request, targetedFields, resourceDocumentAdapter, operationsDocumentAdapter);
+ }
- protected abstract JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph);
+ protected abstract JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph);
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public sealed class IncomingResource : Identifiable
- {
- [Attr]
- public bool Attribute01 { get; set; }
+ [UsedImplicitly(ImplicitUseTargetFlags.Members)]
+ public sealed class IncomingResource : Identifiable
+ {
+ [Attr]
+ public bool Attribute01 { get; set; }
- [Attr]
- public char Attribute02 { get; set; }
+ [Attr]
+ public char Attribute02 { get; set; }
- [Attr]
- public ulong? Attribute03 { get; set; }
+ [Attr]
+ public ulong? Attribute03 { get; set; }
- [Attr]
- public decimal Attribute04 { get; set; }
+ [Attr]
+ public decimal Attribute04 { get; set; }
- [Attr]
- public float? Attribute05 { get; set; }
+ [Attr]
+ public float? Attribute05 { get; set; }
- [Attr]
- public string Attribute06 { get; set; } = null!;
+ [Attr]
+ public string Attribute06 { get; set; } = null!;
- [Attr]
- public DateTime? Attribute07 { get; set; }
+ [Attr]
+ public DateTime? Attribute07 { get; set; }
- [Attr]
- public DateTimeOffset? Attribute08 { get; set; }
+ [Attr]
+ public DateTimeOffset? Attribute08 { get; set; }
- [Attr]
- public TimeSpan? Attribute09 { get; set; }
+ [Attr]
+ public TimeSpan? Attribute09 { get; set; }
- [Attr]
- public DayOfWeek Attribute10 { get; set; }
+ [Attr]
+ public DayOfWeek Attribute10 { get; set; }
- [HasOne]
- public IncomingResource Single1 { get; set; } = null!;
+ [HasOne]
+ public IncomingResource Single1 { get; set; } = null!;
- [HasOne]
- public IncomingResource Single2 { get; set; } = null!;
+ [HasOne]
+ public IncomingResource Single2 { get; set; } = null!;
- [HasOne]
- public IncomingResource Single3 { get; set; } = null!;
+ [HasOne]
+ public IncomingResource Single3 { get; set; } = null!;
- [HasOne]
- public IncomingResource Single4 { get; set; } = null!;
+ [HasOne]
+ public IncomingResource Single4 { get; set; } = null!;
- [HasOne]
- public IncomingResource Single5 { get; set; } = null!;
+ [HasOne]
+ public IncomingResource Single5 { get; set; } = null!;
- [HasMany]
- public ISet Multi1 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi1 { get; set; } = null!;
- [HasMany]
- public ISet Multi2 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi2 { get; set; } = null!;
- [HasMany]
- public ISet Multi3 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi3 { get; set; } = null!;
- [HasMany]
- public ISet Multi4 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi4 { get; set; } = null!;
- [HasMany]
- public ISet Multi5 { get; set; } = null!;
- }
+ [HasMany]
+ public ISet Multi5 { get; set; } = null!;
}
}
diff --git a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs
index 0181f4ccbc..d28684e27b 100644
--- a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs
+++ b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs
@@ -1,285 +1,283 @@
-using System;
using System.Text.Json;
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Middleware;
using JsonApiDotNetCore.Serialization.Objects;
-namespace Benchmarks.Deserialization
+namespace Benchmarks.Deserialization;
+
+[MarkdownExporter]
+// ReSharper disable once ClassCanBeSealed.Global
+public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
- [MarkdownExporter]
- // ReSharper disable once ClassCanBeSealed.Global
- public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
+ private static readonly string RequestBody = JsonSerializer.Serialize(new
{
- private static readonly string RequestBody = JsonSerializer.Serialize(new
+ atomic__operations = new object[]
{
- atomic__operations = new object[]
+ new
{
- new
+ op = "add",
+ data = new
{
- op = "add",
- data = new
+ type = "incomingResources",
+ lid = "a-1",
+ attributes = new
+ {
+ attribute01 = true,
+ attribute02 = 'A',
+ attribute03 = 100UL,
+ attribute04 = 100.001m,
+ attribute05 = 200.002f,
+ attribute06 = "text",
+ attribute07 = DateTime.MaxValue,
+ attribute08 = DateTimeOffset.MaxValue,
+ attribute09 = TimeSpan.MaxValue,
+ attribute10 = DayOfWeek.Friday
+ },
+ relationships = new
{
- type = "incomingResources",
- lid = "a-1",
- attributes = new
+ single1 = new
{
- attribute01 = true,
- attribute02 = 'A',
- attribute03 = 100UL,
- attribute04 = 100.001m,
- attribute05 = 200.002f,
- attribute06 = "text",
- attribute07 = DateTime.MaxValue,
- attribute08 = DateTimeOffset.MaxValue,
- attribute09 = TimeSpan.MaxValue,
- attribute10 = DayOfWeek.Friday
+ data = new
+ {
+ type = "incomingResources",
+ id = "101"
+ }
},
- relationships = new
+ single2 = new
{
- single1 = new
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "101"
- }
- },
- single2 = new
+ type = "incomingResources",
+ id = "102"
+ }
+ },
+ single3 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "102"
- }
- },
- single3 = new
+ type = "incomingResources",
+ id = "103"
+ }
+ },
+ single4 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "103"
- }
- },
- single4 = new
+ type = "incomingResources",
+ id = "104"
+ }
+ },
+ single5 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "104"
- }
- },
- single5 = new
+ type = "incomingResources",
+ id = "105"
+ }
+ },
+ multi1 = new
+ {
+ data = new[]
{
- data = new
+ new
{
type = "incomingResources",
- id = "105"
- }
- },
- multi1 = new
- {
- data = new[]
- {
- new
- {
- type = "incomingResources",
- id = "201"
- }
+ id = "201"
}
- },
- multi2 = new
+ }
+ },
+ multi2 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "202"
- }
+ type = "incomingResources",
+ id = "202"
}
- },
- multi3 = new
+ }
+ },
+ multi3 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "203"
- }
+ type = "incomingResources",
+ id = "203"
}
- },
- multi4 = new
+ }
+ },
+ multi4 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "204"
- }
+ type = "incomingResources",
+ id = "204"
}
- },
- multi5 = new
+ }
+ },
+ multi5 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "205"
- }
+ type = "incomingResources",
+ id = "205"
}
}
}
}
- },
- new
+ }
+ },
+ new
+ {
+ op = "update",
+ data = new
{
- op = "update",
- data = new
+ type = "incomingResources",
+ id = "1",
+ attributes = new
+ {
+ attribute01 = true,
+ attribute02 = 'A',
+ attribute03 = 100UL,
+ attribute04 = 100.001m,
+ attribute05 = 200.002f,
+ attribute06 = "text",
+ attribute07 = DateTime.MaxValue,
+ attribute08 = DateTimeOffset.MaxValue,
+ attribute09 = TimeSpan.MaxValue,
+ attribute10 = DayOfWeek.Friday
+ },
+ relationships = new
{
- type = "incomingResources",
- id = "1",
- attributes = new
+ single1 = new
{
- attribute01 = true,
- attribute02 = 'A',
- attribute03 = 100UL,
- attribute04 = 100.001m,
- attribute05 = 200.002f,
- attribute06 = "text",
- attribute07 = DateTime.MaxValue,
- attribute08 = DateTimeOffset.MaxValue,
- attribute09 = TimeSpan.MaxValue,
- attribute10 = DayOfWeek.Friday
+ data = new
+ {
+ type = "incomingResources",
+ id = "101"
+ }
},
- relationships = new
+ single2 = new
{
- single1 = new
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "101"
- }
- },
- single2 = new
+ type = "incomingResources",
+ id = "102"
+ }
+ },
+ single3 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "102"
- }
- },
- single3 = new
+ type = "incomingResources",
+ id = "103"
+ }
+ },
+ single4 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "103"
- }
- },
- single4 = new
+ type = "incomingResources",
+ id = "104"
+ }
+ },
+ single5 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "104"
- }
- },
- single5 = new
+ type = "incomingResources",
+ id = "105"
+ }
+ },
+ multi1 = new
+ {
+ data = new[]
{
- data = new
+ new
{
type = "incomingResources",
- id = "105"
- }
- },
- multi1 = new
- {
- data = new[]
- {
- new
- {
- type = "incomingResources",
- id = "201"
- }
+ id = "201"
}
- },
- multi2 = new
+ }
+ },
+ multi2 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "202"
- }
+ type = "incomingResources",
+ id = "202"
}
- },
- multi3 = new
+ }
+ },
+ multi3 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "203"
- }
+ type = "incomingResources",
+ id = "203"
}
- },
- multi4 = new
+ }
+ },
+ multi4 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "204"
- }
+ type = "incomingResources",
+ id = "204"
}
- },
- multi5 = new
+ }
+ },
+ multi5 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "205"
- }
+ type = "incomingResources",
+ id = "205"
}
}
}
}
- },
- new
+ }
+ },
+ new
+ {
+ op = "remove",
+ @ref = new
{
- op = "remove",
- @ref = new
- {
- type = "incomingResources",
- lid = "a-1"
- }
+ type = "incomingResources",
+ lid = "a-1"
}
}
- }).Replace("atomic__operations", "atomic:operations");
-
- [Benchmark]
- public object? DeserializeOperationsRequest()
- {
- var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions)!;
- return DocumentAdapter.Convert(document);
}
+ }).Replace("atomic__operations", "atomic:operations");
- protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
+ [Benchmark]
+ public object? DeserializeOperationsRequest()
+ {
+ var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions)!;
+ return DocumentAdapter.Convert(document);
+ }
+
+ protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
+ {
+ return new JsonApiRequest
{
- return new JsonApiRequest
- {
- Kind = EndpointKind.AtomicOperations
- };
- }
+ Kind = EndpointKind.AtomicOperations
+ };
}
}
diff --git a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs
index e154306819..23a6205bf5 100644
--- a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs
+++ b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs
@@ -1,150 +1,148 @@
-using System;
using System.Text.Json;
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Middleware;
using JsonApiDotNetCore.Serialization.Objects;
-namespace Benchmarks.Deserialization
+namespace Benchmarks.Deserialization;
+
+[MarkdownExporter]
+// ReSharper disable once ClassCanBeSealed.Global
+public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
{
- [MarkdownExporter]
- // ReSharper disable once ClassCanBeSealed.Global
- public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
+ private static readonly string RequestBody = JsonSerializer.Serialize(new
{
- private static readonly string RequestBody = JsonSerializer.Serialize(new
+ data = new
{
- data = new
+ type = "incomingResources",
+ attributes = new
+ {
+ attribute01 = true,
+ attribute02 = 'A',
+ attribute03 = 100UL,
+ attribute04 = 100.001m,
+ attribute05 = 200.002f,
+ attribute06 = "text",
+ attribute07 = DateTime.MaxValue,
+ attribute08 = DateTimeOffset.MaxValue,
+ attribute09 = TimeSpan.MaxValue,
+ attribute10 = DayOfWeek.Friday
+ },
+ relationships = new
{
- type = "incomingResources",
- attributes = new
+ single1 = new
{
- attribute01 = true,
- attribute02 = 'A',
- attribute03 = 100UL,
- attribute04 = 100.001m,
- attribute05 = 200.002f,
- attribute06 = "text",
- attribute07 = DateTime.MaxValue,
- attribute08 = DateTimeOffset.MaxValue,
- attribute09 = TimeSpan.MaxValue,
- attribute10 = DayOfWeek.Friday
+ data = new
+ {
+ type = "incomingResources",
+ id = "101"
+ }
},
- relationships = new
+ single2 = new
{
- single1 = new
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "101"
- }
- },
- single2 = new
+ type = "incomingResources",
+ id = "102"
+ }
+ },
+ single3 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "102"
- }
- },
- single3 = new
+ type = "incomingResources",
+ id = "103"
+ }
+ },
+ single4 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "103"
- }
- },
- single4 = new
+ type = "incomingResources",
+ id = "104"
+ }
+ },
+ single5 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "104"
- }
- },
- single5 = new
+ type = "incomingResources",
+ id = "105"
+ }
+ },
+ multi1 = new
+ {
+ data = new[]
{
- data = new
+ new
{
type = "incomingResources",
- id = "105"
+ id = "201"
}
- },
- multi1 = new
- {
- data = new[]
- {
- new
- {
- type = "incomingResources",
- id = "201"
- }
- }
- },
- multi2 = new
+ }
+ },
+ multi2 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "202"
- }
+ type = "incomingResources",
+ id = "202"
}
- },
- multi3 = new
+ }
+ },
+ multi3 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "203"
- }
+ type = "incomingResources",
+ id = "203"
}
- },
- multi4 = new
+ }
+ },
+ multi4 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "204"
- }
+ type = "incomingResources",
+ id = "204"
}
- },
- multi5 = new
+ }
+ },
+ multi5 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "205"
- }
+ type = "incomingResources",
+ id = "205"
}
}
}
}
- });
-
- [Benchmark]
- public object? DeserializeResourceRequest()
- {
- var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions)!;
- return DocumentAdapter.Convert(document);
}
+ });
- protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
+ [Benchmark]
+ public object? DeserializeResourceRequest()
+ {
+ var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions)!;
+ return DocumentAdapter.Convert(document);
+ }
+
+ protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
+ {
+ return new JsonApiRequest
{
- return new JsonApiRequest
- {
- Kind = EndpointKind.Primary,
- PrimaryResourceType = resourceGraph.GetResourceType(),
- WriteOperation = WriteOperationKind.CreateResource
- };
- }
+ Kind = EndpointKind.Primary,
+ PrimaryResourceType = resourceGraph.GetResourceType(),
+ WriteOperation = WriteOperationKind.CreateResource
+ };
}
}
diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs
index 45406133dd..818b9ab5e5 100644
--- a/benchmarks/Program.cs
+++ b/benchmarks/Program.cs
@@ -3,22 +3,21 @@
using Benchmarks.QueryString;
using Benchmarks.Serialization;
-namespace Benchmarks
+namespace Benchmarks;
+
+internal static class Program
{
- internal static class Program
+ private static void Main(string[] args)
{
- private static void Main(string[] args)
+ var switcher = new BenchmarkSwitcher(new[]
{
- var switcher = new BenchmarkSwitcher(new[]
- {
- typeof(ResourceDeserializationBenchmarks),
- typeof(OperationsDeserializationBenchmarks),
- typeof(ResourceSerializationBenchmarks),
- typeof(OperationsSerializationBenchmarks),
- typeof(QueryStringParserBenchmarks)
- });
+ typeof(ResourceDeserializationBenchmarks),
+ typeof(OperationsDeserializationBenchmarks),
+ typeof(ResourceSerializationBenchmarks),
+ typeof(OperationsSerializationBenchmarks),
+ typeof(QueryStringParserBenchmarks)
+ });
- switcher.Run(args);
- }
+ switcher.Run(args);
}
}
diff --git a/benchmarks/QueryString/QueryStringParserBenchmarks.cs b/benchmarks/QueryString/QueryStringParserBenchmarks.cs
index 42d34f8ce4..efa4f12659 100644
--- a/benchmarks/QueryString/QueryStringParserBenchmarks.cs
+++ b/benchmarks/QueryString/QueryStringParserBenchmarks.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.Design;
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore;
@@ -11,94 +10,92 @@
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging.Abstractions;
-namespace Benchmarks.QueryString
+namespace Benchmarks.QueryString;
+
+// ReSharper disable once ClassCanBeSealed.Global
+[MarkdownExporter]
+[SimpleJob(3, 10, 20)]
+[MemoryDiagnoser]
+public class QueryStringParserBenchmarks
{
- // ReSharper disable once ClassCanBeSealed.Global
- [MarkdownExporter]
- [SimpleJob(3, 10, 20)]
- [MemoryDiagnoser]
- public class QueryStringParserBenchmarks
+ private readonly FakeRequestQueryStringAccessor _queryStringAccessor = new();
+ private readonly QueryStringReader _queryStringReader;
+
+ public QueryStringParserBenchmarks()
{
- private readonly FakeRequestQueryStringAccessor _queryStringAccessor = new();
- private readonly QueryStringReader _queryStringReader;
+ IJsonApiOptions options = new JsonApiOptions
+ {
+ EnableLegacyFilterNotation = true
+ };
- public QueryStringParserBenchmarks()
+ IResourceGraph resourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add("alt-resource-name").Build();
+
+ var request = new JsonApiRequest
{
- IJsonApiOptions options = new JsonApiOptions
- {
- EnableLegacyFilterNotation = true
- };
+ PrimaryResourceType = resourceGraph.GetResourceType(typeof(QueryableResource)),
+ IsCollection = true
+ };
- IResourceGraph resourceGraph =
- new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add("alt-resource-name").Build();
+ var resourceFactory = new ResourceFactory(new ServiceContainer());
- var request = new JsonApiRequest
- {
- PrimaryResourceType = resourceGraph.GetResourceType(typeof(QueryableResource)),
- IsCollection = true
- };
+ var includeReader = new IncludeQueryStringParameterReader(request, resourceGraph, options);
+ var filterReader = new FilterQueryStringParameterReader(request, resourceGraph, resourceFactory, options);
+ var sortReader = new SortQueryStringParameterReader(request, resourceGraph);
+ var sparseFieldSetReader = new SparseFieldSetQueryStringParameterReader(request, resourceGraph);
+ var paginationReader = new PaginationQueryStringParameterReader(request, resourceGraph, options);
- var resourceFactory = new ResourceFactory(new ServiceContainer());
+ IQueryStringParameterReader[] readers = ArrayFactory.Create(includeReader, filterReader, sortReader,
+ sparseFieldSetReader, paginationReader);
- var includeReader = new IncludeQueryStringParameterReader(request, resourceGraph, options);
- var filterReader = new FilterQueryStringParameterReader(request, resourceGraph, resourceFactory, options);
- var sortReader = new SortQueryStringParameterReader(request, resourceGraph);
- var sparseFieldSetReader = new SparseFieldSetQueryStringParameterReader(request, resourceGraph);
- var paginationReader = new PaginationQueryStringParameterReader(request, resourceGraph, options);
+ _queryStringReader = new QueryStringReader(options, _queryStringAccessor, readers, NullLoggerFactory.Instance);
+ }
- IQueryStringParameterReader[] readers = ArrayFactory.Create(includeReader, filterReader, sortReader,
- sparseFieldSetReader, paginationReader);
+ [Benchmark]
+ public void AscendingSort()
+ {
+ const string queryString = "?sort=alt-attr-name";
- _queryStringReader = new QueryStringReader(options, _queryStringAccessor, readers, NullLoggerFactory.Instance);
- }
+ _queryStringAccessor.SetQueryString(queryString);
+ _queryStringReader.ReadAll(null);
+ }
- [Benchmark]
- public void AscendingSort()
- {
- const string queryString = "?sort=alt-attr-name";
+ [Benchmark]
+ public void DescendingSort()
+ {
+ const string queryString = "?sort=-alt-attr-name";
- _queryStringAccessor.SetQueryString(queryString);
- _queryStringReader.ReadAll(null);
- }
+ _queryStringAccessor.SetQueryString(queryString);
+ _queryStringReader.ReadAll(null);
+ }
- [Benchmark]
- public void DescendingSort()
+ [Benchmark]
+ public void ComplexQuery()
+ {
+ Run(100, () =>
{
- const string queryString = "?sort=-alt-attr-name";
+ const string queryString =
+ "?filter[alt-attr-name]=abc,eq:abc&sort=-alt-attr-name&include=child&page[size]=1&fields[alt-resource-name]=alt-attr-name";
_queryStringAccessor.SetQueryString(queryString);
_queryStringReader.ReadAll(null);
- }
+ });
+ }
- [Benchmark]
- public void ComplexQuery()
+ private void Run(int iterations, Action action)
+ {
+ for (int index = 0; index < iterations; index++)
{
- Run(100, () =>
- {
- const string queryString =
- "?filter[alt-attr-name]=abc,eq:abc&sort=-alt-attr-name&include=child&page[size]=1&fields[alt-resource-name]=alt-attr-name";
-
- _queryStringAccessor.SetQueryString(queryString);
- _queryStringReader.ReadAll(null);
- });
+ action();
}
+ }
- private void Run(int iterations, Action action)
- {
- for (int index = 0; index < iterations; index++)
- {
- action();
- }
- }
+ private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor
+ {
+ public IQueryCollection Query { get; private set; } = new QueryCollection();
- private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor
+ public void SetQueryString(string queryString)
{
- public IQueryCollection Query { get; private set; } = new QueryCollection();
-
- public void SetQueryString(string queryString)
- {
- Query = new QueryCollection(QueryHelpers.ParseQuery(queryString));
- }
+ Query = new QueryCollection(QueryHelpers.ParseQuery(queryString));
}
}
}
diff --git a/benchmarks/QueryString/QueryableResource.cs b/benchmarks/QueryString/QueryableResource.cs
index bcf0a5075a..7c26474ae4 100644
--- a/benchmarks/QueryString/QueryableResource.cs
+++ b/benchmarks/QueryString/QueryableResource.cs
@@ -2,15 +2,14 @@
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
-namespace Benchmarks.QueryString
+namespace Benchmarks.QueryString;
+
+[UsedImplicitly(ImplicitUseTargetFlags.Members)]
+public sealed class QueryableResource : Identifiable
{
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public sealed class QueryableResource : Identifiable
- {
- [Attr(PublicName = "alt-attr-name")]
- public string? Name { get; set; }
+ [Attr(PublicName = "alt-attr-name")]
+ public string? Name { get; set; }
- [HasOne]
- public QueryableResource? Child { get; set; }
- }
+ [HasOne]
+ public QueryableResource? Child { get; set; }
}
diff --git a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs
index fef0d67a12..7076ca5cb8 100644
--- a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs
+++ b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs
@@ -1,5 +1,3 @@
-using System;
-using System.Collections.Generic;
using System.Text.Json;
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore.Configuration;
@@ -8,130 +6,129 @@
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Serialization.Objects;
-namespace Benchmarks.Serialization
+namespace Benchmarks.Serialization;
+
+[MarkdownExporter]
+// ReSharper disable once ClassCanBeSealed.Global
+public class OperationsSerializationBenchmarks : SerializationBenchmarkBase
{
- [MarkdownExporter]
- // ReSharper disable once ClassCanBeSealed.Global
- public class OperationsSerializationBenchmarks : SerializationBenchmarkBase
- {
- private readonly IEnumerable _responseOperations;
+ private readonly IEnumerable _responseOperations;
- public OperationsSerializationBenchmarks()
- {
- // ReSharper disable once VirtualMemberCallInConstructor
- JsonApiRequest request = CreateJsonApiRequest(ResourceGraph);
+ public OperationsSerializationBenchmarks()
+ {
+ // ReSharper disable once VirtualMemberCallInConstructor
+ JsonApiRequest request = CreateJsonApiRequest(ResourceGraph);
- _responseOperations = CreateResponseOperations(request);
- }
+ _responseOperations = CreateResponseOperations(request);
+ }
- private static IEnumerable CreateResponseOperations(IJsonApiRequest request)
+ private static IEnumerable CreateResponseOperations(IJsonApiRequest request)
+ {
+ var resource1 = new OutgoingResource
{
- var resource1 = new OutgoingResource
- {
- Id = 1,
- Attribute01 = true,
- Attribute02 = 'A',
- Attribute03 = 100UL,
- Attribute04 = 100.001m,
- Attribute05 = 100.002f,
- Attribute06 = "text1",
- Attribute07 = new DateTime(2001, 1, 1),
- Attribute08 = new DateTimeOffset(2001, 1, 1, 0, 0, 0, TimeSpan.FromHours(1)),
- Attribute09 = new TimeSpan(1, 0, 0),
- Attribute10 = DayOfWeek.Sunday
- };
-
- var resource2 = new OutgoingResource
- {
- Id = 2,
- Attribute01 = false,
- Attribute02 = 'B',
- Attribute03 = 200UL,
- Attribute04 = 200.001m,
- Attribute05 = 200.002f,
- Attribute06 = "text2",
- Attribute07 = new DateTime(2002, 2, 2),
- Attribute08 = new DateTimeOffset(2002, 2, 2, 0, 0, 0, TimeSpan.FromHours(2)),
- Attribute09 = new TimeSpan(2, 0, 0),
- Attribute10 = DayOfWeek.Monday
- };
+ Id = 1,
+ Attribute01 = true,
+ Attribute02 = 'A',
+ Attribute03 = 100UL,
+ Attribute04 = 100.001m,
+ Attribute05 = 100.002f,
+ Attribute06 = "text1",
+ Attribute07 = new DateTime(2001, 1, 1),
+ Attribute08 = new DateTimeOffset(2001, 1, 1, 0, 0, 0, TimeSpan.FromHours(1)),
+ Attribute09 = new TimeSpan(1, 0, 0),
+ Attribute10 = DayOfWeek.Sunday
+ };
- var resource3 = new OutgoingResource
- {
- Id = 3,
- Attribute01 = true,
- Attribute02 = 'C',
- Attribute03 = 300UL,
- Attribute04 = 300.001m,
- Attribute05 = 300.002f,
- Attribute06 = "text3",
- Attribute07 = new DateTime(2003, 3, 3),
- Attribute08 = new DateTimeOffset(2003, 3, 3, 0, 0, 0, TimeSpan.FromHours(3)),
- Attribute09 = new TimeSpan(3, 0, 0),
- Attribute10 = DayOfWeek.Tuesday
- };
+ var resource2 = new OutgoingResource
+ {
+ Id = 2,
+ Attribute01 = false,
+ Attribute02 = 'B',
+ Attribute03 = 200UL,
+ Attribute04 = 200.001m,
+ Attribute05 = 200.002f,
+ Attribute06 = "text2",
+ Attribute07 = new DateTime(2002, 2, 2),
+ Attribute08 = new DateTimeOffset(2002, 2, 2, 0, 0, 0, TimeSpan.FromHours(2)),
+ Attribute09 = new TimeSpan(2, 0, 0),
+ Attribute10 = DayOfWeek.Monday
+ };
- var resource4 = new OutgoingResource
- {
- Id = 4,
- Attribute01 = false,
- Attribute02 = 'D',
- Attribute03 = 400UL,
- Attribute04 = 400.001m,
- Attribute05 = 400.002f,
- Attribute06 = "text4",
- Attribute07 = new DateTime(2004, 4, 4),
- Attribute08 = new DateTimeOffset(2004, 4, 4, 0, 0, 0, TimeSpan.FromHours(4)),
- Attribute09 = new TimeSpan(4, 0, 0),
- Attribute10 = DayOfWeek.Wednesday
- };
+ var resource3 = new OutgoingResource
+ {
+ Id = 3,
+ Attribute01 = true,
+ Attribute02 = 'C',
+ Attribute03 = 300UL,
+ Attribute04 = 300.001m,
+ Attribute05 = 300.002f,
+ Attribute06 = "text3",
+ Attribute07 = new DateTime(2003, 3, 3),
+ Attribute08 = new DateTimeOffset(2003, 3, 3, 0, 0, 0, TimeSpan.FromHours(3)),
+ Attribute09 = new TimeSpan(3, 0, 0),
+ Attribute10 = DayOfWeek.Tuesday
+ };
- var resource5 = new OutgoingResource
- {
- Id = 5,
- Attribute01 = true,
- Attribute02 = 'E',
- Attribute03 = 500UL,
- Attribute04 = 500.001m,
- Attribute05 = 500.002f,
- Attribute06 = "text5",
- Attribute07 = new DateTime(2005, 5, 5),
- Attribute08 = new DateTimeOffset(2005, 5, 5, 0, 0, 0, TimeSpan.FromHours(5)),
- Attribute09 = new TimeSpan(5, 0, 0),
- Attribute10 = DayOfWeek.Thursday
- };
+ var resource4 = new OutgoingResource
+ {
+ Id = 4,
+ Attribute01 = false,
+ Attribute02 = 'D',
+ Attribute03 = 400UL,
+ Attribute04 = 400.001m,
+ Attribute05 = 400.002f,
+ Attribute06 = "text4",
+ Attribute07 = new DateTime(2004, 4, 4),
+ Attribute08 = new DateTimeOffset(2004, 4, 4, 0, 0, 0, TimeSpan.FromHours(4)),
+ Attribute09 = new TimeSpan(4, 0, 0),
+ Attribute10 = DayOfWeek.Wednesday
+ };
- var targetedFields = new TargetedFields();
+ var resource5 = new OutgoingResource
+ {
+ Id = 5,
+ Attribute01 = true,
+ Attribute02 = 'E',
+ Attribute03 = 500UL,
+ Attribute04 = 500.001m,
+ Attribute05 = 500.002f,
+ Attribute06 = "text5",
+ Attribute07 = new DateTime(2005, 5, 5),
+ Attribute08 = new DateTimeOffset(2005, 5, 5, 0, 0, 0, TimeSpan.FromHours(5)),
+ Attribute09 = new TimeSpan(5, 0, 0),
+ Attribute10 = DayOfWeek.Thursday
+ };
- return new List
- {
- new(resource1, targetedFields, request),
- new(resource2, targetedFields, request),
- new(resource3, targetedFields, request),
- new(resource4, targetedFields, request),
- new(resource5, targetedFields, request)
- };
- }
+ var targetedFields = new TargetedFields();
- [Benchmark]
- public string SerializeOperationsResponse()
+ return new List
{
- Document responseDocument = ResponseModelAdapter.Convert(_responseOperations);
- return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions);
- }
+ new(resource1, targetedFields, request),
+ new(resource2, targetedFields, request),
+ new(resource3, targetedFields, request),
+ new(resource4, targetedFields, request),
+ new(resource5, targetedFields, request)
+ };
+ }
- protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
- {
- return new JsonApiRequest
- {
- Kind = EndpointKind.AtomicOperations,
- PrimaryResourceType = resourceGraph.GetResourceType()
- };
- }
+ [Benchmark]
+ public string SerializeOperationsResponse()
+ {
+ Document responseDocument = ResponseModelAdapter.Convert(_responseOperations);
+ return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions);
+ }
- protected override IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceGraph resourceGraph)
+ protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
+ {
+ return new JsonApiRequest
{
- return new EvaluatedIncludeCache();
- }
+ Kind = EndpointKind.AtomicOperations,
+ PrimaryResourceType = resourceGraph.GetResourceType()
+ };
+ }
+
+ protected override IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceGraph resourceGraph)
+ {
+ return new EvaluatedIncludeCache();
}
}
diff --git a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs
index 3435265262..6b716a5401 100644
--- a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs
+++ b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs
@@ -1,5 +1,3 @@
-using System;
-using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text.Json;
using BenchmarkDotNet.Attributes;
@@ -11,133 +9,132 @@
using JsonApiDotNetCore.Resources.Annotations;
using JsonApiDotNetCore.Serialization.Objects;
-namespace Benchmarks.Serialization
+namespace Benchmarks.Serialization;
+
+[MarkdownExporter]
+// ReSharper disable once ClassCanBeSealed.Global
+public class ResourceSerializationBenchmarks : SerializationBenchmarkBase
{
- [MarkdownExporter]
- // ReSharper disable once ClassCanBeSealed.Global
- public class ResourceSerializationBenchmarks : SerializationBenchmarkBase
+ private static readonly OutgoingResource ResponseResource = CreateResponseResource();
+
+ private static OutgoingResource CreateResponseResource()
{
- private static readonly OutgoingResource ResponseResource = CreateResponseResource();
+ var resource1 = new OutgoingResource
+ {
+ Id = 1,
+ Attribute01 = true,
+ Attribute02 = 'A',
+ Attribute03 = 100UL,
+ Attribute04 = 100.001m,
+ Attribute05 = 100.002f,
+ Attribute06 = "text1",
+ Attribute07 = new DateTime(2001, 1, 1),
+ Attribute08 = new DateTimeOffset(2001, 1, 1, 0, 0, 0, TimeSpan.FromHours(1)),
+ Attribute09 = new TimeSpan(1, 0, 0),
+ Attribute10 = DayOfWeek.Sunday
+ };
- private static OutgoingResource CreateResponseResource()
+ var resource2 = new OutgoingResource
{
- var resource1 = new OutgoingResource
- {
- Id = 1,
- Attribute01 = true,
- Attribute02 = 'A',
- Attribute03 = 100UL,
- Attribute04 = 100.001m,
- Attribute05 = 100.002f,
- Attribute06 = "text1",
- Attribute07 = new DateTime(2001, 1, 1),
- Attribute08 = new DateTimeOffset(2001, 1, 1, 0, 0, 0, TimeSpan.FromHours(1)),
- Attribute09 = new TimeSpan(1, 0, 0),
- Attribute10 = DayOfWeek.Sunday
- };
-
- var resource2 = new OutgoingResource
- {
- Id = 2,
- Attribute01 = false,
- Attribute02 = 'B',
- Attribute03 = 200UL,
- Attribute04 = 200.001m,
- Attribute05 = 200.002f,
- Attribute06 = "text2",
- Attribute07 = new DateTime(2002, 2, 2),
- Attribute08 = new DateTimeOffset(2002, 2, 2, 0, 0, 0, TimeSpan.FromHours(2)),
- Attribute09 = new TimeSpan(2, 0, 0),
- Attribute10 = DayOfWeek.Monday
- };
-
- var resource3 = new OutgoingResource
- {
- Id = 3,
- Attribute01 = true,
- Attribute02 = 'C',
- Attribute03 = 300UL,
- Attribute04 = 300.001m,
- Attribute05 = 300.002f,
- Attribute06 = "text3",
- Attribute07 = new DateTime(2003, 3, 3),
- Attribute08 = new DateTimeOffset(2003, 3, 3, 0, 0, 0, TimeSpan.FromHours(3)),
- Attribute09 = new TimeSpan(3, 0, 0),
- Attribute10 = DayOfWeek.Tuesday
- };
-
- var resource4 = new OutgoingResource
- {
- Id = 4,
- Attribute01 = false,
- Attribute02 = 'D',
- Attribute03 = 400UL,
- Attribute04 = 400.001m,
- Attribute05 = 400.002f,
- Attribute06 = "text4",
- Attribute07 = new DateTime(2004, 4, 4),
- Attribute08 = new DateTimeOffset(2004, 4, 4, 0, 0, 0, TimeSpan.FromHours(4)),
- Attribute09 = new TimeSpan(4, 0, 0),
- Attribute10 = DayOfWeek.Wednesday
- };
-
- var resource5 = new OutgoingResource
- {
- Id = 5,
- Attribute01 = true,
- Attribute02 = 'E',
- Attribute03 = 500UL,
- Attribute04 = 500.001m,
- Attribute05 = 500.002f,
- Attribute06 = "text5",
- Attribute07 = new DateTime(2005, 5, 5),
- Attribute08 = new DateTimeOffset(2005, 5, 5, 0, 0, 0, TimeSpan.FromHours(5)),
- Attribute09 = new TimeSpan(5, 0, 0),
- Attribute10 = DayOfWeek.Thursday
- };
-
- resource1.Single2 = resource2;
- resource2.Single3 = resource3;
- resource3.Multi4 = resource4.AsHashSet();
- resource4.Multi5 = resource5.AsHashSet();
-
- return resource1;
- }
-
- [Benchmark]
- public string SerializeResourceResponse()
+ Id = 2,
+ Attribute01 = false,
+ Attribute02 = 'B',
+ Attribute03 = 200UL,
+ Attribute04 = 200.001m,
+ Attribute05 = 200.002f,
+ Attribute06 = "text2",
+ Attribute07 = new DateTime(2002, 2, 2),
+ Attribute08 = new DateTimeOffset(2002, 2, 2, 0, 0, 0, TimeSpan.FromHours(2)),
+ Attribute09 = new TimeSpan(2, 0, 0),
+ Attribute10 = DayOfWeek.Monday
+ };
+
+ var resource3 = new OutgoingResource
{
- Document responseDocument = ResponseModelAdapter.Convert(ResponseResource);
- return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions);
- }
+ Id = 3,
+ Attribute01 = true,
+ Attribute02 = 'C',
+ Attribute03 = 300UL,
+ Attribute04 = 300.001m,
+ Attribute05 = 300.002f,
+ Attribute06 = "text3",
+ Attribute07 = new DateTime(2003, 3, 3),
+ Attribute08 = new DateTimeOffset(2003, 3, 3, 0, 0, 0, TimeSpan.FromHours(3)),
+ Attribute09 = new TimeSpan(3, 0, 0),
+ Attribute10 = DayOfWeek.Tuesday
+ };
- protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
+ var resource4 = new OutgoingResource
{
- return new JsonApiRequest
- {
- Kind = EndpointKind.Primary,
- PrimaryResourceType = resourceGraph.GetResourceType()
- };
- }
-
- protected override IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceGraph resourceGraph)
+ Id = 4,
+ Attribute01 = false,
+ Attribute02 = 'D',
+ Attribute03 = 400UL,
+ Attribute04 = 400.001m,
+ Attribute05 = 400.002f,
+ Attribute06 = "text4",
+ Attribute07 = new DateTime(2004, 4, 4),
+ Attribute08 = new DateTimeOffset(2004, 4, 4, 0, 0, 0, TimeSpan.FromHours(4)),
+ Attribute09 = new TimeSpan(4, 0, 0),
+ Attribute10 = DayOfWeek.Wednesday
+ };
+
+ var resource5 = new OutgoingResource
{
- ResourceType resourceAType = resourceGraph.GetResourceType();
+ Id = 5,
+ Attribute01 = true,
+ Attribute02 = 'E',
+ Attribute03 = 500UL,
+ Attribute04 = 500.001m,
+ Attribute05 = 500.002f,
+ Attribute06 = "text5",
+ Attribute07 = new DateTime(2005, 5, 5),
+ Attribute08 = new DateTimeOffset(2005, 5, 5, 0, 0, 0, TimeSpan.FromHours(5)),
+ Attribute09 = new TimeSpan(5, 0, 0),
+ Attribute10 = DayOfWeek.Thursday
+ };
+
+ resource1.Single2 = resource2;
+ resource2.Single3 = resource3;
+ resource3.Multi4 = resource4.AsHashSet();
+ resource4.Multi5 = resource5.AsHashSet();
+
+ return resource1;
+ }
+
+ [Benchmark]
+ public string SerializeResourceResponse()
+ {
+ Document responseDocument = ResponseModelAdapter.Convert(ResponseResource);
+ return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions);
+ }
+
+ protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
+ {
+ return new JsonApiRequest
+ {
+ Kind = EndpointKind.Primary,
+ PrimaryResourceType = resourceGraph.GetResourceType()
+ };
+ }
+
+ protected override IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceGraph resourceGraph)
+ {
+ ResourceType resourceAType = resourceGraph.GetResourceType();
- RelationshipAttribute single2 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Single2));
- RelationshipAttribute single3 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Single3));
- RelationshipAttribute multi4 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Multi4));
- RelationshipAttribute multi5 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Multi5));
+ RelationshipAttribute single2 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Single2));
+ RelationshipAttribute single3 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Single3));
+ RelationshipAttribute multi4 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Multi4));
+ RelationshipAttribute multi5 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Multi5));
- ImmutableArray chain = ImmutableArray.Create(single2, single3, multi4, multi5);
- IEnumerable chains = new ResourceFieldChainExpression(chain).AsEnumerable();
+ ImmutableArray chain = ImmutableArray.Create(single2, single3, multi4, multi5);
+ IEnumerable chains = new ResourceFieldChainExpression(chain).AsEnumerable();
- var converter = new IncludeChainConverter();
- IncludeExpression include = converter.FromRelationshipChains(chains);
+ var converter = new IncludeChainConverter();
+ IncludeExpression include = converter.FromRelationshipChains(chains);
- var cache = new EvaluatedIncludeCache();
- cache.Set(include);
- return cache;
- }
+ var cache = new EvaluatedIncludeCache();
+ cache.Set(include);
+ return cache;
}
}
diff --git a/benchmarks/Serialization/SerializationBenchmarkBase.cs b/benchmarks/Serialization/SerializationBenchmarkBase.cs
index 84d28c22ab..befa5049e8 100644
--- a/benchmarks/Serialization/SerializationBenchmarkBase.cs
+++ b/benchmarks/Serialization/SerializationBenchmarkBase.cs
@@ -1,10 +1,6 @@
-using System;
-using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text.Json;
using System.Text.Json.Serialization;
-using System.Threading;
-using System.Threading.Tasks;
using JetBrains.Annotations;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Middleware;
@@ -19,249 +15,248 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions;
-namespace Benchmarks.Serialization
+namespace Benchmarks.Serialization;
+
+public abstract class SerializationBenchmarkBase
{
- public abstract class SerializationBenchmarkBase
- {
- protected readonly JsonSerializerOptions SerializerWriteOptions;
- protected readonly IResponseModelAdapter ResponseModelAdapter;
- protected readonly IResourceGraph ResourceGraph;
+ protected readonly JsonSerializerOptions SerializerWriteOptions;
+ protected readonly IResponseModelAdapter ResponseModelAdapter;
+ protected readonly IResourceGraph ResourceGraph;
- protected SerializationBenchmarkBase()
+ protected SerializationBenchmarkBase()
+ {
+ var options = new JsonApiOptions
{
- var options = new JsonApiOptions
+ SerializerOptions =
{
- SerializerOptions =
+ Converters =
{
- Converters =
- {
- new JsonStringEnumConverter()
- }
+ new JsonStringEnumConverter()
}
- };
+ }
+ };
- ResourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build();
- SerializerWriteOptions = ((IJsonApiOptions)options).SerializerWriteOptions;
+ ResourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build();
+ SerializerWriteOptions = ((IJsonApiOptions)options).SerializerWriteOptions;
- // ReSharper disable VirtualMemberCallInConstructor
- JsonApiRequest request = CreateJsonApiRequest(ResourceGraph);
- IEvaluatedIncludeCache evaluatedIncludeCache = CreateEvaluatedIncludeCache(ResourceGraph);
- // ReSharper restore VirtualMemberCallInConstructor
+ // ReSharper disable VirtualMemberCallInConstructor
+ JsonApiRequest request = CreateJsonApiRequest(ResourceGraph);
+ IEvaluatedIncludeCache evaluatedIncludeCache = CreateEvaluatedIncludeCache(ResourceGraph);
+ // ReSharper restore VirtualMemberCallInConstructor
- var linkBuilder = new FakeLinkBuilder();
- var metaBuilder = new FakeMetaBuilder();
- IQueryConstraintProvider[] constraintProviders = Array.Empty();
- var resourceDefinitionAccessor = new FakeResourceDefinitionAccessor();
- var sparseFieldSetCache = new SparseFieldSetCache(constraintProviders, resourceDefinitionAccessor);
- var requestQueryStringAccessor = new FakeRequestQueryStringAccessor();
+ var linkBuilder = new FakeLinkBuilder();
+ var metaBuilder = new FakeMetaBuilder();
+ IQueryConstraintProvider[] constraintProviders = Array.Empty();
+ var resourceDefinitionAccessor = new FakeResourceDefinitionAccessor();
+ var sparseFieldSetCache = new SparseFieldSetCache(constraintProviders, resourceDefinitionAccessor);
+ var requestQueryStringAccessor = new FakeRequestQueryStringAccessor();
- ResponseModelAdapter = new ResponseModelAdapter(request, options, linkBuilder, metaBuilder, resourceDefinitionAccessor, evaluatedIncludeCache,
- sparseFieldSetCache, requestQueryStringAccessor);
- }
+ ResponseModelAdapter = new ResponseModelAdapter(request, options, linkBuilder, metaBuilder, resourceDefinitionAccessor, evaluatedIncludeCache,
+ sparseFieldSetCache, requestQueryStringAccessor);
+ }
- protected abstract JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph);
+ protected abstract JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph);
- protected abstract IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceGraph resourceGraph);
+ protected abstract IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceGraph resourceGraph);
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public sealed class OutgoingResource : Identifiable
- {
- [Attr]
- public bool Attribute01 { get; set; }
+ [UsedImplicitly(ImplicitUseTargetFlags.Members)]
+ public sealed class OutgoingResource : Identifiable
+ {
+ [Attr]
+ public bool Attribute01 { get; set; }
- [Attr]
- public char Attribute02 { get; set; }
+ [Attr]
+ public char Attribute02 { get; set; }
- [Attr]
- public ulong? Attribute03 { get; set; }
+ [Attr]
+ public ulong? Attribute03 { get; set; }
- [Attr]
- public decimal Attribute04 { get; set; }
+ [Attr]
+ public decimal Attribute04 { get; set; }
- [Attr]
- public float? Attribute05 { get; set; }
+ [Attr]
+ public float? Attribute05 { get; set; }
- [Attr]
- public string Attribute06 { get; set; } = null!;
+ [Attr]
+ public string Attribute06 { get; set; } = null!;
- [Attr]
- public DateTime? Attribute07 { get; set; }
+ [Attr]
+ public DateTime? Attribute07 { get; set; }
- [Attr]
- public DateTimeOffset? Attribute08 { get; set; }
+ [Attr]
+ public DateTimeOffset? Attribute08 { get; set; }
- [Attr]
- public TimeSpan? Attribute09 { get; set; }
+ [Attr]
+ public TimeSpan? Attribute09 { get; set; }
- [Attr]
- public DayOfWeek Attribute10 { get; set; }
+ [Attr]
+ public DayOfWeek Attribute10 { get; set; }
- [HasOne]
- public OutgoingResource Single1 { get; set; } = null!;
+ [HasOne]
+ public OutgoingResource Single1 { get; set; } = null!;
- [HasOne]
- public OutgoingResource Single2 { get; set; } = null!;
+ [HasOne]
+ public OutgoingResource Single2 { get; set; } = null!;
- [HasOne]
- public OutgoingResource Single3 { get; set; } = null!;
+ [HasOne]
+ public OutgoingResource Single3 { get; set; } = null!;
- [HasOne]
- public OutgoingResource Single4 { get; set; } = null!;
+ [HasOne]
+ public OutgoingResource Single4 { get; set; } = null!;
- [HasOne]
- public OutgoingResource Single5 { get; set; } = null!;
+ [HasOne]
+ public OutgoingResource Single5 { get; set; } = null!;
- [HasMany]
- public ISet Multi1 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi1 { get; set; } = null!;
- [HasMany]
- public ISet Multi2 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi2 { get; set; } = null!;
- [HasMany]
- public ISet Multi3 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi3 { get; set; } = null!;
- [HasMany]
- public ISet Multi4 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi4 { get; set; } = null!;
- [HasMany]
- public ISet Multi5 { get; set; } = null!;
- }
+ [HasMany]
+ public ISet Multi5 { get; set; } = null!;
+ }
- private sealed class FakeResourceDefinitionAccessor : IResourceDefinitionAccessor
+ private sealed class FakeResourceDefinitionAccessor : IResourceDefinitionAccessor
+ {
+ public IImmutableSet OnApplyIncludes(ResourceType resourceType, IImmutableSet existingIncludes)
{
- public IImmutableSet OnApplyIncludes(ResourceType resourceType, IImmutableSet existingIncludes)
- {
- return existingIncludes;
- }
-
- public FilterExpression? OnApplyFilter(ResourceType resourceType, FilterExpression? existingFilter)
- {
- return existingFilter;
- }
+ return existingIncludes;
+ }
- public SortExpression? OnApplySort(ResourceType resourceType, SortExpression? existingSort)
- {
- return existingSort;
- }
+ public FilterExpression? OnApplyFilter(ResourceType resourceType, FilterExpression? existingFilter)
+ {
+ return existingFilter;
+ }
- public PaginationExpression? OnApplyPagination(ResourceType resourceType, PaginationExpression? existingPagination)
- {
- return existingPagination;
- }
+ public SortExpression? OnApplySort(ResourceType resourceType, SortExpression? existingSort)
+ {
+ return existingSort;
+ }
- public SparseFieldSetExpression? OnApplySparseFieldSet(ResourceType resourceType, SparseFieldSetExpression? existingSparseFieldSet)
- {
- return existingSparseFieldSet;
- }
+ public PaginationExpression? OnApplyPagination(ResourceType resourceType, PaginationExpression? existingPagination)
+ {
+ return existingPagination;
+ }
- public object? GetQueryableHandlerForQueryStringParameter(Type resourceClrType, string parameterName)
- {
- return null;
- }
+ public SparseFieldSetExpression? OnApplySparseFieldSet(ResourceType resourceType, SparseFieldSetExpression? existingSparseFieldSet)
+ {
+ return existingSparseFieldSet;
+ }
- public IDictionary? GetMeta(ResourceType resourceType, IIdentifiable resourceInstance)
- {
- return null;
- }
+ public object? GetQueryableHandlerForQueryStringParameter(Type resourceClrType, string parameterName)
+ {
+ return null;
+ }
- public Task OnPrepareWriteAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.CompletedTask;
- }
+ public IDictionary? GetMeta(ResourceType resourceType, IIdentifiable resourceInstance)
+ {
+ return null;
+ }
- public Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship,
- IIdentifiable? rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.FromResult(rightResourceId);
- }
+ public Task OnPrepareWriteAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.CompletedTask;
+ }
- public Task OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship,
- ISet rightResourceIds, WriteOperationKind writeOperation, CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.CompletedTask;
- }
+ public Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship,
+ IIdentifiable? rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.FromResult(rightResourceId);
+ }
- public Task OnAddToRelationshipAsync(TId leftResourceId, HasManyAttribute hasManyRelationship, ISet rightResourceIds,
- CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.CompletedTask;
- }
+ public Task OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds,
+ WriteOperationKind writeOperation, CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.CompletedTask;
+ }
- public Task OnRemoveFromRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship,
- ISet rightResourceIds, CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.CompletedTask;
- }
+ public Task OnAddToRelationshipAsync(TId leftResourceId, HasManyAttribute hasManyRelationship, ISet rightResourceIds,
+ CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.CompletedTask;
+ }
- public Task OnWritingAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.CompletedTask;
- }
+ public Task OnRemoveFromRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds,
+ CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.CompletedTask;
+ }
- public Task OnWriteSucceededAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.CompletedTask;
- }
+ public Task OnWritingAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.CompletedTask;
+ }
- public void OnDeserialize(IIdentifiable resource)
- {
- }
+ public Task OnWriteSucceededAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.CompletedTask;
+ }
- public void OnSerialize(IIdentifiable resource)
- {
- }
+ public void OnDeserialize(IIdentifiable resource)
+ {
}
- private sealed class FakeLinkBuilder : ILinkBuilder
+ public void OnSerialize(IIdentifiable resource)
{
- public TopLevelLinks GetTopLevelLinks()
- {
- return new TopLevelLinks
- {
- Self = "TopLevel:Self"
- };
- }
+ }
+ }
- public ResourceLinks GetResourceLinks(ResourceType resourceType, IIdentifiable resource)
+ private sealed class FakeLinkBuilder : ILinkBuilder
+ {
+ public TopLevelLinks GetTopLevelLinks()
+ {
+ return new TopLevelLinks
{
- return new ResourceLinks
- {
- Self = "Resource:Self"
- };
- }
+ Self = "TopLevel:Self"
+ };
+ }
- public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable leftResource)
+ public ResourceLinks GetResourceLinks(ResourceType resourceType, IIdentifiable resource)
+ {
+ return new ResourceLinks
{
- return new RelationshipLinks
- {
- Self = "Relationship:Self",
- Related = "Relationship:Related"
- };
- }
+ Self = "Resource:Self"
+ };
}
- private sealed class FakeMetaBuilder : IMetaBuilder
+ public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable leftResource)
{
- public void Add(IReadOnlyDictionary values)
+ return new RelationshipLinks
{
- }
+ Self = "Relationship:Self",
+ Related = "Relationship:Related"
+ };
+ }
+ }
- public IDictionary? Build()
- {
- return null;
- }
+ private sealed class FakeMetaBuilder : IMetaBuilder
+ {
+ public void Add(IReadOnlyDictionary values)
+ {
}
- private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor
+ public IDictionary? Build()
{
- public IQueryCollection Query { get; } = new QueryCollection(0);
+ return null;
}
}
+
+ private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor
+ {
+ public IQueryCollection Query { get; } = new QueryCollection(0);
+ }
}
diff --git a/codecov.yml b/codecov.yml
index 551e7d4c54..32a518442e 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -8,3 +8,6 @@ coverage:
patch:
default:
informational: true
+
+github_checks:
+ annotations: false
diff --git a/docs/getting-started/step-by-step.md b/docs/getting-started/step-by-step.md
index 21daf04171..57090d2d09 100644
--- a/docs/getting-started/step-by-step.md
+++ b/docs/getting-started/step-by-step.md
@@ -3,22 +3,21 @@
The most basic use case leverages Entity Framework Core.
The shortest path to a running API looks like:
-- Create a new web app
+- Create a new API project
- Install
- Define models
- Define the DbContext
-- Define controllers
-- Add Middleware and Services
+- Add services and middleware
- Seed the database
-- Start the app
+- Start the API
This page will walk you through the **simplest** use case. More detailed examples can be found in the detailed usage subsections.
-### Create A New Web App
+### Create a new API project
```
-mkdir MyApp
-cd MyApp
+mkdir MyApi
+cd MyApi
dotnet new webapi
```
@@ -32,7 +31,7 @@ dotnet add package JsonApiDotNetCore
Install-Package JsonApiDotNetCore
```
-### Define Models
+### Define models
Define your domain models such that they implement `IIdentifiable`.
The easiest way to do this is to inherit from `Identifiable`.
@@ -40,6 +39,7 @@ The easiest way to do this is to inherit from `Identifiable`.
```c#
#nullable enable
+[Resource]
public class Person : Identifiable
{
[Attr]
@@ -47,7 +47,7 @@ public class Person : Identifiable
}
```
-### Define DbContext
+### Define the DbContext
Nothing special here, just an ordinary `DbContext`.
@@ -63,62 +63,56 @@ public class AppDbContext : DbContext
}
```
-### Define Controllers
+### Add services and middleware
-You need to create controllers that inherit from `JsonApiController`
-where `TResource` is the model that inherits from `Identifiable`.
+Finally, register the services and middleware by adding them to your Program.cs:
```c#
-public class PeopleController : JsonApiController
+WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+// Add the Entity Framework Core DbContext like you normally would.
+builder.Services.AddDbContext(options =>
{
- public PeopleController(IJsonApiOptions options, IResourceGraph resourceGraph,
- ILoggerFactory loggerFactory, IResourceService resourceService)
- : base(options, resourceGraph, loggerFactory, resourceService)
- {
- }
-}
-```
+ string connectionString = GetConnectionString();
-### Middleware and Services
+ // Use whatever provider you want, this is just an example.
+ options.UseNpgsql(connectionString);
+});
-Finally, add the services by adding the following to your Startup.ConfigureServices:
+// Add JsonApiDotNetCore services.
+builder.Services.AddJsonApi();
-```c#
-// This method gets called by the runtime. Use this method to add services to the container.
-public void ConfigureServices(IServiceCollection services)
-{
- // Add the Entity Framework Core DbContext like you normally would
- services.AddDbContext(options =>
- {
- // Use whatever provider you want, this is just an example
- options.UseNpgsql(GetDbConnectionString());
- });
+WebApplication app = builder.Build();
- // Add JsonApiDotNetCore
- services.AddJsonApi();
-}
-```
+// Configure the HTTP request pipeline.
-Add the middleware to the Startup.Configure method.
+app.UseRouting();
-```c#
-// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
-public void Configure(IApplicationBuilder app)
-{
- app.UseRouting();
- app.UseJsonApi();
- app.UseEndpoints(endpoints => endpoints.MapControllers());
-}
+// Add JsonApiDotNetCore middleware.
+app.UseJsonApi();
+
+app.MapControllers();
+
+app.Run();
```
-### Seeding the Database
+### Seed the database
-One way to seed the database is in your Configure method:
+One way to seed the database is from your Program.cs:
```c#
-public void Configure(IApplicationBuilder app, AppDbContext dbContext)
+await CreateDatabaseAsync(app.Services);
+
+app.Run();
+
+static async Task CreateDatabaseAsync(IServiceProvider serviceProvider)
{
- dbContext.Database.EnsureCreated();
+ await using AsyncServiceScope scope = serviceProvider.CreateAsyncScope();
+
+ var dbContext = scope.ServiceProvider.GetRequiredService();
+ await dbContext.Database.EnsureCreatedAsync();
if (!dbContext.People.Any())
{
@@ -127,16 +121,12 @@ public void Configure(IApplicationBuilder app, AppDbContext dbContext)
Name = "John Doe"
});
- dbContext.SaveChanges();
+ await dbContext.SaveChangesAsync();
}
-
- app.UseRouting();
- app.UseJsonApi();
- app.UseEndpoints(endpoints => endpoints.MapControllers());
}
```
-### Start the App
+### Start the API
```
dotnet run
diff --git a/docs/usage/errors.md b/docs/usage/errors.md
index 3278526e6c..67ce6fde17 100644
--- a/docs/usage/errors.md
+++ b/docs/usage/errors.md
@@ -88,11 +88,6 @@ public class CustomExceptionHandler : ExceptionHandler
}
}
-public class Startup
-{
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddScoped();
- }
-}
+// Program.cs
+builder.Services.AddScoped();
```
diff --git a/docs/usage/extensibility/controllers.md b/docs/usage/extensibility/controllers.md
index 1993f77841..0c71f45090 100644
--- a/docs/usage/extensibility/controllers.md
+++ b/docs/usage/extensibility/controllers.md
@@ -1,6 +1,99 @@
# Controllers
-You need to create controllers that inherit from `JsonApiController`
+To expose API endpoints, ASP.NET controllers need to be defined.
+
+_since v5_
+
+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:
+
+```c#
+[Resource] // Generates ArticlesController.g.cs
+public class Article : Identifiable
+{
+ // ...
+}
+```
+
+## Resource Access Control
+
+It is often desirable to limit which endpoints are exposed on your controller.
+A subset can be specified too:
+
+```c#
+[Resource(GenerateControllerEndpoints =
+ JsonApiEndpoints.GetCollection | JsonApiEndpoints.GetSingle)]
+public class Article : Identifiable
+{
+ // ...
+}
+```
+
+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.
+
+When an endpoint is blocked, an HTTP 403 Forbidden response is returned.
+
+```http
+DELETE http://localhost:14140/articles/1 HTTP/1.1
+```
+
+```json
+{
+ "links": {
+ "self": "/articles"
+ },
+ "errors": [
+ {
+ "id": "dde7f219-2274-4473-97ef-baac3e7c1487",
+ "status": "403",
+ "title": "The requested endpoint is not accessible.",
+ "detail": "Endpoint '/articles/1' is not accessible for DELETE requests."
+ }
+ ]
+}
+```
+
+## Augmenting controllers
+
+Auto-generated controllers can easily be augmented because they are partial classes. For example:
+
+```c#
+[DisableRoutingConvention]
+[Route("some/custom/route")]
+[DisableQueryString(JsonApiQueryStringParameters.Include)]
+partial class ArticlesController
+{
+ [HttpPost]
+ public IActionResult Upload()
+ {
+ // ...
+ }
+}
+```
+
+If you need to inject extra dependencies, tell the IoC container with `[ActivatorUtilitiesConstructor]` to prefer your constructor:
+
+```c#
+partial class ArticlesController
+{
+ private IAuthenticationService _authService;
+
+ [ActivatorUtilitiesConstructor]
+ public ArticlesController(IAuthenticationService authService, IJsonApiOptions options,
+ IResourceGraph resourceGraph, ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
+ {
+ _authService = authService;
+ }
+}
+```
+
+In case you don't want to use auto-generated controllers and define them yourself (see below), remove
+`[Resource]` from your models or use `[Resource(GenerateControllerEndpoints = JsonApiEndpoints.None)]`.
+
+## Earlier versions
+
+In earlier versions of JsonApiDotNetCore, you needed to create controllers that inherit from `JsonApiController`. For example:
```c#
public class ArticlesController : JsonApiController
@@ -15,7 +108,7 @@ public class ArticlesController : JsonApiController
If you want to setup routes yourself, you can instead inherit from `BaseJsonApiController` 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.
-## Resource Access Control
+### Resource Access Control
It is often desirable to limit which routes are exposed on your controller.
@@ -37,25 +130,3 @@ public class ReportsController : JsonApiController
```
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).
-
-When a route is blocked, an HTTP 403 Forbidden response is returned.
-
-```http
-DELETE http://localhost:14140/people/1 HTTP/1.1
-```
-
-```json
-{
- "links": {
- "self": "/api/v1/people"
- },
- "errors": [
- {
- "id": "dde7f219-2274-4473-97ef-baac3e7c1487",
- "status": "403",
- "title": "The requested endpoint is not accessible.",
- "detail": "Endpoint '/people/1' is not accessible for DELETE requests."
- }
- ]
-}
-```
diff --git a/docs/usage/extensibility/layer-overview.md b/docs/usage/extensibility/layer-overview.md
index 8afd1b38bb..2fe99e2fbd 100644
--- a/docs/usage/extensibility/layer-overview.md
+++ b/docs/usage/extensibility/layer-overview.md
@@ -25,18 +25,15 @@ on your needs, you may want to replace other parts by deriving from the built-in
**Note:** If you're using [auto-discovery](~/usage/resource-graph.md#auto-discovery), then resource services, repositories and resource definitions will be automatically registered for you.
-Replacing built-in services is done on a per-resource basis and can be done through dependency injection in your Startup.cs file.
+Replacing built-in services is done on a per-resource basis and can be done at startup.
For convenience, extension methods are provided to register layers on all their implemented interfaces.
```c#
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddResourceService();
- services.AddResourceRepository();
- services.AddResourceDefinition();
-
- services.AddScoped();
- services.AddScoped();
-}
+// Program.cs
+builder.Services.AddResourceService();
+builder.Services.AddResourceRepository();
+builder.Services.AddResourceDefinition();
+
+builder.Services.AddScoped();
+builder.Services.AddScoped();
```
diff --git a/docs/usage/extensibility/middleware.md b/docs/usage/extensibility/middleware.md
index c05efa2e41..62528893d3 100644
--- a/docs/usage/extensibility/middleware.md
+++ b/docs/usage/extensibility/middleware.md
@@ -10,46 +10,54 @@ It is possible to replace the built-in middleware components by configuring the
The following example replaces the internal exception filter with a custom implementation.
```c#
-/// In Startup.ConfigureServices
-services.AddService();
+// Program.cs
+builder.Services.AddService();
```
## Configuring `MvcOptions`
-The following example replaces all internal filters with a custom filter.
+The following example replaces the built-in query string action filter with a custom filter.
```c#
-public class Startup
+// Program.cs
+
+// Add services to the container.
+
+builder.Services.AddScoped();
+
+IMvcCoreBuilder mvcCoreBuilder = builder.Services.AddMvcCore();
+builder.Services.AddJsonApi(mvcBuilder: mvcCoreBuilder);
+
+Action? postConfigureMvcOptions = null;
+
+// Ensure this is placed after the AddJsonApi() call.
+mvcCoreBuilder.AddMvcOptions(mvcOptions =>
+{
+ postConfigureMvcOptions?.Invoke(mvcOptions);
+});
+
+// Configure the HTTP request pipeline.
+
+// Ensure this is placed before the MapControllers() call.
+postConfigureMvcOptions = mvcOptions =>
{
- private Action _postConfigureMvcOptions;
-
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddSingleton();
-
- IMvcCoreBuilder mvcBuilder = services.AddMvcCore();
- services.AddJsonApi(mvcBuilder: builder);
-
- // Ensure this call is placed after the AddJsonApi call.
- builder.AddMvcOptions(mvcOptions =>
- {
- _postConfigureMvcOptions.Invoke(mvcOptions);
- });
- }
-
- public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
- {
- // Ensure this call is placed before the UseEndpoints call.
- _postConfigureMvcOptions = mvcOptions =>
- {
- mvcOptions.Filters.Clear();
- mvcOptions.Filters.Insert(0,
- app.ApplicationServices.GetService());
- };
-
- app.UseRouting();
- app.UseJsonApi();
- app.UseEndpoints(endpoints => endpoints.MapControllers());
- }
-}
+ IFilterMetadata existingFilter = mvcOptions.Filters.Single(filter =>
+ filter is ServiceFilterAttribute serviceFilter &&
+ serviceFilter.ServiceType == typeof(IAsyncQueryStringActionFilter));
+
+ mvcOptions.Filters.Remove(existingFilter);
+
+ using IServiceScope scope = app.Services.CreateScope();
+
+ var newFilter =
+ scope.ServiceProvider.GetRequiredService();
+
+ mvcOptions.Filters.Insert(0, newFilter);
+};
+
+app.UseRouting();
+app.UseJsonApi();
+app.MapControllers();
+
+app.Run();
```
diff --git a/docs/usage/extensibility/query-strings.md b/docs/usage/extensibility/query-strings.md
index 1411717ffd..83d9f2e0ea 100644
--- a/docs/usage/extensibility/query-strings.md
+++ b/docs/usage/extensibility/query-strings.md
@@ -24,15 +24,17 @@ See [here](~/usage/extensibility/resource-definitions.md#custom-query-string-par
In order to add parsing of custom query string parameters, you can implement the `IQueryStringParameterReader` interface and register your reader.
```c#
-public class YourQueryStringParameterReader : IQueryStringParameterReader
+public class CustomQueryStringParameterReader : IQueryStringParameterReader
{
// ...
}
```
```c#
-services.AddScoped();
-services.AddScoped(sp => sp.GetService());
+// Program.cs
+builder.Services.AddScoped();
+builder.Services.AddScoped(serviceProvider =>
+ serviceProvider.GetRequiredService());
```
Now you can inject your custom reader in resource services, repositories, resource definitions etc.
diff --git a/docs/usage/extensibility/repositories.md b/docs/usage/extensibility/repositories.md
index 7d76f2389a..102406e71b 100644
--- a/docs/usage/extensibility/repositories.md
+++ b/docs/usage/extensibility/repositories.md
@@ -3,15 +3,11 @@
If you want to use a data access technology other than Entity Framework Core, you can create an implementation of `IResourceRepository`.
If you only need minor changes you can override the methods defined in `EntityFrameworkCoreRepository`.
-The repository should then be registered in Startup.cs.
-
```c#
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddScoped, ArticleRepository>();
- services.AddScoped, ArticleRepository>();
- services.AddScoped, ArticleRepository>();
-}
+// Program.cs
+builder.Services.AddScoped, ArticleRepository>();
+builder.Services.AddScoped, ArticleRepository>();
+builder.Services.AddScoped, ArticleRepository>();
```
In v4.0 we introduced an extension method that you can use to register a resource repository on all of its JsonApiDotNetCore interfaces.
@@ -20,13 +16,8 @@ This is helpful when you implement (a subset of) the resource interfaces and wan
**Note:** If you're using [auto-discovery](~/usage/resource-graph.md#auto-discovery), this happens automatically.
```c#
-public class Startup
-{
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddResourceRepository();
- }
-}
+// Program.cs
+builder.Services.AddResourceRepository();
```
A sample implementation that performs authorization might look like this.
@@ -64,7 +55,8 @@ If you need to use multiple Entity Framework Core DbContexts, first create a rep
This example shows a single `DbContextARepository` for all entities that are members of `DbContextA`.
```c#
-public class DbContextARepository : EntityFrameworkCoreRepository
+public class DbContextARepository
+ : EntityFrameworkCoreRepository
where TResource : class, IIdentifiable
{
public DbContextARepository(ITargetedFields targetedFields,
@@ -83,13 +75,12 @@ public class DbContextARepository : EntityFrameworkCoreRepositor
Then register the added types and use the non-generic overload of `AddJsonApi` to add their resources to the graph.
```c#
-// In Startup.ConfigureServices
-
-services.AddDbContext(options => options.UseSqlite("Data Source=A.db"));
-services.AddDbContext(options => options.UseSqlite("Data Source=B.db"));
+// Program.cs
+builder.Services.AddDbContext(options => options.UseSqlite("Data Source=A.db"));
+builder.Services.AddDbContext(options => options.UseSqlite("Data Source=B.db"));
-services.AddScoped, DbContextARepository>();
-services.AddScoped, DbContextBRepository>();
+builder.Services.AddJsonApi(dbContextTypes: new[] { typeof(DbContextA), typeof(DbContextB) });
-services.AddJsonApi(dbContextTypes: new[] { typeof(DbContextA), typeof(DbContextB) });
+builder.Services.AddScoped, DbContextARepository>();
+builder.Services.AddScoped, DbContextBRepository>();
```
diff --git a/docs/usage/extensibility/resource-definitions.md b/docs/usage/extensibility/resource-definitions.md
index 4c9eeeb8a6..8696811e16 100644
--- a/docs/usage/extensibility/resource-definitions.md
+++ b/docs/usage/extensibility/resource-definitions.md
@@ -10,20 +10,15 @@ In v4.2 we introduced an extension method that you can use to register your reso
**Note:** If you're using [auto-discovery](~/usage/resource-graph.md#auto-discovery), this happens automatically.
```c#
-public class Startup
-{
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddResourceDefinition();
- }
-}
+// Program.cs
+builder.Services.AddResourceDefinition();
```
**Note:** Prior to the introduction of auto-discovery (in v3), you needed to register the
resource definition on the container yourself:
```c#
-services.AddScoped, ProductDefinition>();
+builder.Services.AddScoped, ArticleDefinition>();
```
## Customizing queries
diff --git a/docs/usage/extensibility/services.md b/docs/usage/extensibility/services.md
index 77d772435e..bc3dd5bff8 100644
--- a/docs/usage/extensibility/services.md
+++ b/docs/usage/extensibility/services.md
@@ -1,7 +1,8 @@
# Resource Services
The `IResourceService` acts as a service layer between the controller and the data access layer.
-This allows you to customize it however you want. This is also a good place to implement custom business logic.
+This allows you to customize it however you want. While this is still a potential place to implement custom business logic,
+since v4, [Resource Definitions](~/usage/extensibility/resource-definitions.md) are more suitable for that.
## Supplementing Default Behavior
@@ -47,17 +48,16 @@ As previously discussed, this library uses Entity Framework Core by default.
If you'd like to use another ORM that does not provide what JsonApiResourceService depends upon, you can use a custom `IResourceService` implementation.
```c#
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- // add the service override for Product
- services.AddScoped, ProductService>();
+// Program.cs
- // add your own Data Access Object
- services.AddScoped();
-}
+// Add the service override for Product.
+builder.Services.AddScoped, ProductService>();
+
+// Add your own Data Access Object.
+builder.Services.AddScoped();
// ProductService.cs
+
public class ProductService : IResourceService
{
private readonly IProductDao _dao;
@@ -77,7 +77,7 @@ public class ProductService : IResourceService
## Limited Requirements
-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.
+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.
This interface hierarchy is defined by this tree structure.
@@ -127,14 +127,9 @@ public class ArticleService : ICreateService, IDeleteService, ArticleService>();
- services.AddScoped, ArticleService>();
- }
-}
+// Program.cs
+builder.Services.AddScoped, ArticleService>();
+builder.Services.AddScoped, ArticleService>();
```
In v3.0 we introduced an extension method that you can use to register a resource service on all of its JsonApiDotNetCore interfaces.
@@ -143,16 +138,22 @@ This is helpful when you implement (a subset of) the resource interfaces and wan
**Note:** If you're using [auto-discovery](~/usage/resource-graph.md#auto-discovery), this happens automatically.
```c#
-public class Startup
+// Program.cs
+builder.Services.AddResourceService();
+```
+
+Then on your model, pass in the set of endpoints to expose (the ones that you've registered services for):
+
+```c#
+[Resource(GenerateControllerEndpoints =
+ JsonApiEndpoints.Create | JsonApiEndpoints.Delete)]
+public class Article : Identifiable
{
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddResourceService();
- }
+ // ...
}
```
-Then in the controller, you should inherit from the JSON:API controller and pass the services into the named, optional base parameters:
+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:
```c#
public class ArticlesController : JsonApiController
diff --git a/docs/usage/meta.md b/docs/usage/meta.md
index 29c074b8b6..a115e25740 100644
--- a/docs/usage/meta.md
+++ b/docs/usage/meta.md
@@ -8,11 +8,12 @@ Global metadata can be added to the root of the response document by registering
This is useful if you need access to other registered services to build the meta object.
```c#
-#nullable enable
+// Program.cs
+builder.Services.AddSingleton();
-// In Startup.ConfigureServices
-services.AddSingleton();
+// CopyrightResponseMeta.cs
+#nullable enable
public sealed class CopyrightResponseMeta : IResponseMeta
{
public IReadOnlyDictionary GetMeta()
diff --git a/docs/usage/openapi.md b/docs/usage/openapi.md
index 07e731dce7..f0880e1c5f 100644
--- a/docs/usage/openapi.md
+++ b/docs/usage/openapi.md
@@ -11,47 +11,33 @@ JsonApiDotNetCore provides an extension package that enables you to produce an [
dotnet add package JsonApiDotNetCore.OpenApi
```
-2. Add the integration in your `Startup` class.
+2. Add the integration in your `Program.cs` file.
```c#
- public class Startup
- {
- public void ConfigureServices(IServiceCollection services)
- {
- IMvcCoreBuilder mvcBuilder = services.AddMvcCore();
-
- services.AddJsonApi(mvcBuilder: mvcBuilder);
-
- // Adds the Swashbuckle integration.
- services.AddOpenApi(mvcBuilder);
- }
-
- public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
- {
- app.UseRouting();
- app.UseJsonApi();
-
- // Adds the Swashbuckle middleware.
- app.UseSwagger();
-
- app.UseEndpoints(endpoints => endpoints.MapControllers());
- }
- }
+ IMvcCoreBuilder mvcCoreBuilder = builder.Services.AddMvcCore();
+
+ builder.Services.AddJsonApi(mvcBuilder: mvcCoreBuilder);
+
+ // Configures Swashbuckle for JSON:API.
+ builder.Services.AddOpenApi(mvcCoreBuilder);
+
+ var app = builder.Build();
+
+ app.UseRouting();
+ app.UseJsonApi();
+
+ // Adds the Swashbuckle middleware.
+ app.UseSwagger();
```
By default, the OpenAPI specification will be available at `http://localhost:/swagger/v1/swagger.json`.
## Documentation
-Swashbuckle also ships with [SwaggerUI](https://swagger.io/tools/swagger-ui/), tooling for a generated documentation page. This can be enabled by installing the `Swashbuckle.AspNetCore.SwaggerUI` NuGet package and adding the following to your `Startup` class:
+Swashbuckle also ships with [SwaggerUI](https://swagger.io/tools/swagger-ui/), tooling for a generated documentation page. This can be enabled by installing the `Swashbuckle.AspNetCore.SwaggerUI` NuGet package and adding the following to your `Program.cs` file:
```c#
-// Startup.cs
-public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
-{
- app.UseSwaggerUI();
-}
+app.UseSwaggerUI();
```
By default, SwaggerUI will be available at `http://localhost:/swagger`.
-
diff --git a/docs/usage/options.md b/docs/usage/options.md
index 2f350b8bf9..83d535bce4 100644
--- a/docs/usage/options.md
+++ b/docs/usage/options.md
@@ -1,19 +1,13 @@
# Global Options
-Configuration can be applied when adding the services to the dependency injection container.
+Configuration can be applied when adding services to the dependency injection container at startup.
```c#
-public class Startup
+// Program.cs
+builder.Services.AddJsonApi(options =>
{
- // This method gets called by the runtime. Use this method to add services to the container.
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddJsonApi(options =>
- {
- // configure the options here
- });
- }
-}
+ // Configure the options here...
+});
```
## Client Generated IDs
diff --git a/docs/usage/resource-graph.md b/docs/usage/resource-graph.md
index beb20d2d92..4010cbea5f 100644
--- a/docs/usage/resource-graph.md
+++ b/docs/usage/resource-graph.md
@@ -23,36 +23,27 @@ Auto-discovery refers to the process of reflecting on an assembly and
detecting all of the JSON:API resources, resource definitions, resource services and repositories.
The following command builds the resource graph using all `IIdentifiable` implementations and registers the services mentioned.
-You can enable auto-discovery for the current assembly by adding the following to your `Startup` class.
+You can enable auto-discovery for the current assembly by adding the following at startup.
```c#
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddJsonApi(discovery => discovery.AddCurrentAssembly());
-}
+// Program.cs
+builder.Services.AddJsonApi(discovery => discovery.AddCurrentAssembly());
```
### Specifying an Entity Framework Core DbContext
-If you are using Entity Framework Core as your ORM, you can add all the models of a `DbContext` to the resource graph.
+If you are using Entity Framework Core as your ORM, you can add all the models of a `DbContext` to the resource graph.
```c#
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddJsonApi();
-}
+// Program.cs
+builder.Services.AddJsonApi();
```
Be aware that this does not register resource definitions, resource services and repositories. You can combine it with auto-discovery to achieve this.
```c#
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddJsonApi(discovery => discovery.AddCurrentAssembly());
-}
+// Program.cs
+builder.Services.AddJsonApi(discovery => discovery.AddCurrentAssembly());
```
### Manual Specification
@@ -60,40 +51,36 @@ public void ConfigureServices(IServiceCollection services)
You can manually construct the graph.
```c#
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddJsonApi(resources: builder =>
- {
- builder.Add();
- });
-}
+// Program.cs
+builder.Services.AddJsonApi(resources: resourceGraphBuilder =>
+ resourceGraphBuilder.Add());
```
## Resource Name
-The public resource name is exposed through the `type` member in the JSON:API payload. This can be configured by the following approaches (in order of priority):
+The public resource name is exposed through the `type` member in the JSON:API payload. This can be configured using the following approaches (in order of priority):
-1. The `publicName` parameter when manually adding a resource to the graph
+1. The `publicName` parameter when manually adding a resource to the graph.
```c#
-services.AddJsonApi(resources: builder =>
+// Program.cs
+builder.Services.AddJsonApi(resources: resourceGraphBuilder =>
{
- builder.Add(publicName: "people");
+ resourceGraphBuilder.Add(publicName: "individuals");
});
```
-2. The model is decorated with a `ResourceAttribute`
+2. The `PublicName` property when a model is decorated with a `ResourceAttribute`.
```c#
-[Resource("myResources")]
-public class MyModel : Identifiable
+[Resource(PublicName = "individuals")]
+public class Person : Identifiable
{
}
```
-3. The configured naming convention (by default this is camel-case).
+3. The configured naming convention (by default this is camel-case), after pluralization.
```c#
-// this will be registered as "myModels"
-public class MyModel : Identifiable
+// this will be registered as "people"
+public class Person : Identifiable
{
}
```
diff --git a/docs/usage/routing.md b/docs/usage/routing.md
index c68914a04a..a264622931 100644
--- a/docs/usage/routing.md
+++ b/docs/usage/routing.md
@@ -6,27 +6,28 @@ An endpoint URL provides access to a resource or a relationship. Resource endpoi
In the relationship endpoint "/articles/1/relationships/comments", "articles" is the left side of the relationship and "comments" the right side.
-## Namespacing and Versioning URLs
-You can add a namespace to all URLs by specifying it in ConfigureServices.
+## Namespacing and versioning of URLs
+You can add a namespace to all URLs by specifying it at startup.
```c#
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddJsonApi(options => options.Namespace = "api/v1");
-}
+// Program.cs
+builder.Services.AddJsonApi(options => options.Namespace = "api/v1");
```
Which results in URLs like: https://yourdomain.com/api/v1/people
-## Default Routing Convention
+## Default routing convention
-The library will configure routes for all controllers in your project. By default, routes are camel-cased. This is based on the [recommendations](https://jsonapi.org/recommendations/) outlined in the JSON:API spec.
+The library will configure routes for all auto-generated and hand-written controllers in your project. By default, routes are camel-cased. This is based on the [recommendations](https://jsonapi.org/recommendations/) outlined in the JSON:API spec.
```c#
-public class OrderLine : Identifiable
+// Auto-generated
+[Resource]
+public class OrderSummary : Identifiable
{
}
+// Hand-written
public class OrderLineController : JsonApiController
{
public OrderLineController(IJsonApiOptions options, IResourceGraph resourceGraph,
@@ -38,6 +39,7 @@ public class OrderLineController : JsonApiController
```
```http
+GET /orderSummaries HTTP/1.1
GET /orderLines HTTP/1.1
```
@@ -57,12 +59,21 @@ public class OrderLineController : ControllerBase
GET /orderLines HTTP/1.1
```
-## Disabling the Default Routing Convention
+### Customized routes
-It is possible to bypass the default routing convention for a controller.
+It is possible to override the default routing convention for an auto-generated or hand-written controller.
```c#
-[Route("v1/custom/route/lines-in-order"), DisableRoutingConvention]
+// Auto-generated
+[DisableRoutingConvention]
+[Route("v1/custom/route/summaries-for-orders")]
+partial class OrderSummariesController
+{
+}
+
+// Hand-written
+[DisableRoutingConvention]
+[Route("v1/custom/route/lines-in-order")]
public class OrderLineController : JsonApiController
{
public OrderLineController(IJsonApiOptions options, IResourceGraph resourceGraph,
@@ -73,13 +84,11 @@ public class OrderLineController : JsonApiController
}
```
-## Advanced Usage: Custom Routing Convention
+## Advanced usage: custom routing convention
It is possible to replace the built-in routing convention with a [custom routing convention](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/application-model?view=aspnetcore-3.1#sample-custom-routing-convention) by registering an implementation of `IJsonApiRoutingConvention`.
```c#
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddSingleton();
-}
+// Program.cs
+builder.Services.AddSingleton();
```
diff --git a/docs/usage/writing/bulk-batch-operations.md b/docs/usage/writing/bulk-batch-operations.md
index 21fe04b636..1ac35fd3fc 100644
--- a/docs/usage/writing/bulk-batch-operations.md
+++ b/docs/usage/writing/bulk-batch-operations.md
@@ -85,10 +85,11 @@ For example requests, see our suite of tests in JsonApiDotNetCoreTests.Integrati
## Configuration
-The maximum number of operations per request defaults to 10, which you can change from Startup.cs:
+The maximum number of operations per request defaults to 10, which you can change at startup:
```c#
-services.AddJsonApi(options => options.MaximumOperationsPerRequest = 250);
+// Program.cs
+builder.Services.AddJsonApi(options => options.MaximumOperationsPerRequest = 250);
```
Or, if you want to allow unconstrained, set it to `null` instead.
diff --git a/src/Examples/GettingStarted/Controllers/BooksController.cs b/src/Examples/GettingStarted/Controllers/BooksController.cs
deleted file mode 100644
index 3f049429cd..0000000000
--- a/src/Examples/GettingStarted/Controllers/BooksController.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using GettingStarted.Models;
-using JsonApiDotNetCore.Configuration;
-using JsonApiDotNetCore.Controllers;
-using JsonApiDotNetCore.Services;
-using Microsoft.Extensions.Logging;
-
-namespace GettingStarted.Controllers
-{
- public sealed class BooksController : JsonApiController
- {
- public BooksController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IResourceService resourceService)
- : base(options, resourceGraph, loggerFactory, resourceService)
- {
- }
- }
-}
diff --git a/src/Examples/GettingStarted/Controllers/PeopleController.cs b/src/Examples/GettingStarted/Controllers/PeopleController.cs
deleted file mode 100644
index e7a5537f14..0000000000
--- a/src/Examples/GettingStarted/Controllers/PeopleController.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using GettingStarted.Models;
-using JsonApiDotNetCore.Configuration;
-using JsonApiDotNetCore.Controllers;
-using JsonApiDotNetCore.Services;
-using Microsoft.Extensions.Logging;
-
-namespace GettingStarted.Controllers
-{
- public sealed class PeopleController : JsonApiController
- {
- public PeopleController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory,
- IResourceService resourceService)
- : base(options, resourceGraph, loggerFactory, resourceService)
- {
- }
- }
-}
diff --git a/src/Examples/GettingStarted/Data/SampleDbContext.cs b/src/Examples/GettingStarted/Data/SampleDbContext.cs
index c5460db810..44662c388b 100644
--- a/src/Examples/GettingStarted/Data/SampleDbContext.cs
+++ b/src/Examples/GettingStarted/Data/SampleDbContext.cs
@@ -2,16 +2,15 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
-namespace GettingStarted.Data
+namespace GettingStarted.Data;
+
+[UsedImplicitly(ImplicitUseTargetFlags.Members)]
+public class SampleDbContext : DbContext
{
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public class SampleDbContext : DbContext
- {
- public DbSet Books => Set();
+ public DbSet Books => Set();
- public SampleDbContext(DbContextOptions options)
- : base(options)
- {
- }
+ public SampleDbContext(DbContextOptions options)
+ : base(options)
+ {
}
}
diff --git a/src/Examples/GettingStarted/GettingStarted.csproj b/src/Examples/GettingStarted/GettingStarted.csproj
index b28d7a0c5a..ab152b79d5 100644
--- a/src/Examples/GettingStarted/GettingStarted.csproj
+++ b/src/Examples/GettingStarted/GettingStarted.csproj
@@ -1,10 +1,12 @@
- $(NetCoreAppVersion)
+ $(TargetFrameworkName)
+
diff --git a/src/Examples/GettingStarted/Models/Book.cs b/src/Examples/GettingStarted/Models/Book.cs
index 0957461cd7..66beed1072 100644
--- a/src/Examples/GettingStarted/Models/Book.cs
+++ b/src/Examples/GettingStarted/Models/Book.cs
@@ -2,18 +2,18 @@
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
-namespace GettingStarted.Models
+namespace GettingStarted.Models;
+
+[UsedImplicitly(ImplicitUseTargetFlags.Members)]
+[Resource]
+public sealed class Book : Identifiable
{
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public sealed class Book : Identifiable
- {
- [Attr]
- public string Title { get; set; } = null!;
+ [Attr]
+ public string Title { get; set; } = null!;
- [Attr]
- public int PublishYear { get; set; }
+ [Attr]
+ public int PublishYear { get; set; }
- [HasOne]
- public Person Author { get; set; } = null!;
- }
+ [HasOne]
+ public Person Author { get; set; } = null!;
}
diff --git a/src/Examples/GettingStarted/Models/Person.cs b/src/Examples/GettingStarted/Models/Person.cs
index f9b8e55fff..89ca4c5a69 100644
--- a/src/Examples/GettingStarted/Models/Person.cs
+++ b/src/Examples/GettingStarted/Models/Person.cs
@@ -1,17 +1,16 @@
-using System.Collections.Generic;
using JetBrains.Annotations;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
-namespace GettingStarted.Models
+namespace GettingStarted.Models;
+
+[UsedImplicitly(ImplicitUseTargetFlags.Members)]
+[Resource]
+public sealed class Person : Identifiable
{
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public sealed class Person : Identifiable
- {
- [Attr]
- public string Name { get; set; } = null!;
+ [Attr]
+ public string Name { get; set; } = null!;
- [HasMany]
- public ICollection Books { get; set; } = new List();
- }
+ [HasMany]
+ public ICollection