diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 69e33b978d..d914fc369d 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,13 +3,13 @@
"isRoot": true,
"tools": {
"jetbrains.resharper.globaltools": {
- "version": "2021.3.4",
+ "version": "2022.2.3",
"commands": [
"jb"
]
},
"regitlint": {
- "version": "6.0.8",
+ "version": "6.1.1",
"commands": [
"regitlint"
]
diff --git a/.editorconfig b/.editorconfig
index b6d9a8990c..ca191cf90e 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -6,17 +6,20 @@ indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
-end_of_line = lf
insert_final_newline = true
-[*.{csproj,json}]
+[*.{config,csproj,css,js,json,props,ruleset,xslt}]
indent_size = 2
[*.{cs}]
-#### .NET Coding Conventions ####
+#### C#/.NET Coding Conventions ####
-# Organize usings
+# 'using' directive preferences
dotnet_sort_system_directives_first = true
+csharp_using_directive_placement = outside_namespace:suggestion
+
+# Namespace declarations
+csharp_style_namespace_declarations = file_scoped:suggestion
# this. preferences
dotnet_style_qualification_for_field = false:suggestion
@@ -30,6 +33,7 @@ dotnet_style_predefined_type_for_member_access = true:suggestion
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
+csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:suggestion
csharp_style_pattern_local_over_anonymous_function = false:silent
# Expression-level preferences
@@ -37,6 +41,7 @@ dotnet_style_operator_placement_when_wrapping = end_of_line
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
# Parameter preferences
dotnet_code_quality_unused_parameters = non_public:suggestion
@@ -54,38 +59,38 @@ csharp_style_expression_bodied_properties = true:suggestion
# Code-block preferences
csharp_prefer_braces = true:suggestion
-# Expression-level preferences
-csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
-
-# 'using' directive preferences
-csharp_using_directive_placement = outside_namespace:suggestion
-
-
-#### C# Formatting Rules ####
-
# Indentation preferences
csharp_indent_case_contents_when_block = false
# Wrapping preferences
csharp_preserve_single_line_statements = false
+# 'var' usage preferences
+csharp_style_var_for_built_in_types = false:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_var_elsewhere = false:suggestion
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:suggestion
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
+dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:suggestion
-#### Naming styles ####
+#### Naming Style ####
dotnet_diagnostic.IDE1006.severity = warning
# Naming rules
-dotnet_naming_rule.private_const_fields_should_be_pascal_case.symbols = private_const_fields
-dotnet_naming_rule.private_const_fields_should_be_pascal_case.style = pascal_case
-dotnet_naming_rule.private_const_fields_should_be_pascal_case.severity = warning
+dotnet_naming_rule.const_fields_should_be_pascal_case.symbols = const_fields
+dotnet_naming_rule.const_fields_should_be_pascal_case.style = pascal_case
+dotnet_naming_rule.const_fields_should_be_pascal_case.severity = warning
dotnet_naming_rule.private_static_readonly_fields_should_be_pascal_case.symbols = private_static_readonly_fields
dotnet_naming_rule.private_static_readonly_fields_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.private_static_readonly_fields_should_be_pascal_case.severity = warning
-dotnet_naming_rule.private_static_or_readonly_fields_should_start_with_underscore.symbols = private_static_or_readonly_fields
-dotnet_naming_rule.private_static_or_readonly_fields_should_start_with_underscore.style = camel_case_prefix_with_underscore
-dotnet_naming_rule.private_static_or_readonly_fields_should_start_with_underscore.severity = warning
+dotnet_naming_rule.private_fields_should_start_with_underscore.symbols = private_fields
+dotnet_naming_rule.private_fields_should_start_with_underscore.style = camel_case_prefix_with_underscore
+dotnet_naming_rule.private_fields_should_start_with_underscore.severity = warning
dotnet_naming_rule.locals_and_parameters_should_be_camel_case.symbols = locals_and_parameters
dotnet_naming_rule.locals_and_parameters_should_be_camel_case.style = camel_case
@@ -96,25 +101,24 @@ dotnet_naming_rule.types_and_members_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.types_and_members_should_be_pascal_case.severity = warning
# Symbol specifications
-dotnet_naming_symbols.private_const_fields.applicable_kinds = field
-dotnet_naming_symbols.private_const_fields.applicable_accessibilities = private
-dotnet_naming_symbols.private_const_fields.required_modifiers = const
+dotnet_naming_symbols.const_fields.applicable_kinds = field
+dotnet_naming_symbols.const_fields.applicable_accessibilities = *
+dotnet_naming_symbols.const_fields.required_modifiers = const
dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private
-dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = static,readonly
+dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = static, readonly
-dotnet_naming_symbols.private_static_or_readonly_fields.applicable_kinds = field
-dotnet_naming_symbols.private_static_or_readonly_fields.applicable_accessibilities = private
-dotnet_naming_symbols.private_static_or_readonly_fields.required_modifiers = static readonly
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private
-dotnet_naming_symbols.locals_and_parameters.applicable_kinds = local,parameter
+dotnet_naming_symbols.locals_and_parameters.applicable_kinds = local, parameter
dotnet_naming_symbols.locals_and_parameters.applicable_accessibilities = *
dotnet_naming_symbols.types_and_members.applicable_kinds = *
dotnet_naming_symbols.types_and_members.applicable_accessibilities = *
-# Naming styles
+# Style specifications
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.camel_case_prefix_with_underscore.required_prefix = _
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 150c8b45df..4205b1ceec 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -1,8 +1,6 @@
# I don't want to read this whole thing I just have a question!!!
-> Note: Please don't file an issue to ask a question.
-
-You'll get faster results by using our official [Gitter channel](https://gitter.im/json-api-dotnet-core/Lobby) or [StackOverflow](https://stackoverflow.com/search?q=jsonapidotnetcore) where the community chimes in with helpful advice if you have questions.
+> You can file an issue to ask a question, but you'll get faster results by using our official [Gitter channel](https://gitter.im/json-api-dotnet-core/Lobby) or [StackOverflow](https://stackoverflow.com/search?q=jsonapidotnetcore) where the community chimes in with helpful advice if you have questions.
# How can I contribute?
@@ -60,8 +58,8 @@ Please follow these steps to have your contribution considered by the maintainer
We use [CSharpGuidelines](https://csharpcodingguidelines.com/) as our coding standard (with a few minor exceptions). Coding style is validated during PR build, where we inject an extra settings layer that promotes various suggestions to warning level. This ensures a high-quality codebase without interfering too much when editing code.
You can run the following [PowerShell scripts](https://github.com/PowerShell/PowerShell/releases) locally:
-- `pwsh inspectcode.ps1`: Scans the code for style violations and opens the result in your web browser.
-- `pwsh cleanupcode.ps1`: Reformats the entire codebase to match with our configured style.
+- `pwsh ./inspectcode.ps1`: Scans the code for style violations and opens the result in your web browser.
+- `pwsh ./cleanupcode.ps1 [branch-name-or-commit-hash]`: Reformats the codebase to match with our configured style, optionally only changed files since the specified branch (usually master).
Code inspection violations can be addressed in several ways, depending on the situation:
- Types that are reported to be never instantiated (because the IoC container creates them dynamically) should be decorated with `[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]`.
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index e0c2388a38..c59acf46d6 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -2,18 +2,18 @@
name: Bug report
about: Create a report to help us improve
title: ''
-labels: ''
+labels: 'bug'
assignees: ''
---
-_Please read our [Contributing Guides](https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/master/.github/CONTRIBUTING.md) before submitting a bug._
+
#### DESCRIPTION
-_A clear and concise description of what the bug is._
+
#### STEPS TO REPRODUCE
-_Consider to include your code here, such as models, DbContext, controllers, resource services, repositories, resource definitions etc. Please also include the request URL with body (if applicable) and the full exception stack trace (set `options.IncludeExceptionStackTraceInErrors` to `true`) in case of errors._ It may also be helpful to include the produced SQL, which can be made visible in logs by adding this to appsettings.json:
+
1.
2.
3.
#### EXPECTED BEHAVIOR
-_A clear and concise description of what you expected to happen._
+
#### ACTUAL BEHAVIOR
-_A clear and concise description of what happens instead._
+
#### VERSIONS USED
- JsonApiDotNetCore version:
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index f7b806fe50..f629ca472d 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -2,21 +2,21 @@
name: Feature request
about: Suggest an idea for this project
title: ''
-labels: ''
+labels: 'enhancement'
assignees: ''
---
-_Please read our [Contributing Guides](https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/master/.github/CONTRIBUTING.md) before suggesting an idea._
+
**Is your feature request related to a problem? Please describe.**
-_A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]_
+
**Describe the solution you'd like**
-_A clear and concise description of what you want to happen._
+
**Describe alternatives you've considered**
-_A clear and concise description of any alternative solutions or features you've considered._
+
**Additional context**
-_Add any other context or screenshots about the feature request here._
+
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
new file mode 100644
index 0000000000..689f2daa01
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -0,0 +1,38 @@
+---
+name: Question
+about: Ask a question
+title: ''
+labels: 'question'
+assignees: ''
+
+---
+
+#### SUMMARY
+
+
+#### DETAILS
+
+
+#### STEPS TO REPRODUCE
+
+
+1.
+2.
+3.
+
+#### VERSIONS USED
+- JsonApiDotNetCore version:
+- ASP.NET Core version:
+- Entity Framework Core version:
+- Database provider:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 3e23a87e27..1a1c618dd7 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -4,6 +4,6 @@ Closes #{ISSUE_NUMBER}
#### QUALITY CHECKLIST
- [ ] Changes implemented in code
-- [ ] Complies with our [contributing guidelines](./.github/CONTRIBUTING.md)
+- [ ] Complies with our [contributing guidelines](https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/master/.github/CONTRIBUTING.md)
- [ ] Adapted tests
- [ ] Documentation updated
diff --git a/.gitignore b/.gitignore
index 2bd200a72d..85bd0f1080 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,7 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
@@ -90,6 +90,7 @@ StyleCopReport.xml
*.tmp_proj
*_wpftmp.csproj
*.log
+*.tlog
*.vspscc
*.vssscc
.builds
@@ -97,9 +98,6 @@ StyleCopReport.xml
*.svclog
*.scc
-# MacOS file systems
-**/.DS_STORE
-
# Chutzpah Test files
_Chutzpah*
@@ -134,9 +132,6 @@ _ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
-# JetBrains Rider
-.idea/
-
# TeamCity is a build add-in
_TeamCity*
@@ -148,7 +143,9 @@ _TeamCity*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
-coverage*[.json, .xml, .info]
+coverage*.json
+coverage*.xml
+coverage*.info
# Visual Studio code coverage results
*.coverage
@@ -297,6 +294,17 @@ node_modules/
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
@@ -353,6 +361,9 @@ ASALocalRun/
# Local History for Visual Studio
.localhistory/
+# Visual Studio History (VSHistory) files
+.vshistory/
+
# BeatPulse healthcheck temp database
healthchecksdb
@@ -365,5 +376,50 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+
+#############################################
+### Additions specific to this repository ###
+#############################################
+
+# MacOS file systems
+**/.DS_STORE
+
# Sqlite example databases
*.db
+
+# JetBrains IDEs Rider/IntelliJ (based on https://intellij-support.jetbrains.com/hc/en-us/articles/206544839)
+**/.idea/**/*.xml
+**/.idea/**/*.iml
+**/.idea/**/*.ids
+**/.idea/**/*.ipr
+**/.idea/**/*.iws
+**/.idea/**/*.name
+**/.idea/**/*.properties
+**/.idea/**/*.ser
+**/.idea/**/shelf/
+**/.idea/**/dictionaries/
+**/.idea/**/libraries/
+**/.idea/**/artifacts/
+**/.idea/**/httpRequests/
+**/.idea/**/dataSources/
+!**/.idea/**/codeStyles/*
diff --git a/.idea/.idea.JsonApiDotNetCore/.idea/.gitignore b/.idea/.idea.JsonApiDotNetCore/.idea/.gitignore
new file mode 100644
index 0000000000..3933e947a2
--- /dev/null
+++ b/.idea/.idea.JsonApiDotNetCore/.idea/.gitignore
@@ -0,0 +1 @@
+# Empty .gitignore file to prevent Rider from adding one
diff --git a/.idea/.idea.JsonApiDotNetCore/.idea/codeStyles/Project.xml b/.idea/.idea.JsonApiDotNetCore/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000000..902b9f865f
--- /dev/null
+++ b/.idea/.idea.JsonApiDotNetCore/.idea/codeStyles/Project.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/.idea.JsonApiDotNetCore/.idea/codeStyles/codeStyleConfig.xml b/.idea/.idea.JsonApiDotNetCore/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000000..405cd65360
--- /dev/null
+++ b/.idea/.idea.JsonApiDotNetCore/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Build.ps1 b/Build.ps1
index e2e638fafa..d8be7da5e6 100644
--- a/Build.ps1
+++ b/Build.ps1
@@ -8,8 +8,7 @@ function CheckLastExitCode {
function RunInspectCode {
$outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml')
- # 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
+ 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
CheckLastExitCode
[xml]$xml = Get-Content "$outputPath"
@@ -40,16 +39,21 @@ function RunCleanupCode {
# When running in cibuild for a pull request, this reformats only the files changed in the PR and fails if the reformat produces changes.
if ($env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT) {
- Write-Output "Running code cleanup on changed files in pull request"
-
# In the past, we used $env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT for the merge commit hash. That is the pinned hash at the time the build is enqueued.
# When a force-push happens after that, while the build hasn't yet started, this hash becomes invalid during the build, resulting in a lookup error.
- # To prevent failing the build for unobvious reasons we use HEAD, which is always the latest version.
- $mergeCommitHash = git rev-parse "HEAD"
- $targetCommitHash = git rev-parse "$env:APPVEYOR_REPO_BRANCH"
+ # To prevent failing the build for unobvious reasons we use HEAD, which is always a detached head (the PR merge result).
+
+ $headCommitHash = git rev-parse HEAD
+ CheckLastExitCode
- dotnet regitlint -s JsonApiDotNetCore.sln --print-command --disable-jb-path-hack --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $mergeCommitHash -b $targetCommitHash --fail-on-diff --print-diff
+ $baseCommitHash = git rev-parse "$env:APPVEYOR_REPO_BRANCH"
CheckLastExitCode
+
+ if ($baseCommitHash -ne $headCommitHash) {
+ Write-Output "Running code cleanup on commit range $baseCommitHash..$headCommitHash in pull request."
+ dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $headCommitHash -b $baseCommitHash --fail-on-diff --print-diff
+ CheckLastExitCode
+ }
}
}
@@ -100,8 +104,11 @@ CheckLastExitCode
dotnet build -c Release
CheckLastExitCode
-RunInspectCode
-RunCleanupCode
+# https://youtrack.jetbrains.com/issue/RSRP-488628/Breaking-InspectCode-fails-with-Roslyn-Worker-process-exited-unexpectedly-after-update
+if ($env:APPVEYOR_BUILD_WORKER_IMAGE -ne 'Ubuntu') {
+ RunInspectCode
+ RunCleanupCode
+}
dotnet test -c Release --no-build --collect:"XPlat Code Coverage"
CheckLastExitCode
diff --git a/Directory.Build.props b/Directory.Build.props
index 5b311343c7..99319a64e6 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -4,9 +4,9 @@
6.0.*
6.0.*
6.0.*
- 4.1.*
+ 4.3.*
2.14.1
- 5.0.1
+ 5.1.0
$(MSBuildThisFileDirectory)CodingGuidelines.ruleset
9999
enable
@@ -16,8 +16,8 @@
-
-
+
+
@@ -34,7 +34,7 @@
3.1.2
- 4.17.2
- 17.1.0
+ 4.18.2
+ 17.3.1
diff --git a/JsonApiDotNetCore.sln b/JsonApiDotNetCore.sln
index 0a8ed12d2a..2f8e9f9127 100644
--- a/JsonApiDotNetCore.sln
+++ b/JsonApiDotNetCore.sln
@@ -10,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
+ CodingGuidelines.ruleset = CodingGuidelines.ruleset
CSharpGuidelinesAnalyzer.config = CSharpGuidelinesAnalyzer.config
Directory.Build.props = Directory.Build.props
EndProjectSection
@@ -46,14 +47,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestBuildingBlocks", "test\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore.SourceGenerators", "src\JsonApiDotNetCore.SourceGenerators\JsonApiDotNetCore.SourceGenerators.csproj", "{952C0FDE-AFC8-455C-986F-6CC882ED8953}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGeneratorDebugger", "test\SourceGeneratorDebugger\SourceGeneratorDebugger.csproj", "{87D066F9-3540-4AC7-A748-134900969EE5}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGeneratorTests", "test\SourceGeneratorTests\SourceGeneratorTests.csproj", "{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore.Annotations", "src\JsonApiDotNetCore.Annotations\JsonApiDotNetCore.Annotations.csproj", "{83FF097C-C8C6-477B-9FAB-DF99B84978B5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DatabasePerTenantExample", "src\Examples\DatabasePerTenantExample\DatabasePerTenantExample.csproj", "{60334658-BE51-43B3-9C4D-F2BBF56C89CE}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnnotationTests", "test\AnnotationTests\AnnotationTests.csproj", "{24B0C12F-38CD-4245-8785-87BEFAD55B00}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -232,18 +233,6 @@ Global
{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
@@ -280,6 +269,18 @@ Global
{60334658-BE51-43B3-9C4D-F2BBF56C89CE}.Release|x64.Build.0 = Release|Any CPU
{60334658-BE51-43B3-9C4D-F2BBF56C89CE}.Release|x86.ActiveCfg = Release|Any CPU
{60334658-BE51-43B3-9C4D-F2BBF56C89CE}.Release|x86.Build.0 = Release|Any CPU
+ {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Debug|x64.Build.0 = Debug|Any CPU
+ {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Debug|x86.Build.0 = Debug|Any CPU
+ {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Release|Any CPU.Build.0 = Release|Any CPU
+ {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Release|x64.ActiveCfg = Release|Any CPU
+ {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Release|x64.Build.0 = Release|Any CPU
+ {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Release|x86.ActiveCfg = Release|Any CPU
+ {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -299,10 +300,10 @@ Global
{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}
{60334658-BE51-43B3-9C4D-F2BBF56C89CE} = {026FBC6C-AF76-4568-9B87-EC73457899FD}
+ {24B0C12F-38CD-4245-8785-87BEFAD55B00} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A2421882-8F0A-4905-928F-B550B192F9A4}
diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings
index 2a7eb28d9b..05a03c584b 100644
--- a/JsonApiDotNetCore.sln.DotSettings
+++ b/JsonApiDotNetCore.sln.DotSettings
@@ -3,7 +3,7 @@
// $EXPR$ -- source expression
// $NAME$ -- source name (string literal or 'nameof' expression)
// $MESSAGE$ -- string literal in the form of "$NAME$ != null"
-JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
+JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$);
199
5000
99
@@ -54,6 +54,8 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
WARNING
WARNING
WARNING
+ HINT
+ WARNING
DO_NOT_SHOW
HINT
SUGGESTION
@@ -598,12 +600,12 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
CSHARP
False
Replace argument null check with Guard clause
- JsonApiDotNetCore.ArgumentGuard.NotNull($argument$, nameof($argument$));
+ JsonApiDotNetCore.ArgumentGuard.NotNull($argument$);
$left$ = $right$;
$left$ = $right$ ?? throw new ArgumentNullException(nameof($argument$));
WARNING
True
- Replace classic argument null check with Guard clause
+ Replace argument == null check with Guard clause
True
True
False
@@ -613,7 +615,7 @@ $left$ = $right$;
CSHARP
False
Replace argument null check with Guard clause
- JsonApiDotNetCore.ArgumentGuard.NotNull($argument$, nameof($argument$));
+ JsonApiDotNetCore.ArgumentGuard.NotNull($argument$);
if ($argument$ == null) throw new ArgumentNullException(nameof($argument$));
WARNING
True
@@ -629,6 +631,19 @@ $left$ = $right$;
$collection$.IsNullOrEmpty()
$collection$ == null || !$collection$.Any()
WARNING
+ True
+ Replace argument is null check with Guard clause
+ True
+ True
+ False
+
+ IdentifierPlaceholder
+ True
+ CSHARP
+ False
+ JsonApiDotNetCore.ArgumentGuard.NotNull($argument$);
+ if ($argument$ is null) throw new ArgumentNullException(nameof($argument$));
+ WARNING
True
True
True
diff --git a/README.md b/README.md
index e2291f080e..26432ee909 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ These are some steps you can take to help you understand what this project is an
- [Embercasts: Full Stack Ember with ASP.NET Core](https://www.embercasts.com/course/full-stack-ember-with-dotnet/watch/whats-in-this-course-cs) (paid course, 2017)
### Official documentation
-- [The JSON:API specification](https://jsonapi.org/format/1.1/)
+- [The JSON:API specification](https://jsonapi.org/format/)
- [JsonApiDotNetCore website](https://www.jsonapi.net/)
- [Roadmap](ROADMAP.md)
@@ -84,7 +84,10 @@ See also our [versioning policy](./VERSIONING_POLICY.md).
| | | Core 3.1 | 5 |
| | | 5 | 5 |
| | | 6 | 5 |
-| v5.x | Stable | 6 | 6 |
+| 5.0.0-5.0.2 | Stable | 6 | 6 |
+| 5.0.3+ | Stable | 6 | 6 |
+| | | 6 | 7 |
+| | | 7 | 7 |
## Contributing
diff --git a/WarningSeverities.DotSettings b/WarningSeverities.DotSettings
index f4a9ae32e8..0d4eeba96f 100644
--- a/WarningSeverities.DotSettings
+++ b/WarningSeverities.DotSettings
@@ -87,7 +87,6 @@
WARNING
WARNING
WARNING
- WARNING
WARNING
WARNING
WARNING
diff --git a/benchmarks/Benchmarks.csproj b/benchmarks/Benchmarks.csproj
index 4bde435c15..3958713af4 100644
--- a/benchmarks/Benchmarks.csproj
+++ b/benchmarks/Benchmarks.csproj
@@ -2,6 +2,7 @@
Exe
$(TargetFrameworkName)
+ true
@@ -9,7 +10,9 @@
-
-
+
+
+
+
diff --git a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs
index d28684e27b..99adce73cb 100644
--- a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs
+++ b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs
@@ -7,6 +7,7 @@
namespace Benchmarks.Deserialization;
[MarkdownExporter]
+[MemoryDiagnoser]
// ReSharper disable once ClassCanBeSealed.Global
public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
diff --git a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs
index 23a6205bf5..e503a329bb 100644
--- a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs
+++ b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs
@@ -7,6 +7,7 @@
namespace Benchmarks.Deserialization;
[MarkdownExporter]
+[MemoryDiagnoser]
// ReSharper disable once ClassCanBeSealed.Global
public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
{
diff --git a/benchmarks/QueryString/QueryStringParserBenchmarks.cs b/benchmarks/QueryString/QueryStringParserBenchmarks.cs
index efa4f12659..4218c2e3dc 100644
--- a/benchmarks/QueryString/QueryStringParserBenchmarks.cs
+++ b/benchmarks/QueryString/QueryStringParserBenchmarks.cs
@@ -1,13 +1,12 @@
using System.ComponentModel.Design;
using BenchmarkDotNet.Attributes;
+using Benchmarks.Tools;
using JsonApiDotNetCore;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Middleware;
using JsonApiDotNetCore.QueryStrings;
using JsonApiDotNetCore.QueryStrings.Internal;
using JsonApiDotNetCore.Resources;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging.Abstractions;
namespace Benchmarks.QueryString;
@@ -71,31 +70,9 @@ public void DescendingSort()
[Benchmark]
public void ComplexQuery()
{
- 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);
- });
- }
-
- 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();
+ 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";
- public void SetQueryString(string queryString)
- {
- Query = new QueryCollection(QueryHelpers.ParseQuery(queryString));
- }
+ _queryStringAccessor.SetQueryString(queryString);
+ _queryStringReader.ReadAll(null);
}
}
diff --git a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs
index 7076ca5cb8..471c9604c7 100644
--- a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs
+++ b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs
@@ -9,6 +9,7 @@
namespace Benchmarks.Serialization;
[MarkdownExporter]
+[MemoryDiagnoser]
// ReSharper disable once ClassCanBeSealed.Global
public class OperationsSerializationBenchmarks : SerializationBenchmarkBase
{
diff --git a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs
index 12f5c2e788..a985bd5936 100644
--- a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs
+++ b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs
@@ -12,6 +12,7 @@
namespace Benchmarks.Serialization;
[MarkdownExporter]
+[MemoryDiagnoser]
// ReSharper disable once ClassCanBeSealed.Global
public class ResourceSerializationBenchmarks : SerializationBenchmarkBase
{
diff --git a/benchmarks/Serialization/SerializationBenchmarkBase.cs b/benchmarks/Serialization/SerializationBenchmarkBase.cs
index e1bcb10843..d9cfefd0b6 100644
--- a/benchmarks/Serialization/SerializationBenchmarkBase.cs
+++ b/benchmarks/Serialization/SerializationBenchmarkBase.cs
@@ -1,18 +1,14 @@
-using System.Collections.Immutable;
using System.Text.Json;
using System.Text.Json.Serialization;
+using Benchmarks.Tools;
using JetBrains.Annotations;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Middleware;
using JsonApiDotNetCore.Queries;
-using JsonApiDotNetCore.Queries.Expressions;
using JsonApiDotNetCore.Queries.Internal;
-using JsonApiDotNetCore.QueryStrings;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
-using JsonApiDotNetCore.Serialization.Objects;
using JsonApiDotNetCore.Serialization.Response;
-using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions;
namespace Benchmarks.Serialization;
@@ -45,9 +41,9 @@ protected SerializationBenchmarkBase()
// ReSharper restore VirtualMemberCallInConstructor
var linkBuilder = new FakeLinkBuilder();
- var metaBuilder = new FakeMetaBuilder();
+ var metaBuilder = new NoMetaBuilder();
IQueryConstraintProvider[] constraintProviders = Array.Empty();
- var resourceDefinitionAccessor = new FakeResourceDefinitionAccessor();
+ var resourceDefinitionAccessor = new NeverResourceDefinitionAccessor();
var sparseFieldSetCache = new SparseFieldSetCache(constraintProviders, resourceDefinitionAccessor);
var requestQueryStringAccessor = new FakeRequestQueryStringAccessor();
@@ -122,141 +118,4 @@ public sealed class OutgoingResource : Identifiable
[HasMany]
public ISet Multi5 { get; set; } = null!;
}
-
- private sealed class FakeResourceDefinitionAccessor : IResourceDefinitionAccessor
- {
- public IImmutableSet OnApplyIncludes(ResourceType resourceType, IImmutableSet existingIncludes)
- {
- return existingIncludes;
- }
-
- public FilterExpression? OnApplyFilter(ResourceType resourceType, FilterExpression? existingFilter)
- {
- return existingFilter;
- }
-
- public SortExpression? OnApplySort(ResourceType resourceType, SortExpression? existingSort)
- {
- return existingSort;
- }
-
- public PaginationExpression? OnApplyPagination(ResourceType resourceType, PaginationExpression? existingPagination)
- {
- return existingPagination;
- }
-
- public SparseFieldSetExpression? OnApplySparseFieldSet(ResourceType resourceType, SparseFieldSetExpression? existingSparseFieldSet)
- {
- return existingSparseFieldSet;
- }
-
- public object? GetQueryableHandlerForQueryStringParameter(Type resourceClrType, string parameterName)
- {
- return null;
- }
-
- public IDictionary? GetMeta(ResourceType resourceType, IIdentifiable resourceInstance)
- {
- return null;
- }
-
- public Task OnPrepareWriteAsync(TResource resource, 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 OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds,
- WriteOperationKind writeOperation, CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.CompletedTask;
- }
-
- public Task OnAddToRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds,
- 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 OnWritingAsync(TResource resource, WriteOperationKind writeOperation, 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 void OnDeserialize(IIdentifiable resource)
- {
- }
-
- public void OnSerialize(IIdentifiable resource)
- {
- }
- }
-
- private sealed class FakeLinkBuilder : ILinkBuilder
- {
- public TopLevelLinks GetTopLevelLinks()
- {
- return new TopLevelLinks
- {
- Self = "TopLevel:Self"
- };
- }
-
- public ResourceLinks GetResourceLinks(ResourceType resourceType, IIdentifiable resource)
- {
- return new ResourceLinks
- {
- Self = "Resource:Self"
- };
- }
-
- public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable leftResource)
- {
- return new RelationshipLinks
- {
- Self = "Relationship:Self",
- Related = "Relationship:Related"
- };
- }
- }
-
- private sealed class FakeMetaBuilder : IMetaBuilder
- {
- public void Add(IDictionary values)
- {
- }
-
- public IDictionary? Build()
- {
- return null;
- }
- }
-
- private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor
- {
- public IQueryCollection Query { get; } = new QueryCollection(0);
- }
}
diff --git a/benchmarks/Tools/FakeLinkBuilder.cs b/benchmarks/Tools/FakeLinkBuilder.cs
new file mode 100644
index 0000000000..3468237507
--- /dev/null
+++ b/benchmarks/Tools/FakeLinkBuilder.cs
@@ -0,0 +1,39 @@
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+using JsonApiDotNetCore.Serialization.Objects;
+using JsonApiDotNetCore.Serialization.Response;
+using Microsoft.AspNetCore.Http;
+
+namespace Benchmarks.Tools;
+
+///
+/// Renders hard-coded fake links, without depending on .
+///
+internal sealed class FakeLinkBuilder : ILinkBuilder
+{
+ public TopLevelLinks GetTopLevelLinks()
+ {
+ return new TopLevelLinks
+ {
+ Self = "TopLevel:Self"
+ };
+ }
+
+ public ResourceLinks GetResourceLinks(ResourceType resourceType, IIdentifiable resource)
+ {
+ return new ResourceLinks
+ {
+ Self = "Resource:Self"
+ };
+ }
+
+ public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable leftResource)
+ {
+ return new RelationshipLinks
+ {
+ Self = "Relationship:Self",
+ Related = "Relationship:Related"
+ };
+ }
+}
diff --git a/benchmarks/Tools/FakeRequestQueryStringAccessor.cs b/benchmarks/Tools/FakeRequestQueryStringAccessor.cs
new file mode 100644
index 0000000000..8b2b5540a1
--- /dev/null
+++ b/benchmarks/Tools/FakeRequestQueryStringAccessor.cs
@@ -0,0 +1,18 @@
+using JsonApiDotNetCore.QueryStrings;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.WebUtilities;
+
+namespace Benchmarks.Tools;
+
+///
+/// Enables to inject a query string, instead of obtaining it from .
+///
+internal sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor
+{
+ public IQueryCollection Query { get; private set; } = new QueryCollection();
+
+ public void SetQueryString(string queryString)
+ {
+ Query = new QueryCollection(QueryHelpers.ParseQuery(queryString));
+ }
+}
diff --git a/benchmarks/Tools/NeverResourceDefinitionAccessor.cs b/benchmarks/Tools/NeverResourceDefinitionAccessor.cs
new file mode 100644
index 0000000000..6e93519dae
--- /dev/null
+++ b/benchmarks/Tools/NeverResourceDefinitionAccessor.cs
@@ -0,0 +1,103 @@
+using System.Collections.Immutable;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Middleware;
+using JsonApiDotNetCore.Queries.Expressions;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+
+namespace Benchmarks.Tools;
+
+///
+/// Never calls into instances.
+///
+internal sealed class NeverResourceDefinitionAccessor : IResourceDefinitionAccessor
+{
+ public IImmutableSet OnApplyIncludes(ResourceType resourceType, IImmutableSet existingIncludes)
+ {
+ return existingIncludes;
+ }
+
+ public FilterExpression? OnApplyFilter(ResourceType resourceType, FilterExpression? existingFilter)
+ {
+ return existingFilter;
+ }
+
+ public SortExpression? OnApplySort(ResourceType resourceType, SortExpression? existingSort)
+ {
+ return existingSort;
+ }
+
+ public PaginationExpression? OnApplyPagination(ResourceType resourceType, PaginationExpression? existingPagination)
+ {
+ return existingPagination;
+ }
+
+ public SparseFieldSetExpression? OnApplySparseFieldSet(ResourceType resourceType, SparseFieldSetExpression? existingSparseFieldSet)
+ {
+ return existingSparseFieldSet;
+ }
+
+ public object? GetQueryableHandlerForQueryStringParameter(Type resourceClrType, string parameterName)
+ {
+ return null;
+ }
+
+ public IDictionary? GetMeta(ResourceType resourceType, IIdentifiable resourceInstance)
+ {
+ return null;
+ }
+
+ public Task OnPrepareWriteAsync(TResource resource, 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 OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds,
+ WriteOperationKind writeOperation, CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.CompletedTask;
+ }
+
+ public Task OnAddToRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds,
+ 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 OnWritingAsync(TResource resource, WriteOperationKind writeOperation, 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 void OnDeserialize(IIdentifiable resource)
+ {
+ }
+
+ public void OnSerialize(IIdentifiable resource)
+ {
+ }
+}
diff --git a/benchmarks/Tools/NoMetaBuilder.cs b/benchmarks/Tools/NoMetaBuilder.cs
new file mode 100644
index 0000000000..db3ed7857e
--- /dev/null
+++ b/benchmarks/Tools/NoMetaBuilder.cs
@@ -0,0 +1,18 @@
+using JsonApiDotNetCore.Serialization.Response;
+
+namespace Benchmarks.Tools;
+
+///
+/// Doesn't produce any top-level meta.
+///
+internal sealed class NoMetaBuilder : IMetaBuilder
+{
+ public void Add(IDictionary values)
+ {
+ }
+
+ public IDictionary? Build()
+ {
+ return null;
+ }
+}
diff --git a/cleanupcode.ps1 b/cleanupcode.ps1
index 6db01a863a..bab8b82af1 100644
--- a/cleanupcode.ps1
+++ b/cleanupcode.ps1
@@ -1,17 +1,44 @@
#Requires -Version 7.0
-# This script reformats the entire codebase to make it compliant with our coding guidelines.
+# This script reformats (part of) the codebase to make it compliant with our coding guidelines.
-dotnet tool restore
+param(
+ # Git branch name or base commit hash to reformat only the subset of changed files. Omit for all files.
+ [string] $revision
+)
-if ($LASTEXITCODE -ne 0) {
- throw "Tool restore failed with exit code $LASTEXITCODE"
+function VerifySuccessExitCode {
+ if ($LastExitCode -ne 0) {
+ throw "Command failed with exit code $LastExitCode."
+ }
}
+dotnet tool restore
+VerifySuccessExitCode
+
dotnet restore
+VerifySuccessExitCode
-if ($LASTEXITCODE -ne 0) {
- throw "Package restore failed with exit code $LASTEXITCODE"
-}
+if ($revision) {
+ $headCommitHash = git rev-parse HEAD
+ VerifySuccessExitCode
-dotnet regitlint -s JsonApiDotNetCore.sln --print-command --disable-jb-path-hack --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN
+ $baseCommitHash = git rev-parse $revision
+ VerifySuccessExitCode
+
+ if ($baseCommitHash -eq $headCommitHash) {
+ Write-Output "Running code cleanup on staged/unstaged files."
+ dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified
+ VerifySuccessExitCode
+ }
+ else {
+ Write-Output "Running code cleanup on commit range $baseCommitHash..$headCommitHash, including staged/unstaged files."
+ dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified,commits -a $headCommitHash -b $baseCommitHash
+ VerifySuccessExitCode
+ }
+}
+else {
+ Write-Output "Running code cleanup on all files."
+ dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN
+ VerifySuccessExitCode
+}
diff --git a/docs/internals/queries.md b/docs/internals/queries.md
index 46005f489c..5c2b238a18 100644
--- a/docs/internals/queries.md
+++ b/docs/internals/queries.md
@@ -22,7 +22,7 @@ Processing a request involves the following steps:
- `JsonApiResourceService` contains no more usage of `IQueryable`.
- `EntityFrameworkCoreRepository` delegates to `QueryableBuilder` to transform the `QueryLayer` tree into `IQueryable` expression trees.
`QueryBuilder` depends on `QueryClauseBuilder` implementations that visit the tree nodes, transforming them to `System.Linq.Expression` equivalents.
- The `IQueryable` expression trees are executed by Entity Framework Core, which produces SQL statements out of them.
+ The `IQueryable` expression trees are passed to Entity Framework Core, which produces SQL statements out of them.
- `JsonApiWriter` transforms resource objects into json response.
# Example
@@ -30,17 +30,17 @@ To get a sense of what this all looks like, let's look at an example query strin
```
/api/v1/blogs?
- include=owner,articles.revisions.author&
- filter=has(articles)&
- sort=count(articles)&
+ include=owner,posts.comments.author&
+ filter=has(posts)&
+ sort=count(posts)&
page[number]=3&
fields[blogs]=title&
- filter[articles]=and(not(equals(author.firstName,null)),has(revisions))&
- sort[articles]=author.lastName&
- fields[articles]=url&
- filter[articles.revisions]=and(greaterThan(publishTime,'2001-01-01'),startsWith(author.firstName,'J'))&
- sort[articles.revisions]=-publishTime,author.lastName&
- fields[revisions]=publishTime
+ filter[posts]=and(not(equals(author.userName,null)),has(comments))&
+ sort[posts]=author.displayName&
+ fields[blogPosts]=url&
+ filter[posts.comments]=and(greaterThan(createdAt,'2001-01-01Z'),startsWith(author.userName,'J'))&
+ sort[posts.comments]=-createdAt,author.displayName&
+ fields[comments]=createdAt
```
After parsing, the set of scoped expressions is transformed into the following tree by `QueryLayerComposer`:
@@ -48,40 +48,50 @@ After parsing, the set of scoped expressions is transformed into the following t
```
QueryLayer
{
- Include: owner,articles.revisions
- Filter: has(articles)
- Sort: count(articles)
+ Include: owner,posts.comments.author
+ Filter: has(posts)
+ Sort: count(posts)
Pagination: Page number: 3, size: 5
- Projection
+ Selection
{
- title
- id
- owner: QueryLayer
+ FieldSelectors
{
- Sort: id
- Pagination: Page number: 1, size: 5
- }
- articles: QueryLayer
- {
- Filter: and(not(equals(author.firstName,null)),has(revisions))
- Sort: author.lastName
- Pagination: Page number: 1, size: 5
- Projection
+ title
+ id
+ posts: QueryLayer
{
- url
- id
- revisions: QueryLayer
+ Filter: and(not(equals(author.userName,null)),has(comments))
+ Sort: author.displayName
+ Pagination: Page number: 1, size: 5
+ Selection
{
- Filter: and(greaterThan(publishTime,'2001-01-01'),startsWith(author.firstName,'J'))
- Sort: -publishTime,author.lastName
- Pagination: Page number: 1, size: 5
- Projection
+ FieldSelectors
{
- publishTime
+ url
id
+ comments: QueryLayer
+ {
+ Filter: and(greaterThan(createdAt,'2001-01-01'),startsWith(author.userName,'J'))
+ Sort: -createdAt,author.displayName
+ Pagination: Page number: 1, size: 5
+ Selection
+ {
+ FieldSelectors
+ {
+ createdAt
+ id
+ author: QueryLayer
+ {
+ }
+ }
+ }
+ }
}
}
}
+ owner: QueryLayer
+ {
+ }
}
}
}
@@ -90,36 +100,86 @@ QueryLayer
Next, the repository translates this into a LINQ query that the following C# code would represent:
```c#
-var query = dbContext.Blogs
+IQueryable query = dbContext.Blogs
+ .Include("Posts.Comments.Author")
.Include("Owner")
- .Include("Articles.Revisions")
- .Where(blog => blog.Articles.Any())
- .OrderBy(blog => blog.Articles.Count)
+ .Where(blog => blog.Posts.Any())
+ .OrderBy(blog => blog.Posts.Count)
.Skip(10)
.Take(5)
.Select(blog => new Blog
{
Title = blog.Title,
Id = blog.Id,
- Owner = blog.Owner,
- Articles = new List(blog.Articles
- .Where(article => article.Author.FirstName != null && article.Revisions.Any())
- .OrderBy(article => article.Author.LastName)
+ Posts = blog.Posts
+ .Where(blogPost => blogPost.Author.UserName != null && blogPost.Comments.Any())
+ .OrderBy(blogPost => blogPost.Author.DisplayName)
.Take(5)
- .Select(article => new Article
+ .Select(blogPost => new BlogPost
{
- Url = article.Url,
- Id = article.Id,
- Revisions = new HashSet(article.Revisions
- .Where(revision => revision.PublishTime > DateTime.Parse("2001-01-01") && revision.Author.FirstName.StartsWith("J"))
- .OrderByDescending(revision => revision.PublishTime)
- .ThenBy(revision => revision.Author.LastName)
+ Url = blogPost.Url,
+ Id = blogPost.Id,
+ Comments = blogPost.Comments
+ .Where(comment => comment.CreatedAt > DateTime.Parse("2001-01-01Z") &&
+ comment.Author.UserName.StartsWith("J"))
+ .OrderByDescending(comment => comment.CreatedAt)
+ .ThenBy(comment => comment.Author.DisplayName)
.Take(5)
- .Select(revision => new Revision
+ .Select(comment => new Comment
{
- PublishTime = revision.PublishTime,
- Id = revision.Id
- }))
- }))
+ CreatedAt = comment.CreatedAt,
+ Id = comment.Id,
+ Author = comment.Author
+ }).ToHashSet()
+ }).ToList(),
+ Owner = blog.Owner
});
```
+
+The LINQ query gets translated by Entity Framework Core into the following SQL:
+
+```sql
+SELECT t."Title", t."Id", a."Id", t2."Url", t2."Id", t2."Id0", t2."CreatedAt", t2."Id1", t2."Id00", t2."DateOfBirth", t2."DisplayName", t2."EmailAddress", t2."Password", t2."PersonId", t2."PreferencesId", t2."UserName", a."DateOfBirth", a."DisplayName", a."EmailAddress", a."Password", a."PersonId", a."PreferencesId", a."UserName"
+FROM (
+ SELECT b."Id", b."OwnerId", b."Title", (
+ SELECT COUNT(*)::INT
+ FROM "Posts" AS p0
+ WHERE b."Id" = p0."ParentId") AS c
+ FROM "Blogs" AS b
+ WHERE EXISTS (
+ SELECT 1
+ FROM "Posts" AS p
+ WHERE b."Id" = p."ParentId")
+ ORDER BY (
+ SELECT COUNT(*)::INT
+ FROM "Posts" AS p0
+ WHERE b."Id" = p0."ParentId")
+ LIMIT @__Create_Item1_1 OFFSET @__Create_Item1_0
+) AS t
+LEFT JOIN "Accounts" AS a ON t."OwnerId" = a."Id"
+LEFT JOIN LATERAL (
+ SELECT t0."Url", t0."Id", t0."Id0", t1."CreatedAt", t1."Id" AS "Id1", t1."Id0" AS "Id00", t1."DateOfBirth", t1."DisplayName", t1."EmailAddress", t1."Password", t1."PersonId", t1."PreferencesId", t1."UserName", t0."DisplayName" AS "DisplayName0", t1."ParentId"
+ FROM (
+ SELECT p1."Url", p1."Id", a0."Id" AS "Id0", a0."DisplayName"
+ FROM "Posts" AS p1
+ LEFT JOIN "Accounts" AS a0 ON p1."AuthorId" = a0."Id"
+ WHERE (t."Id" = p1."ParentId") AND (((a0."UserName" IS NOT NULL)) AND EXISTS (
+ SELECT 1
+ FROM "Comments" AS c
+ WHERE p1."Id" = c."ParentId"))
+ ORDER BY a0."DisplayName"
+ LIMIT @__Create_Item1_1
+ ) AS t0
+ LEFT JOIN (
+ SELECT t3."CreatedAt", t3."Id", t3."Id0", t3."DateOfBirth", t3."DisplayName", t3."EmailAddress", t3."Password", t3."PersonId", t3."PreferencesId", t3."UserName", t3."ParentId"
+ FROM (
+ SELECT c0."CreatedAt", c0."Id", a1."Id" AS "Id0", a1."DateOfBirth", a1."DisplayName", a1."EmailAddress", a1."Password", a1."PersonId", a1."PreferencesId", a1."UserName", c0."ParentId", ROW_NUMBER() OVER(PARTITION BY c0."ParentId" ORDER BY c0."CreatedAt" DESC, a1."DisplayName") AS row
+ FROM "Comments" AS c0
+ LEFT JOIN "Accounts" AS a1 ON c0."AuthorId" = a1."Id"
+ WHERE (c0."CreatedAt" > @__Create_Item1_2) AND ((@__Create_Item1_3 = '') OR (((a1."UserName" IS NOT NULL)) AND ((a1."UserName" LIKE @__Create_Item1_3 || '%' ESCAPE '') AND (left(a1."UserName", length(@__Create_Item1_3))::text = @__Create_Item1_3::text))))
+ ) AS t3
+ WHERE t3.row <= @__Create_Item1_1
+ ) AS t1 ON t0."Id" = t1."ParentId"
+) AS t2 ON TRUE
+ORDER BY t.c, t."Id", a."Id", t2."DisplayName0", t2."Id", t2."Id0", t2."ParentId", t2."CreatedAt" DESC, t2."DisplayName", t2."Id1"
+```
diff --git a/docs/usage/extensibility/controllers.md b/docs/usage/extensibility/controllers.md
index 0c71f45090..7e54d3fb9c 100644
--- a/docs/usage/extensibility/controllers.md
+++ b/docs/usage/extensibility/controllers.md
@@ -39,7 +39,7 @@ DELETE http://localhost:14140/articles/1 HTTP/1.1
```json
{
"links": {
- "self": "/articles"
+ "self": "/articles/1"
},
"errors": [
{
diff --git a/docs/usage/extensibility/resource-definitions.md b/docs/usage/extensibility/resource-definitions.md
index af4f8a27c5..6bc16b869e 100644
--- a/docs/usage/extensibility/resource-definitions.md
+++ b/docs/usage/extensibility/resource-definitions.md
@@ -34,10 +34,10 @@ from Entity Framework Core `IQueryable` execution.
### Excluding fields
-There are some cases where you want attributes (or relationships) conditionally excluded from your resource response.
+There are some cases where you want attributes or relationships conditionally excluded from your resource response.
For example, you may accept some sensitive data that should only be exposed to administrators after creation.
-**Note:** to exclude attributes unconditionally, use `[Attr(Capabilities = ~AttrCapabilities.AllowView)]` on a resource class property.
+**Note:** to exclude fields unconditionally, [attribute capabilities](~/usage/resources/attributes.md#capabilities) and [relationship capabilities](~/usage/resources/relationships.md#capabilities) can be used instead.
```c#
public class UserDefinition : JsonApiResourceDefinition
diff --git a/docs/usage/reading/including-relationships.md b/docs/usage/reading/including-relationships.md
index f22d2321aa..0b69a007c1 100644
--- a/docs/usage/reading/including-relationships.md
+++ b/docs/usage/reading/including-relationships.md
@@ -1,6 +1,6 @@
# Including Relationships
-JsonApiDotNetCore supports [request include params](http://jsonapi.org/format/#fetching-includes) out of the box,
+JsonApiDotNetCore supports [request include params](https://jsonapi.org/format/#fetching-includes) out of the box,
for side-loading related resources.
```http
diff --git a/docs/usage/resources/attributes.md b/docs/usage/resources/attributes.md
index 669dba0892..77c6ff9566 100644
--- a/docs/usage/resources/attributes.md
+++ b/docs/usage/resources/attributes.md
@@ -43,9 +43,10 @@ options.DefaultAttrCapabilities = AttrCapabilities.None; // default: All
This can be overridden per attribute.
-### Viewability
+### AllowView
-Attributes can be marked to allow returning their value in responses. When not allowed and requested using `?fields[]=`, it results in an HTTP 400 response.
+Indicates whether the attribute value can be returned in responses. When not allowed and requested using `?fields[]=`, it results in an HTTP 400 response.
+Otherwise, the attribute is silently omitted.
```c#
#nullable enable
@@ -57,45 +58,59 @@ public class User : Identifiable
}
```
-### Creatability
+### AllowFilter
-Attributes can be marked as creatable, which will allow `POST` requests to assign a value to them. When sent but not allowed, an HTTP 422 response is returned.
+Indicates whether the attribute can be filtered on. When not allowed and used in `?filter=`, an HTTP 400 is returned.
```c#
#nullable enable
public class Person : Identifiable
{
- [Attr(Capabilities = AttrCapabilities.AllowCreate)]
- public string? CreatorName { get; set; }
+ [Attr(Capabilities = AttrCapabilities.AllowFilter)]
+ public string? FirstName { get; set; }
}
```
-### Changeability
+### AllowSort
-Attributes can be marked as changeable, which will allow `PATCH` requests to update them. When sent but not allowed, an HTTP 422 response is returned.
+Indicates whether the attribute can be sorted on. When not allowed and used in `?sort=`, an HTTP 400 is returned.
```c#
#nullable enable
public class Person : Identifiable
{
- [Attr(Capabilities = AttrCapabilities.AllowChange)]
- public string? FirstName { get; set; };
+ [Attr(Capabilities = ~AttrCapabilities.AllowSort)]
+ public string? FirstName { get; set; }
}
```
-### Filter/Sort-ability
+### AllowCreate
-Attributes can be marked to allow filtering and/or sorting. When not allowed, it results in an HTTP 400 response.
+Indicates whether POST requests can assign the attribute value. When sent but not allowed, an HTTP 422 response is returned.
```c#
#nullable enable
public class Person : Identifiable
{
- [Attr(Capabilities = AttrCapabilities.AllowSort | AttrCapabilities.AllowFilter)]
- public string? FirstName { get; set; }
+ [Attr(Capabilities = AttrCapabilities.AllowCreate)]
+ public string? CreatorName { get; set; }
+}
+```
+
+### AllowChange
+
+Indicates whether PATCH requests can update the attribute value. When sent but not allowed, an HTTP 422 response is returned.
+
+```c#
+#nullable enable
+
+public class Person : Identifiable
+{
+ [Attr(Capabilities = AttrCapabilities.AllowChange)]
+ public string? FirstName { get; set; };
}
```
diff --git a/docs/usage/resources/relationships.md b/docs/usage/resources/relationships.md
index 8776041e98..1787bbd8ac 100644
--- a/docs/usage/resources/relationships.md
+++ b/docs/usage/resources/relationships.md
@@ -3,7 +3,7 @@
A relationship is a named link between two resource types, including a direction.
They are similar to [navigation properties in Entity Framework Core](https://docs.microsoft.com/en-us/ef/core/modeling/relationships).
-Relationships come in three flavors: to-one, to-many and many-to-many.
+Relationships come in two flavors: to-one and to-many.
The left side of a relationship is where the relationship is declared, the right side is the resource type it points to.
## HasOne
@@ -22,10 +22,14 @@ public class TodoItem : Identifiable
The left side of this relationship is of type `TodoItem` (public name: "todoItems") and the right side is of type `Person` (public name: "persons").
-### Required one-to-one relationships in Entity Framework Core
+### One-to-one relationships in Entity Framework Core
-By default, Entity Framework Core generates an identifying foreign key for a required 1-to-1 relationship.
-This means no foreign key column is generated, instead the primary keys point to each other directly.
+By default, Entity Framework Core tries to generate an *identifying foreign key* for a one-to-one relationship whenever possible.
+In that case, no foreign key column is generated. Instead the primary keys point to each other directly.
+
+**That mechanism does not make sense for JSON:API, because patching a relationship would result in also
+changing the identity of a resource. Naming the foreign key explicitly fixes the problem, which enforces
+to create a foreign key column.**
The next example defines that each car requires an engine, while an engine is optionally linked to a car.
@@ -51,18 +55,19 @@ public sealed class AppDbContext : DbContext
builder.Entity()
.HasOne(car => car.Engine)
.WithOne(engine => engine.Car)
- .HasForeignKey()
- .IsRequired();
+ .HasForeignKey();
}
}
```
Which results in Entity Framework Core generating the next database objects:
+
```sql
CREATE TABLE "Engine" (
"Id" integer GENERATED BY DEFAULT AS IDENTITY,
CONSTRAINT "PK_Engine" PRIMARY KEY ("Id")
);
+
CREATE TABLE "Cars" (
"Id" integer NOT NULL,
CONSTRAINT "PK_Cars" PRIMARY KEY ("Id"),
@@ -71,9 +76,7 @@ CREATE TABLE "Cars" (
);
```
-That mechanism does not make sense for JSON:API, because patching a relationship would result in also
-changing the identity of a resource. Naming the foreign key explicitly fixes the problem by forcing to
-create a foreign key column.
+To fix this, name the foreign key explicitly:
```c#
protected override void OnModelCreating(ModelBuilder builder)
@@ -81,17 +84,18 @@ protected override void OnModelCreating(ModelBuilder builder)
builder.Entity()
.HasOne(car => car.Engine)
.WithOne(engine => engine.Car)
- .HasForeignKey("EngineId") // Explicit foreign key name added
- .IsRequired();
+ .HasForeignKey("EngineId"); // <-- Explicit foreign key name added
}
```
Which generates the correct database objects:
+
```sql
CREATE TABLE "Engine" (
"Id" integer GENERATED BY DEFAULT AS IDENTITY,
CONSTRAINT "PK_Engine" PRIMARY KEY ("Id")
);
+
CREATE TABLE "Cars" (
"Id" integer GENERATED BY DEFAULT AS IDENTITY,
"EngineId" integer NOT NULL,
@@ -99,6 +103,99 @@ CREATE TABLE "Cars" (
CONSTRAINT "FK_Cars_Engine_EngineId" FOREIGN KEY ("EngineId") REFERENCES "Engine" ("Id")
ON DELETE CASCADE
);
+
+CREATE UNIQUE INDEX "IX_Cars_EngineId" ON "Cars" ("EngineId");
+```
+
+#### Optional one-to-one relationships in Entity Framework Core
+
+For optional one-to-one relationships, Entity Framework Core uses `DeleteBehavior.ClientSetNull` by default, instead of `DeleteBehavior.SetNull`.
+This means that Entity Framework Core tries to handle the cascading effects (by sending multiple SQL statements), instead of leaving it up to the database.
+Of course that's only going to work when all the related resources are loaded in the change tracker upfront, which is expensive because it requires fetching more data than necessary.
+
+The reason for this odd default is poor support in SQL Server, as explained [here](https://stackoverflow.com/questions/54326165/ef-core-why-clientsetnull-is-default-ondelete-behavior-for-optional-relations) and [here](https://learn.microsoft.com/en-us/ef/core/saving/cascade-delete#database-cascade-limitations).
+
+**Our [testing](https://github.com/json-api-dotnet/JsonApiDotNetCore/pull/1205) shows that these limitations don't exist when using PostgreSQL.
+Therefore the general advice is to map the delete behavior of optional one-to-one relationships explicitly with `.OnDelete(DeleteBehavior.SetNull)`. This is simpler and more efficient.**
+
+The next example defines that each car optionally has an engine, while an engine is optionally linked to a car.
+
+```c#
+#nullable enable
+
+public sealed class Car : Identifiable
+{
+ [HasOne]
+ public Engine? Engine { get; set; }
+}
+
+public sealed class Engine : Identifiable
+{
+ [HasOne]
+ public Car? Car { get; set; }
+}
+
+public sealed class AppDbContext : DbContext
+{
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ builder.Entity()
+ .HasOne(car => car.Engine)
+ .WithOne(engine => engine.Car)
+ .HasForeignKey("EngineId");
+ }
+}
+```
+
+Which results in Entity Framework Core generating the next database objects:
+
+```sql
+CREATE TABLE "Engines" (
+ "Id" integer GENERATED BY DEFAULT AS IDENTITY,
+ CONSTRAINT "PK_Engines" PRIMARY KEY ("Id")
+);
+
+CREATE TABLE "Cars" (
+ "Id" integer GENERATED BY DEFAULT AS IDENTITY,
+ "EngineId" integer NULL,
+ CONSTRAINT "PK_Cars" PRIMARY KEY ("Id"),
+ CONSTRAINT "FK_Cars_Engines_EngineId" FOREIGN KEY ("EngineId") REFERENCES "Engines" ("Id")
+);
+
+CREATE UNIQUE INDEX "IX_Cars_EngineId" ON "Cars" ("EngineId");
+```
+
+To fix this, set the delete behavior explicitly:
+
+```
+public sealed class AppDbContext : DbContext
+{
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ builder.Entity()
+ .HasOne(car => car.Engine)
+ .WithOne(engine => engine.Car)
+ .HasForeignKey("EngineId")
+ .OnDelete(DeleteBehavior.SetNull); // <-- Explicit delete behavior set
+ }
+}
+```
+
+Which generates the correct database objects:
+
+```sql
+CREATE TABLE "Engines" (
+ "Id" integer GENERATED BY DEFAULT AS IDENTITY,
+ CONSTRAINT "PK_Engines" PRIMARY KEY ("Id")
+);
+
+CREATE TABLE "Cars" (
+ "Id" integer GENERATED BY DEFAULT AS IDENTITY,
+ "EngineId" integer NULL,
+ CONSTRAINT "PK_Cars" PRIMARY KEY ("Id"),
+ CONSTRAINT "FK_Cars_Engines_EngineId" FOREIGN KEY ("EngineId") REFERENCES "Engines" ("Id") ON DELETE SET NULL
+);
+
CREATE UNIQUE INDEX "IX_Cars_EngineId" ON "Cars" ("EngineId");
```
@@ -160,7 +257,111 @@ public class TodoItem : Identifiable
}
```
-## Includibility
+## Capabilities
+
+_since v5.1_
+
+Default JSON:API relationship capabilities are specified in
+@JsonApiDotNetCore.Configuration.JsonApiOptions#JsonApiDotNetCore_Configuration_JsonApiOptions_DefaultHasOneCapabilities and
+@JsonApiDotNetCore.Configuration.JsonApiOptions#JsonApiDotNetCore_Configuration_JsonApiOptions_DefaultHasManyCapabilities:
+
+```c#
+options.DefaultHasOneCapabilities = HasOneCapabilities.None; // default: All
+options.DefaultHasManyCapabilities = HasManyCapabilities.None; // default: All
+```
+
+This can be overridden per relationship.
+
+### AllowView
+
+Indicates whether the relationship can be returned in responses. When not allowed and requested using `?fields[]=`, it results in an HTTP 400 response.
+Otherwise, the relationship (and its related resources, when included) are silently omitted.
+
+Note that this setting does not affect retrieving the related resources directly.
+
+```c#
+#nullable enable
+
+public class User : Identifiable
+{
+ [HasOne(Capabilities = ~HasOneCapabilities.AllowView)]
+ public LoginAccount Account { get; set; } = null!;
+}
+```
+
+### AllowInclude
+
+Indicates whether the relationship can be included. When not allowed and used in `?include=`, an HTTP 400 is returned.
+
+```c#
+#nullable enable
+
+public class User : Identifiable
+{
+ [HasMany(Capabilities = ~HasManyCapabilities.AllowInclude)]
+ public ISet Groups { get; set; } = new HashSet();
+}
+```
+
+### AllowFilter
+
+For to-many relationships only. Indicates whether it can be used in the `count()` and `has()` filter functions. When not allowed and used in `?filter=`, an HTTP 400 is returned.
+
+```c#
+#nullable enable
+
+public class User : Identifiable
+{
+ [HasMany(Capabilities = HasManyCapabilities.AllowFilter)]
+ public ISet Groups { get; set; } = new HashSet();
+}
+```
+
+### AllowSet
+
+Indicates whether POST and PATCH requests can replace the relationship. When sent but not allowed, an HTTP 422 response is returned.
+
+```c#
+#nullable enable
+
+public class User : Identifiable
+{
+ [HasOne(Capabilities = ~HasOneCapabilities.AllowSet)]
+ public LoginAccount Account { get; set; } = null!;
+}
+```
+
+### AllowAdd
+
+For to-many relationships only. Indicates whether POST requests can add resources to the relationship. When sent but not allowed, an HTTP 422 response is returned.
+
+```c#
+#nullable enable
+
+public class User : Identifiable
+{
+ [HasMany(Capabilities = ~HasManyCapabilities.AllowAdd)]
+ public ISet Groups { get; set; } = new HashSet();
+}
+```
+
+### AllowRemove
+
+For to-many relationships only. Indicates whether DELETE requests can remove resources from the relationship. When sent but not allowed, an HTTP 422 response is returned.
+
+```c#
+#nullable enable
+
+public class User : Identifiable
+{
+ [HasMany(Capabilities = ~HasManyCapabilities.AllowRemove)]
+ public ISet Groups { get; set; } = new HashSet();
+}
+```
+
+## CanInclude
+
+_obsolete since v5.1_
Relationships can be marked to disallow including them using the `?include=` query string parameter. When not allowed, it results in an HTTP 400 response.
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs
index 9be7e6e64e..5fe508f7f2 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs
@@ -25,9 +25,9 @@ public sealed class TodoItem : Identifiable
[HasOne]
public Person Owner { get; set; } = null!;
- [HasOne]
+ [HasOne(Capabilities = HasOneCapabilities.AllowView | HasOneCapabilities.AllowSet)]
public Person? Assignee { get; set; }
- [HasMany]
+ [HasMany(Capabilities = HasManyCapabilities.AllowView | HasManyCapabilities.AllowFilter)]
public ISet Tags { get; set; } = new HashSet();
}
diff --git a/src/JsonApiDotNetCore.Annotations/ArgumentGuard.cs b/src/JsonApiDotNetCore.Annotations/ArgumentGuard.cs
index be336e56a0..e0c786a106 100644
--- a/src/JsonApiDotNetCore.Annotations/ArgumentGuard.cs
+++ b/src/JsonApiDotNetCore.Annotations/ArgumentGuard.cs
@@ -1,52 +1,40 @@
+using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using SysNotNull = System.Diagnostics.CodeAnalysis.NotNullAttribute;
#pragma warning disable AV1008 // Class should not be static
+#pragma warning disable AV1553 // Do not use optional parameters with default value null for strings, collections or tasks
namespace JsonApiDotNetCore;
internal static class ArgumentGuard
{
[AssertionMethod]
- public static void NotNull([NoEnumeration] [SysNotNull] T? value, [InvokerParameterName] string name)
+ public static void NotNull([NoEnumeration] [SysNotNull] T? value, [CallerArgumentExpression("value")] string? parameterName = null)
where T : class
{
- if (value is null)
- {
- throw new ArgumentNullException(name);
- }
- }
-
- [AssertionMethod]
- public static void NotNullNorEmpty([SysNotNull] IEnumerable? value, [InvokerParameterName] string name)
- {
- NotNull(value, name);
-
- if (!value.Any())
- {
- throw new ArgumentException($"Must have one or more {name}.", name);
- }
+ ArgumentNullException.ThrowIfNull(value, parameterName);
}
[AssertionMethod]
- public static void NotNullNorEmpty([SysNotNull] IEnumerable? value, [InvokerParameterName] string name, string collectionName)
+ public static void NotNullNorEmpty([SysNotNull] IEnumerable? value, [CallerArgumentExpression("value")] string? parameterName = null)
{
- NotNull(value, name);
+ ArgumentNullException.ThrowIfNull(value, parameterName);
if (!value.Any())
{
- throw new ArgumentException($"Must have one or more {collectionName}.", name);
+ throw new ArgumentException("Collection cannot be null or empty.", parameterName);
}
}
[AssertionMethod]
- public static void NotNullNorEmpty([SysNotNull] string? value, [InvokerParameterName] string name)
+ public static void NotNullNorEmpty([SysNotNull] string? value, [CallerArgumentExpression("value")] string? parameterName = null)
{
- NotNull(value, name);
+ ArgumentNullException.ThrowIfNull(value, parameterName);
if (value == string.Empty)
{
- throw new ArgumentException("String cannot be null or empty.", name);
+ throw new ArgumentException("String cannot be null or empty.", parameterName);
}
}
}
diff --git a/src/JsonApiDotNetCore.Annotations/CollectionConverter.cs b/src/JsonApiDotNetCore.Annotations/CollectionConverter.cs
index a308607c3b..fa1c0c90bd 100644
--- a/src/JsonApiDotNetCore.Annotations/CollectionConverter.cs
+++ b/src/JsonApiDotNetCore.Annotations/CollectionConverter.cs
@@ -26,8 +26,8 @@ internal sealed class CollectionConverter
///
public IEnumerable CopyToTypedCollection(IEnumerable source, Type collectionType)
{
- ArgumentGuard.NotNull(source, nameof(source));
- ArgumentGuard.NotNull(collectionType, nameof(collectionType));
+ ArgumentGuard.NotNull(source);
+ ArgumentGuard.NotNull(collectionType);
Type concreteCollectionType = ToConcreteCollectionType(collectionType);
dynamic concreteCollectionInstance = Activator.CreateInstance(concreteCollectionType)!;
@@ -121,7 +121,7 @@ public IReadOnlyCollection ExtractResources(object? value)
///
public bool TypeCanContainHashSet(Type collectionType)
{
- ArgumentGuard.NotNull(collectionType, nameof(collectionType));
+ ArgumentGuard.NotNull(collectionType);
if (collectionType.IsGenericType)
{
diff --git a/src/JsonApiDotNetCore.Annotations/Configuration/ResourceType.cs b/src/JsonApiDotNetCore.Annotations/Configuration/ResourceType.cs
index 515dfe8a63..0263958b00 100644
--- a/src/JsonApiDotNetCore.Annotations/Configuration/ResourceType.cs
+++ b/src/JsonApiDotNetCore.Annotations/Configuration/ResourceType.cs
@@ -100,9 +100,9 @@ public ResourceType(string publicName, Type clrType, Type identityClrType, IRead
LinkTypes topLevelLinks = LinkTypes.NotConfigured, LinkTypes resourceLinks = LinkTypes.NotConfigured,
LinkTypes relationshipLinks = LinkTypes.NotConfigured)
{
- ArgumentGuard.NotNullNorEmpty(publicName, nameof(publicName));
- ArgumentGuard.NotNull(clrType, nameof(clrType));
- ArgumentGuard.NotNull(identityClrType, nameof(identityClrType));
+ ArgumentGuard.NotNullNorEmpty(publicName);
+ ArgumentGuard.NotNull(clrType);
+ ArgumentGuard.NotNull(identityClrType);
PublicName = publicName;
ClrType = clrType;
@@ -153,7 +153,7 @@ public AttrAttribute GetAttributeByPublicName(string publicName)
public AttrAttribute? FindAttributeByPublicName(string publicName)
{
- ArgumentGuard.NotNull(publicName, nameof(publicName));
+ ArgumentGuard.NotNull(publicName);
return _fieldsByPublicName.TryGetValue(publicName, out ResourceFieldAttribute? field) && field is AttrAttribute attribute ? attribute : null;
}
@@ -167,7 +167,7 @@ public AttrAttribute GetAttributeByPropertyName(string propertyName)
public AttrAttribute? FindAttributeByPropertyName(string propertyName)
{
- ArgumentGuard.NotNull(propertyName, nameof(propertyName));
+ ArgumentGuard.NotNull(propertyName);
return _fieldsByPropertyName.TryGetValue(propertyName, out ResourceFieldAttribute? field) && field is AttrAttribute attribute ? attribute : null;
}
@@ -180,7 +180,7 @@ public RelationshipAttribute GetRelationshipByPublicName(string publicName)
public RelationshipAttribute? FindRelationshipByPublicName(string publicName)
{
- ArgumentGuard.NotNull(publicName, nameof(publicName));
+ ArgumentGuard.NotNull(publicName);
return _fieldsByPublicName.TryGetValue(publicName, out ResourceFieldAttribute? field) && field is RelationshipAttribute relationship
? relationship
@@ -197,7 +197,7 @@ public RelationshipAttribute GetRelationshipByPropertyName(string propertyName)
public RelationshipAttribute? FindRelationshipByPropertyName(string propertyName)
{
- ArgumentGuard.NotNull(propertyName, nameof(propertyName));
+ ArgumentGuard.NotNull(propertyName);
return _fieldsByPropertyName.TryGetValue(propertyName, out ResourceFieldAttribute? field) && field is RelationshipAttribute relationship
? relationship
@@ -217,7 +217,7 @@ public IReadOnlySet GetAllConcreteDerivedTypes()
///
public ResourceType GetTypeOrDerived(Type clrType)
{
- ArgumentGuard.NotNull(clrType, nameof(clrType));
+ ArgumentGuard.NotNull(clrType);
ResourceType? derivedType = FindTypeOrDerived(this, clrType);
diff --git a/src/JsonApiDotNetCore.Annotations/Controllers/JsonApiEndpoints.cs b/src/JsonApiDotNetCore.Annotations/Controllers/JsonApiEndpoints.shared.cs
similarity index 100%
rename from src/JsonApiDotNetCore.Annotations/Controllers/JsonApiEndpoints.cs
rename to src/JsonApiDotNetCore.Annotations/Controllers/JsonApiEndpoints.shared.cs
diff --git a/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj b/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj
index 7c78f620ed..1fe6858b96 100644
--- a/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj
+++ b/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj
@@ -1,9 +1,10 @@
- $(TargetFrameworkName)
+ $(TargetFrameworkName);netstandard1.0
true
true
JsonApiDotNetCore
+ latest
@@ -27,4 +28,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs
index d3d6133f6e..7a6cbd960f 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs
@@ -14,17 +14,16 @@ public sealed class AttrAttribute : ResourceFieldAttribute
internal bool HasExplicitCapabilities => _capabilities != null;
///
- /// The set of capabilities that are allowed to be performed on this attribute. When not explicitly assigned, the configured default set of capabilities
- /// is used.
+ /// The set of allowed capabilities on this attribute. When not explicitly set, the configured default set of capabilities is used.
///
///
- ///
- /// public class Author : Identifiable
+ ///
/// {
/// [Attr(Capabilities = AttrCapabilities.AllowFilter | AttrCapabilities.AllowSort)]
- /// public string Name { get; set; }
+ /// public string Name { get; set; } = null!;
/// }
- ///
+ /// ]]>
///
public AttrCapabilities Capabilities
{
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.netstandard.cs
new file mode 100644
index 0000000000..a7915240dc
--- /dev/null
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.netstandard.cs
@@ -0,0 +1,14 @@
+using JetBrains.Annotations;
+
+namespace JsonApiDotNetCore.Resources.Annotations;
+
+///
+/// A simplified version, provided for convenience to multi-target against NetStandard. Does not actually work with JsonApiDotNetCore.
+///
+[PublicAPI]
+[AttributeUsage(AttributeTargets.Property)]
+public sealed class AttrAttribute : ResourceFieldAttribute
+{
+ ///
+ public AttrCapabilities Capabilities { get; set; }
+}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.cs
deleted file mode 100644
index c6f849fdff..0000000000
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-namespace JsonApiDotNetCore.Resources.Annotations;
-
-///
-/// Indicates capabilities that can be performed on an .
-///
-[Flags]
-public enum AttrCapabilities
-{
- None = 0,
-
- ///
- /// Whether or not GET requests can retrieve the attribute. Attempts to retrieve when disabled will return an HTTP 400 response.
- ///
- AllowView = 1,
-
- ///
- /// Whether or not POST requests can assign the attribute value. Attempts to assign when disabled will return an HTTP 422 response.
- ///
- AllowCreate = 2,
-
- ///
- /// Whether or not PATCH requests can update the attribute value. Attempts to update when disabled will return an HTTP 422 response.
- ///
- AllowChange = 4,
-
- ///
- /// Whether or not an attribute can be filtered on via a query string parameter. Attempts to filter when disabled will return an HTTP 400 response.
- ///
- AllowFilter = 8,
-
- ///
- /// Whether or not an attribute can be sorted on via a query string parameter. Attempts to sort when disabled will return an HTTP 400 response.
- ///
- AllowSort = 16,
-
- All = AllowView | AllowCreate | AllowChange | AllowFilter | AllowSort
-}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.shared.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.shared.cs
new file mode 100644
index 0000000000..0951010b3b
--- /dev/null
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.shared.cs
@@ -0,0 +1,43 @@
+using JetBrains.Annotations;
+
+namespace JsonApiDotNetCore.Resources.Annotations;
+
+///
+/// Indicates what can be performed on an .
+///
+[PublicAPI]
+[Flags]
+public enum AttrCapabilities
+{
+ None = 0,
+
+ ///
+ /// Whether or not the attribute value can be returned in responses. Attempts to explicitly request it via the fields query string parameter when
+ /// disabled will return an HTTP 400 response. Otherwise, the attribute is silently omitted.
+ ///
+ AllowView = 1,
+
+ ///
+ /// Whether or not POST requests can assign the attribute value. Attempts to assign when disabled will return an HTTP 422 response.
+ ///
+ AllowCreate = 1 << 1,
+
+ ///
+ /// Whether or not PATCH requests can update the attribute value. Attempts to update when disabled will return an HTTP 422 response.
+ ///
+ AllowChange = 1 << 2,
+
+ ///
+ /// Whether or not the attribute can be filtered on. Attempts to use it in the filter query string parameter when disabled will return an HTTP 400
+ /// response.
+ ///
+ AllowFilter = 1 << 3,
+
+ ///
+ /// Whether or not the attribute can be sorted on. Attempts to use it in the sort query string parameter when disabled will return an HTTP 400
+ /// response.
+ ///
+ AllowSort = 1 << 4,
+
+ All = AllowView | AllowCreate | AllowChange | AllowFilter | AllowSort
+}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/EagerLoadAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/EagerLoadAttribute.netstandard.cs
new file mode 100644
index 0000000000..47052a078c
--- /dev/null
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/EagerLoadAttribute.netstandard.cs
@@ -0,0 +1,12 @@
+using JetBrains.Annotations;
+
+namespace JsonApiDotNetCore.Resources.Annotations;
+
+///
+/// A simplified version, provided for convenience to multi-target against NetStandard. Does not actually work with JsonApiDotNetCore.
+///
+[PublicAPI]
+[AttributeUsage(AttributeTargets.Property)]
+public sealed class EagerLoadAttribute : Attribute
+{
+}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.cs
index 39bcf34b3f..5792744d5c 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.cs
@@ -1,5 +1,7 @@
using JetBrains.Annotations;
+// ReSharper disable NonReadonlyMemberInGetHashCode
+
namespace JsonApiDotNetCore.Resources.Annotations;
///
@@ -20,12 +22,33 @@ namespace JsonApiDotNetCore.Resources.Annotations;
public sealed class HasManyAttribute : RelationshipAttribute
{
private readonly Lazy _lazyIsManyToMany;
+ private HasManyCapabilities? _capabilities;
///
/// Inspects to determine if this is a many-to-many relationship.
///
internal bool IsManyToMany => _lazyIsManyToMany.Value;
+ internal bool HasExplicitCapabilities => _capabilities != null;
+
+ ///
+ /// The set of allowed capabilities on this to-many relationship. When not explicitly set, the configured default set of capabilities is used.
+ ///
+ ///
+ ///
+ /// {
+ /// [HasMany(Capabilities = HasManyCapabilities.AllowView | HasManyCapabilities.AllowInclude)]
+ /// public ISet Chapters { get; set; } = new HashSet();
+ /// }
+ /// ]]>
+ ///
+ public HasManyCapabilities Capabilities
+ {
+ get => _capabilities ?? default;
+ set => _capabilities = value;
+ }
+
public HasManyAttribute()
{
_lazyIsManyToMany = new Lazy(EvaluateIsManyToMany, LazyThreadSafetyMode.PublicationOnly);
@@ -41,4 +64,26 @@ private bool EvaluateIsManyToMany()
return false;
}
+
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ if (obj is null || GetType() != obj.GetType())
+ {
+ return false;
+ }
+
+ var other = (HasManyAttribute)obj;
+
+ return _capabilities == other._capabilities && base.Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(_capabilities, base.GetHashCode());
+ }
}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.netstandard.cs
new file mode 100644
index 0000000000..cf83f0ce17
--- /dev/null
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.netstandard.cs
@@ -0,0 +1,14 @@
+using JetBrains.Annotations;
+
+namespace JsonApiDotNetCore.Resources.Annotations;
+
+///
+/// A simplified version, provided for convenience to multi-target against NetStandard. Does not actually work with JsonApiDotNetCore.
+///
+[PublicAPI]
+[AttributeUsage(AttributeTargets.Property)]
+public sealed class HasManyAttribute : RelationshipAttribute
+{
+ ///
+ public HasManyCapabilities Capabilities { get; set; }
+}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyCapabilities.shared.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyCapabilities.shared.cs
new file mode 100644
index 0000000000..cf65951321
--- /dev/null
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyCapabilities.shared.cs
@@ -0,0 +1,51 @@
+using JetBrains.Annotations;
+
+namespace JsonApiDotNetCore.Resources.Annotations;
+
+///
+/// Indicates what can be performed on a .
+///
+[PublicAPI]
+[Flags]
+public enum HasManyCapabilities
+{
+ None = 0,
+
+ ///
+ /// Whether or not the relationship can be returned in responses. Attempts to explicitly request it via the fields query string parameter when
+ /// disabled will return an HTTP 400 response. Otherwise, the relationship (and its related resources, when included) are silently omitted.
+ ///
+ ///
+ /// Note this setting does not affect retrieving the related resources directly.
+ ///
+ AllowView = 1,
+
+ ///
+ /// Whether or not the relationship can be included. Attempts to use it in the include query string parameter when disabled will return an HTTP
+ /// 400 response.
+ ///
+ AllowInclude = 1 << 1,
+
+ ///
+ /// Whether or not the to-many relationship can be used in the count() and has() functions as part of the filter query string
+ /// parameter. Attempts to use it when disabled will return an HTTP 400 response.
+ ///
+ AllowFilter = 1 << 2,
+
+ ///
+ /// Whether or not POST and PATCH requests can replace the relationship. Attempts to replace when disabled will return an HTTP 422 response.
+ ///
+ AllowSet = 1 << 3,
+
+ ///
+ /// Whether or not POST requests can add to the to-many relationship. Attempts to add when disabled will return an HTTP 422 response.
+ ///
+ AllowAdd = 1 << 4,
+
+ ///
+ /// Whether or not DELETE requests can remove from the to-many relationship. Attempts to remove when disabled will return an HTTP 422 response.
+ ///
+ AllowRemove = 1 << 5,
+
+ All = AllowView | AllowInclude | AllowFilter | AllowSet | AllowAdd | AllowRemove
+}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.cs
index 0a68f702d3..c0416c92fb 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.cs
@@ -1,5 +1,7 @@
using JetBrains.Annotations;
+// ReSharper disable NonReadonlyMemberInGetHashCode
+
namespace JsonApiDotNetCore.Resources.Annotations;
///
@@ -19,12 +21,33 @@ namespace JsonApiDotNetCore.Resources.Annotations;
public sealed class HasOneAttribute : RelationshipAttribute
{
private readonly Lazy _lazyIsOneToOne;
+ private HasOneCapabilities? _capabilities;
///
/// Inspects to determine if this is a one-to-one relationship.
///
internal bool IsOneToOne => _lazyIsOneToOne.Value;
+ internal bool HasExplicitCapabilities => _capabilities != null;
+
+ ///
+ /// The set of allowed capabilities on this to-one relationship. When not explicitly set, the configured default set of capabilities is used.
+ ///
+ ///
+ ///
+ /// {
+ /// [HasOne(Capabilities = HasOneCapabilities.AllowView | HasOneCapabilities.AllowInclude)]
+ /// public Person? Author { get; set; }
+ /// }
+ /// ]]>
+ ///
+ public HasOneCapabilities Capabilities
+ {
+ get => _capabilities ?? default;
+ set => _capabilities = value;
+ }
+
public HasOneAttribute()
{
_lazyIsOneToOne = new Lazy(EvaluateIsOneToOne, LazyThreadSafetyMode.PublicationOnly);
@@ -40,4 +63,26 @@ private bool EvaluateIsOneToOne()
return false;
}
+
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ if (obj is null || GetType() != obj.GetType())
+ {
+ return false;
+ }
+
+ var other = (HasOneAttribute)obj;
+
+ return _capabilities == other._capabilities && base.Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(_capabilities, base.GetHashCode());
+ }
}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.netstandard.cs
new file mode 100644
index 0000000000..42be2f3c5f
--- /dev/null
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.netstandard.cs
@@ -0,0 +1,14 @@
+using JetBrains.Annotations;
+
+namespace JsonApiDotNetCore.Resources.Annotations;
+
+///
+/// A simplified version, provided for convenience to multi-target against NetStandard. Does not actually work with JsonApiDotNetCore.
+///
+[PublicAPI]
+[AttributeUsage(AttributeTargets.Property)]
+public sealed class HasOneAttribute : RelationshipAttribute
+{
+ ///
+ public HasOneCapabilities Capabilities { get; set; }
+}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneCapabilities.shared.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneCapabilities.shared.cs
new file mode 100644
index 0000000000..a001e39407
--- /dev/null
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneCapabilities.shared.cs
@@ -0,0 +1,35 @@
+using JetBrains.Annotations;
+
+namespace JsonApiDotNetCore.Resources.Annotations;
+
+///
+/// Indicates what can be performed on a .
+///
+[PublicAPI]
+[Flags]
+public enum HasOneCapabilities
+{
+ None = 0,
+
+ ///
+ /// Whether or not the relationship can be returned in responses. Attempts to explicitly request it via the fields query string parameter when
+ /// disabled will return an HTTP 400 response. Otherwise, the relationship (and its related resources, when included) are silently omitted.
+ ///
+ ///
+ /// Note this setting does not affect retrieving the related resources directly.
+ ///
+ AllowView = 1,
+
+ ///
+ /// Whether or not the relationship can be included. Attempts to use it in the include query string parameter when disabled will return an HTTP
+ /// 400 response.
+ ///
+ AllowInclude = 1 << 1,
+
+ ///
+ /// Whether or not POST and PATCH requests can replace the relationship. Attempts to replace when disabled will return an HTTP 422 response.
+ ///
+ AllowSet = 1 << 2,
+
+ All = AllowView | AllowInclude | AllowSet
+}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/LinkTypes.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/LinkTypes.shared.cs
similarity index 100%
rename from src/JsonApiDotNetCore.Annotations/Resources/Annotations/LinkTypes.cs
rename to src/JsonApiDotNetCore.Annotations/Resources/Annotations/LinkTypes.shared.cs
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/NoResourceAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/NoResourceAttribute.shared.cs
similarity index 100%
rename from src/JsonApiDotNetCore.Annotations/Resources/Annotations/NoResourceAttribute.cs
rename to src/JsonApiDotNetCore.Annotations/Resources/Annotations/NoResourceAttribute.shared.cs
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs
index 11320a7abc..dd94bab221 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs
@@ -14,9 +14,13 @@ public abstract class RelationshipAttribute : ResourceFieldAttribute
{
private protected static readonly CollectionConverter CollectionConverter = new();
- // These are definitely assigned after building the resource graph, which is why their public equivalents are declared as non-nullable.
+ // This field is definitely assigned after building the resource graph, which is why its public equivalent is declared as non-nullable.
private ResourceType? _rightType;
+ private bool? _canInclude;
+
+ internal bool HasExplicitCanInclude => _canInclude != null;
+
///
/// The of the Entity Framework Core inverse navigation, which may or may not exist. Even if it exists, it may not be exposed
/// as a JSON:API relationship.
@@ -57,7 +61,7 @@ public ResourceType RightType
get => _rightType!;
internal set
{
- ArgumentGuard.NotNull(value, nameof(value));
+ ArgumentGuard.NotNull(value);
_rightType = value;
}
}
@@ -69,13 +73,18 @@ internal set
public LinkTypes Links { get; set; } = LinkTypes.NotConfigured;
///
- /// Whether or not this relationship can be included using the
- ///
- /// ?include=publicName
- ///
- /// query string parameter. This is true by default.
+ /// Whether or not this relationship can be included using the include query string parameter. This is true by default.
///
- public bool CanInclude { get; set; } = true;
+ ///
+ /// When explicitly set, this value takes precedence over Capabilities for backwards-compatibility. Capabilities are adjusted accordingly when building
+ /// the resource graph.
+ ///
+ [Obsolete("Use AllowInclude in Capabilities instead.")]
+ public bool CanInclude
+ {
+ get => _canInclude ?? true;
+ set => _canInclude = value;
+ }
public override bool Equals(object? obj)
{
@@ -91,11 +100,11 @@ public override bool Equals(object? obj)
var other = (RelationshipAttribute)obj;
- return _rightType?.ClrType == other._rightType?.ClrType && Links == other.Links && CanInclude == other.CanInclude && base.Equals(other);
+ return _rightType?.ClrType == other._rightType?.ClrType && Links == other.Links && base.Equals(other);
}
public override int GetHashCode()
{
- return HashCode.Combine(_rightType?.ClrType, Links, CanInclude, base.GetHashCode());
+ return HashCode.Combine(_rightType?.ClrType, Links, base.GetHashCode());
}
}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs
new file mode 100644
index 0000000000..d7af592564
--- /dev/null
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+
+namespace JsonApiDotNetCore.Resources.Annotations;
+
+///
+/// A simplified version, provided for convenience to multi-target against NetStandard. Does not actually work with JsonApiDotNetCore.
+///
+[PublicAPI]
+public abstract class RelationshipAttribute : ResourceFieldAttribute
+{
+ ///
+ public LinkTypes Links { get; set; } = LinkTypes.NotConfigured;
+
+ ///
+ [Obsolete("Use AllowInclude in Capabilities instead.")]
+ public bool CanInclude { get; set; } = true;
+}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceAttribute.shared.cs
similarity index 91%
rename from src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceAttribute.cs
rename to src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceAttribute.shared.cs
index ca517e3e99..72669de585 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceAttribute.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceAttribute.shared.cs
@@ -1,9 +1,6 @@
using JetBrains.Annotations;
using JsonApiDotNetCore.Controllers;
-// ReSharper disable CheckNamespace
-#pragma warning disable AV1505 // Namespace should match with assembly name
-
namespace JsonApiDotNetCore.Resources.Annotations;
///
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs
index 599b17a42a..e8e1d17aca 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs
@@ -19,7 +19,7 @@ public abstract class ResourceFieldAttribute : Attribute
private ResourceType? _type;
///
- /// The publicly exposed name of this JSON:API field. When not explicitly assigned, the configured naming convention is applied on the property name.
+ /// The publicly exposed name of this JSON:API field. When not explicitly set, the configured naming convention is applied on the property name.
///
public string PublicName
{
@@ -43,7 +43,7 @@ public PropertyInfo Property
get => _property!;
internal set
{
- ArgumentGuard.NotNull(value, nameof(value));
+ ArgumentGuard.NotNull(value);
_property = value;
}
}
@@ -56,7 +56,7 @@ public ResourceType Type
get => _type!;
internal set
{
- ArgumentGuard.NotNull(value, nameof(value));
+ ArgumentGuard.NotNull(value);
_type = value;
}
}
@@ -67,7 +67,7 @@ internal set
///
public object? GetValue(object resource)
{
- ArgumentGuard.NotNull(resource, nameof(resource));
+ ArgumentGuard.NotNull(resource);
if (Property.GetMethod == null)
{
@@ -92,7 +92,7 @@ internal set
///
public void SetValue(object resource, object? newValue)
{
- ArgumentGuard.NotNull(resource, nameof(resource));
+ ArgumentGuard.NotNull(resource);
if (Property.SetMethod == null)
{
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.netstandard.cs
new file mode 100644
index 0000000000..958794365d
--- /dev/null
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.netstandard.cs
@@ -0,0 +1,13 @@
+using JetBrains.Annotations;
+
+namespace JsonApiDotNetCore.Resources.Annotations;
+
+///
+/// A simplified version, provided for convenience to multi-target against NetStandard. Does not actually work with JsonApiDotNetCore.
+///
+[PublicAPI]
+public abstract class ResourceFieldAttribute : Attribute
+{
+ ///
+ public string PublicName { get; set; } = null!;
+}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceLinksAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceLinksAttribute.shared.cs
similarity index 100%
rename from src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceLinksAttribute.cs
rename to src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceLinksAttribute.shared.cs
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/IIdentifiable.cs b/src/JsonApiDotNetCore.Annotations/Resources/IIdentifiable.shared.cs
similarity index 94%
rename from src/JsonApiDotNetCore.Annotations/Resources/IIdentifiable.cs
rename to src/JsonApiDotNetCore.Annotations/Resources/IIdentifiable.shared.cs
index 2c6dc02025..b6dc5e3b22 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/IIdentifiable.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/IIdentifiable.shared.cs
@@ -1,8 +1,11 @@
+using JetBrains.Annotations;
+
namespace JsonApiDotNetCore.Resources;
///
/// Defines the basic contract for a JSON:API resource. All resource classes must implement .
///
+[PublicAPI]
public interface IIdentifiable
{
///
@@ -22,6 +25,7 @@ public interface IIdentifiable
///
/// The resource identifier type.
///
+[PublicAPI]
public interface IIdentifiable : IIdentifiable
{
///
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Identifiable.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Identifiable.netstandard.cs
new file mode 100644
index 0000000000..a5a7179af6
--- /dev/null
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Identifiable.netstandard.cs
@@ -0,0 +1,16 @@
+namespace JsonApiDotNetCore.Resources;
+
+///
+/// A simplified version, provided for convenience to multi-target against NetStandard. Does not actually work with JsonApiDotNetCore.
+///
+public abstract class Identifiable : IIdentifiable
+{
+ ///
+ public virtual TId Id { get; set; } = default!;
+
+ ///
+ public string? StringId { get; set; }
+
+ ///
+ public string? LocalId { get; set; }
+}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Internal/RuntimeTypeConverter.cs b/src/JsonApiDotNetCore.Annotations/Resources/Internal/RuntimeTypeConverter.cs
index 8722458938..d79ed4c635 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Internal/RuntimeTypeConverter.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Internal/RuntimeTypeConverter.cs
@@ -10,7 +10,7 @@ public static class RuntimeTypeConverter
{
public static object? ConvertType(object? value, Type type)
{
- ArgumentGuard.NotNull(type, nameof(type));
+ ArgumentGuard.NotNull(type);
if (value == null)
{
diff --git a/src/JsonApiDotNetCore.Annotations/TypeExtensions.cs b/src/JsonApiDotNetCore.Annotations/TypeExtensions.cs
index c28ea84332..b31f82d48e 100644
--- a/src/JsonApiDotNetCore.Annotations/TypeExtensions.cs
+++ b/src/JsonApiDotNetCore.Annotations/TypeExtensions.cs
@@ -15,7 +15,7 @@ public static bool IsOrImplementsInterface(this Type? source)
///
private static bool IsOrImplementsInterface(this Type? source, Type interfaceType)
{
- ArgumentGuard.NotNull(interfaceType, nameof(interfaceType));
+ ArgumentGuard.NotNull(interfaceType);
if (source == null)
{
@@ -41,7 +41,7 @@ private static bool AreTypesEqual(Type left, Type right, bool isLeftGeneric)
///
public static string GetFriendlyTypeName(this Type type)
{
- ArgumentGuard.NotNull(type, nameof(type));
+ ArgumentGuard.NotNull(type);
// Based on https://stackoverflow.com/questions/2581642/how-do-i-get-the-type-name-of-a-generic-type-argument.
diff --git a/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs b/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs
index 6728fd537c..89a511b08e 100644
--- a/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs
+++ b/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs
@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
using System.Collections.Immutable;
-using System.Linq;
using System.Text;
using Humanizer;
using Microsoft.CodeAnalysis;
@@ -11,160 +8,163 @@
#pragma warning disable RS2008 // Enable analyzer release tracking
-namespace JsonApiDotNetCore.SourceGenerators
+namespace JsonApiDotNetCore.SourceGenerators;
+// To debug in Visual Studio (requires v17.2 or higher):
+// - Set JsonApiDotNetCore.SourceGenerators as startup project
+// - Add a breakpoint at the start of the Initialize or Execute method
+// - Optional: change targetProject in Properties\launchSettings.json
+// - Press F5
+
+[Generator(LanguageNames.CSharp)]
+public sealed class ControllerSourceGenerator : ISourceGenerator
{
- [Generator(LanguageNames.CSharp)]
- public sealed class ControllerSourceGenerator : ISourceGenerator
+ private const string Category = "JsonApiDotNetCore";
+
+ private static readonly DiagnosticDescriptor MissingInterfaceWarning = new("JADNC001", "Resource type does not implement IIdentifiable",
+ "Type '{0}' must implement IIdentifiable when using ResourceAttribute to auto-generate ASP.NET controllers", Category, DiagnosticSeverity.Warning,
+ true);
+
+ private static readonly DiagnosticDescriptor MissingIndentInTableError = new("JADNC900", "Internal error: Insufficient entries in IndentTable",
+ "Internal error: Missing entry in IndentTable for depth {0}", Category, DiagnosticSeverity.Warning, true);
+
+ // PERF: Heap-allocate the delegate only once, instead of per compilation.
+ private static readonly SyntaxReceiverCreator CreateSyntaxReceiver = static () => new TypeWithAttributeSyntaxReceiver();
+
+ public void Initialize(GeneratorInitializationContext context)
{
- private const string Category = "JsonApiDotNetCore";
+ context.RegisterForSyntaxNotifications(CreateSyntaxReceiver);
+ }
- private static readonly DiagnosticDescriptor MissingInterfaceWarning = new DiagnosticDescriptor("JADNC001",
- "Resource type does not implement IIdentifiable",
- "Type '{0}' must implement IIdentifiable when using ResourceAttribute to auto-generate ASP.NET controllers", Category,
- DiagnosticSeverity.Warning, true);
+ public void Execute(GeneratorExecutionContext context)
+ {
+ var receiver = (TypeWithAttributeSyntaxReceiver?)context.SyntaxReceiver;
- private static readonly DiagnosticDescriptor MissingIndentInTableError = new DiagnosticDescriptor("JADNC900",
- "Internal error: Insufficient entries in IndentTable", "Internal error: Missing entry in IndentTable for depth {0}", Category,
- DiagnosticSeverity.Warning, true);
+ if (receiver == null)
+ {
+ return;
+ }
- // PERF: Heap-allocate the delegate only once, instead of per compilation.
- private static readonly SyntaxReceiverCreator CreateSyntaxReceiver = () => new TypeWithAttributeSyntaxReceiver();
+ INamedTypeSymbol? resourceAttributeType = context.Compilation.GetTypeByMetadataName("JsonApiDotNetCore.Resources.Annotations.ResourceAttribute");
+ INamedTypeSymbol? identifiableOpenInterface = context.Compilation.GetTypeByMetadataName("JsonApiDotNetCore.Resources.IIdentifiable`1");
+ INamedTypeSymbol? loggerFactoryInterface = context.Compilation.GetTypeByMetadataName("Microsoft.Extensions.Logging.ILoggerFactory");
- public void Initialize(GeneratorInitializationContext context)
+ if (resourceAttributeType == null || identifiableOpenInterface == null || loggerFactoryInterface == null)
{
- context.RegisterForSyntaxNotifications(CreateSyntaxReceiver);
+ return;
}
- public void Execute(GeneratorExecutionContext context)
+ var controllerNamesInUse = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ var writer = new SourceCodeWriter(context, MissingIndentInTableError);
+
+ foreach (TypeDeclarationSyntax? typeDeclarationSyntax in receiver.TypeDeclarations)
{
- var receiver = (TypeWithAttributeSyntaxReceiver)context.SyntaxReceiver;
+ // PERF: Note that our code runs on every keystroke in the IDE, which makes it critical to provide near-realtime performance.
+ // This means keeping an eye on memory allocations and bailing out early when compilations are cancelled while the user is still typing.
+ context.CancellationToken.ThrowIfCancellationRequested();
+
+ SemanticModel semanticModel = context.Compilation.GetSemanticModel(typeDeclarationSyntax.SyntaxTree);
+ INamedTypeSymbol? resourceType = semanticModel.GetDeclaredSymbol(typeDeclarationSyntax, context.CancellationToken);
- if (receiver == null)
+ if (resourceType == null)
{
- return;
+ continue;
}
- INamedTypeSymbol resourceAttributeType = context.Compilation.GetTypeByMetadataName("JsonApiDotNetCore.Resources.Annotations.ResourceAttribute");
- INamedTypeSymbol identifiableOpenInterface = context.Compilation.GetTypeByMetadataName("JsonApiDotNetCore.Resources.IIdentifiable`1");
- INamedTypeSymbol loggerFactoryInterface = context.Compilation.GetTypeByMetadataName("Microsoft.Extensions.Logging.ILoggerFactory");
+ AttributeData? resourceAttributeData = FirstOrDefault(resourceType.GetAttributes(), resourceAttributeType,
+ static (data, type) => SymbolEqualityComparer.Default.Equals(data.AttributeClass, type));
- if (resourceAttributeType == null || identifiableOpenInterface == null || loggerFactoryInterface == null)
+ if (resourceAttributeData == null)
{
- return;
+ continue;
}
- var controllerNamesInUse = new Dictionary(StringComparer.OrdinalIgnoreCase);
- var writer = new SourceCodeWriter(context, MissingIndentInTableError);
+ TypedConstant endpointsArgument =
+ resourceAttributeData.NamedArguments.FirstOrDefault(static pair => pair.Key == "GenerateControllerEndpoints").Value;
- foreach (TypeDeclarationSyntax typeDeclarationSyntax in receiver.TypeDeclarations)
+ if (endpointsArgument.Value != null && (JsonApiEndpointsCopy)endpointsArgument.Value == JsonApiEndpointsCopy.None)
{
- // PERF: Note that our code runs on every keystroke in the IDE, which makes it critical to provide near-realtime performance.
- // This means keeping an eye on memory allocations and bailing out early when compilations are cancelled while the user is still typing.
- context.CancellationToken.ThrowIfCancellationRequested();
-
- SemanticModel semanticModel = context.Compilation.GetSemanticModel(typeDeclarationSyntax.SyntaxTree);
- INamedTypeSymbol resourceType = semanticModel.GetDeclaredSymbol(typeDeclarationSyntax, context.CancellationToken);
-
- if (resourceType == null)
- {
- continue;
- }
-
- AttributeData resourceAttributeData = FirstOrDefault(resourceType.GetAttributes(), resourceAttributeType,
- (data, type) => SymbolEqualityComparer.Default.Equals(data.AttributeClass, type));
-
- if (resourceAttributeData == null)
- {
- continue;
- }
-
- TypedConstant endpointsArgument = resourceAttributeData.NamedArguments.FirstOrDefault(pair => pair.Key == "GenerateControllerEndpoints").Value;
-
- if (endpointsArgument.Value != null && (JsonApiEndpointsCopy)endpointsArgument.Value == JsonApiEndpointsCopy.None)
- {
- continue;
- }
-
- TypedConstant controllerNamespaceArgument =
- resourceAttributeData.NamedArguments.FirstOrDefault(pair => pair.Key == "ControllerNamespace").Value;
-
- string controllerNamespace = GetControllerNamespace(controllerNamespaceArgument, resourceType);
-
- INamedTypeSymbol identifiableClosedInterface = FirstOrDefault(resourceType.AllInterfaces, identifiableOpenInterface,
- (@interface, openInterface) => @interface.IsGenericType &&
- SymbolEqualityComparer.Default.Equals(@interface.ConstructedFrom, openInterface));
+ continue;
+ }
- if (identifiableClosedInterface == null)
- {
- var diagnostic = Diagnostic.Create(MissingInterfaceWarning, typeDeclarationSyntax.GetLocation(), resourceType.Name);
- context.ReportDiagnostic(diagnostic);
- continue;
- }
+ TypedConstant controllerNamespaceArgument =
+ resourceAttributeData.NamedArguments.FirstOrDefault(static pair => pair.Key == "ControllerNamespace").Value;
- ITypeSymbol idType = identifiableClosedInterface.TypeArguments[0];
- string controllerName = $"{resourceType.Name.Pluralize()}Controller";
- JsonApiEndpointsCopy endpointsToGenerate = (JsonApiEndpointsCopy?)(int?)endpointsArgument.Value ?? JsonApiEndpointsCopy.All;
+ string? controllerNamespace = GetControllerNamespace(controllerNamespaceArgument, resourceType);
- string sourceCode = writer.Write(resourceType, idType, endpointsToGenerate, controllerNamespace, controllerName, loggerFactoryInterface);
- SourceText sourceText = SourceText.From(sourceCode, Encoding.UTF8);
+ INamedTypeSymbol? identifiableClosedInterface = FirstOrDefault(resourceType.AllInterfaces, identifiableOpenInterface,
+ static (@interface, openInterface) =>
+ @interface.IsGenericType && SymbolEqualityComparer.Default.Equals(@interface.ConstructedFrom, openInterface));
- string fileName = GetUniqueFileName(controllerName, controllerNamesInUse);
- context.AddSource(fileName, sourceText);
+ if (identifiableClosedInterface == null)
+ {
+ var diagnostic = Diagnostic.Create(MissingInterfaceWarning, typeDeclarationSyntax.GetLocation(), resourceType.Name);
+ context.ReportDiagnostic(diagnostic);
+ continue;
}
- }
- private static TElement FirstOrDefault(ImmutableArray source, TContext context, Func predicate)
- {
- // PERF: Using this method enables to avoid allocating a closure in the passed lambda expression.
- // See https://www.jetbrains.com/help/resharper/2021.2/Fixing_Issues_Found_by_DPA.html#closures-in-lambda-expressions.
+ ITypeSymbol idType = identifiableClosedInterface.TypeArguments[0];
+ string controllerName = $"{resourceType.Name.Pluralize()}Controller";
+ JsonApiEndpointsCopy endpointsToGenerate = (JsonApiEndpointsCopy?)(int?)endpointsArgument.Value ?? JsonApiEndpointsCopy.All;
- foreach (TElement element in source)
- {
- if (predicate(element, context))
- {
- return element;
- }
- }
+ string sourceCode = writer.Write(resourceType, idType, endpointsToGenerate, controllerNamespace, controllerName, loggerFactoryInterface);
+ SourceText sourceText = SourceText.From(sourceCode, Encoding.UTF8);
- return default;
+ string fileName = GetUniqueFileName(controllerName, controllerNamesInUse);
+ context.AddSource(fileName, sourceText);
}
+ }
- private static string GetControllerNamespace(TypedConstant controllerNamespaceArgument, INamedTypeSymbol resourceType)
+ private static TElement? FirstOrDefault(ImmutableArray source, TContext context, Func predicate)
+ {
+ // PERF: Using this method enables to avoid allocating a closure in the passed lambda expression.
+ // See https://www.jetbrains.com/help/resharper/2021.2/Fixing_Issues_Found_by_DPA.html#closures-in-lambda-expressions.
+
+ foreach (TElement element in source)
{
- if (!controllerNamespaceArgument.IsNull)
+ if (predicate(element, context))
{
- return (string)controllerNamespaceArgument.Value;
+ return element;
}
+ }
- if (resourceType.ContainingNamespace.IsGlobalNamespace)
- {
- return null;
- }
+ return default;
+ }
- if (resourceType.ContainingNamespace.ContainingNamespace.IsGlobalNamespace)
- {
- return "Controllers";
- }
+ private static string? GetControllerNamespace(TypedConstant controllerNamespaceArgument, INamedTypeSymbol resourceType)
+ {
+ if (!controllerNamespaceArgument.IsNull)
+ {
+ return (string?)controllerNamespaceArgument.Value;
+ }
- return $"{resourceType.ContainingNamespace.ContainingNamespace}.Controllers";
+ if (resourceType.ContainingNamespace.IsGlobalNamespace)
+ {
+ return null;
}
- private static string GetUniqueFileName(string controllerName, IDictionary controllerNamesInUse)
+ if (resourceType.ContainingNamespace.ContainingNamespace.IsGlobalNamespace)
{
- // We emit unique file names to prevent a failure in the source generator, but also because our test suite
- // may contain two resources with the same class name in different namespaces. That works, as long as only
- // one of its controllers gets registered.
+ return "Controllers";
+ }
- if (controllerNamesInUse.TryGetValue(controllerName, out int lastIndex))
- {
- lastIndex++;
- controllerNamesInUse[controllerName] = lastIndex;
+ return $"{resourceType.ContainingNamespace.ContainingNamespace}.Controllers";
+ }
- return $"{controllerName}{lastIndex}.g.cs";
- }
+ private static string GetUniqueFileName(string controllerName, IDictionary controllerNamesInUse)
+ {
+ // We emit unique file names to prevent a failure in the source generator, but also because our test suite
+ // may contain two resources with the same class name in different namespaces. That works, as long as only
+ // one of its controllers gets registered.
- controllerNamesInUse[controllerName] = 1;
- return $"{controllerName}.g.cs";
+ if (controllerNamesInUse.TryGetValue(controllerName, out int lastIndex))
+ {
+ lastIndex++;
+ controllerNamesInUse[controllerName] = lastIndex;
+
+ return $"{controllerName}{lastIndex}.g.cs";
}
+
+ controllerNamesInUse[controllerName] = 1;
+ return $"{controllerName}.g.cs";
}
}
diff --git a/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj b/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj
index 9f7c0b85dd..8bf3e90cf6 100644
--- a/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj
+++ b/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj
@@ -5,8 +5,7 @@
true
false
$(NoWarn);NU5128
- disable
- disable
+ latest
true
@@ -48,6 +47,6 @@
-
+
diff --git a/src/JsonApiDotNetCore.SourceGenerators/JsonApiEndpointsCopy.cs b/src/JsonApiDotNetCore.SourceGenerators/JsonApiEndpointsCopy.cs
index 14134adcfd..911be3f359 100644
--- a/src/JsonApiDotNetCore.SourceGenerators/JsonApiEndpointsCopy.cs
+++ b/src/JsonApiDotNetCore.SourceGenerators/JsonApiEndpointsCopy.cs
@@ -1,26 +1,23 @@
-using System;
+namespace JsonApiDotNetCore.SourceGenerators;
-namespace JsonApiDotNetCore.SourceGenerators
+// IMPORTANT: A copy of this type exists in the JsonApiDotNetCore project. Keep these in sync when making changes.
+[Flags]
+public enum JsonApiEndpointsCopy
{
- // IMPORTANT: A copy of this type exists in the JsonApiDotNetCore project. Keep these in sync when making changes.
- [Flags]
- public enum JsonApiEndpointsCopy
- {
- None = 0,
- GetCollection = 1,
- GetSingle = 1 << 1,
- GetSecondary = 1 << 2,
- GetRelationship = 1 << 3,
- Post = 1 << 4,
- PostRelationship = 1 << 5,
- Patch = 1 << 6,
- PatchRelationship = 1 << 7,
- Delete = 1 << 8,
- DeleteRelationship = 1 << 9,
+ None = 0,
+ GetCollection = 1,
+ GetSingle = 1 << 1,
+ GetSecondary = 1 << 2,
+ GetRelationship = 1 << 3,
+ Post = 1 << 4,
+ PostRelationship = 1 << 5,
+ Patch = 1 << 6,
+ PatchRelationship = 1 << 7,
+ Delete = 1 << 8,
+ DeleteRelationship = 1 << 9,
- Query = GetCollection | GetSingle | GetSecondary | GetRelationship,
- Command = Post | PostRelationship | Patch | PatchRelationship | Delete | DeleteRelationship,
+ Query = GetCollection | GetSingle | GetSecondary | GetRelationship,
+ Command = Post | PostRelationship | Patch | PatchRelationship | Delete | DeleteRelationship,
- All = Query | Command
- }
+ All = Query | Command
}
diff --git a/src/JsonApiDotNetCore.SourceGenerators/Properties/launchSettings.json b/src/JsonApiDotNetCore.SourceGenerators/Properties/launchSettings.json
index 2679b059a9..03635841ec 100644
--- a/src/JsonApiDotNetCore.SourceGenerators/Properties/launchSettings.json
+++ b/src/JsonApiDotNetCore.SourceGenerators/Properties/launchSettings.json
@@ -2,7 +2,7 @@
"profiles": {
"JsonApiDotNetCore.SourceGenerators": {
"commandName": "DebugRoslynComponent",
- "targetProject": "..\\..\\test\\SourceGeneratorDebugger\\SourceGeneratorDebugger.csproj"
+ "targetProject": "..\\Examples\\JsonApiDotNetCoreExample\\JsonApiDotNetCoreExample.csproj"
}
}
}
diff --git a/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs b/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs
index e03e3cbad2..13dc91a836 100644
--- a/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs
+++ b/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs
@@ -1,266 +1,271 @@
-using System.Collections.Generic;
using System.Text;
using Microsoft.CodeAnalysis;
-namespace JsonApiDotNetCore.SourceGenerators
+namespace JsonApiDotNetCore.SourceGenerators;
+
+///
+/// Writes the source code for an ASP.NET controller for a JSON:API resource.
+///
+internal sealed class SourceCodeWriter
{
- ///
- /// Writes the source code for an ASP.NET controller for a JSON:API resource.
- ///
- internal sealed class SourceCodeWriter
- {
- private const int SpacesPerIndent = 4;
+ private const int SpacesPerIndent = 4;
- private static readonly IDictionary IndentTable = new Dictionary
+ private static readonly IDictionary IndentTable = new Dictionary
+ {
+ [0] = string.Empty,
+ [1] = new(' ', 1 * SpacesPerIndent),
+ [2] = new(' ', 2 * SpacesPerIndent),
+ [3] = new(' ', 3 * SpacesPerIndent)
+ };
+
+ private static readonly IDictionary AggregateEndpointToServiceNameMap =
+ new Dictionary
{
- [0] = string.Empty,
- [1] = new string(' ', 1 * SpacesPerIndent),
- [2] = new string(' ', 2 * SpacesPerIndent),
- [3] = new string(' ', 3 * SpacesPerIndent)
+ [JsonApiEndpointsCopy.All] = ("IResourceService", "resourceService"),
+ [JsonApiEndpointsCopy.Query] = ("IResourceQueryService", "queryService"),
+ [JsonApiEndpointsCopy.Command] = ("IResourceCommandService", "commandService")
};
- private static readonly IDictionary AggregateEndpointToServiceNameMap =
- new Dictionary
- {
- [JsonApiEndpointsCopy.All] = ("IResourceService", "resourceService"),
- [JsonApiEndpointsCopy.Query] = ("IResourceQueryService", "queryService"),
- [JsonApiEndpointsCopy.Command] = ("IResourceCommandService", "commandService")
- };
-
- private static readonly IDictionary EndpointToServiceNameMap =
- new Dictionary
- {
- [JsonApiEndpointsCopy.GetCollection] = ("IGetAllService", "getAll"),
- [JsonApiEndpointsCopy.GetSingle] = ("IGetByIdService", "getById"),
- [JsonApiEndpointsCopy.GetSecondary] = ("IGetSecondaryService", "getSecondary"),
- [JsonApiEndpointsCopy.GetRelationship] = ("IGetRelationshipService", "getRelationship"),
- [JsonApiEndpointsCopy.Post] = ("ICreateService", "create"),
- [JsonApiEndpointsCopy.PostRelationship] = ("IAddToRelationshipService", "addToRelationship"),
- [JsonApiEndpointsCopy.Patch] = ("IUpdateService", "update"),
- [JsonApiEndpointsCopy.PatchRelationship] = ("ISetRelationshipService", "setRelationship"),
- [JsonApiEndpointsCopy.Delete] = ("IDeleteService", "delete"),
- [JsonApiEndpointsCopy.DeleteRelationship] = ("IRemoveFromRelationshipService", "removeFromRelationship")
- };
-
- private readonly GeneratorExecutionContext _context;
- private readonly DiagnosticDescriptor _missingIndentInTableErrorDescriptor;
-
- private readonly StringBuilder _sourceBuilder = new StringBuilder();
- private int _depth;
-
- public SourceCodeWriter(GeneratorExecutionContext context, DiagnosticDescriptor missingIndentInTableErrorDescriptor)
+ private static readonly IDictionary EndpointToServiceNameMap =
+ new Dictionary
{
- _context = context;
- _missingIndentInTableErrorDescriptor = missingIndentInTableErrorDescriptor;
- }
-
- public string Write(INamedTypeSymbol resourceType, ITypeSymbol idType, JsonApiEndpointsCopy endpointsToGenerate, string controllerNamespace,
- string controllerName, INamedTypeSymbol loggerFactoryInterface)
- {
- _sourceBuilder.Clear();
- _depth = 0;
-
- if (idType.IsReferenceType && idType.NullableAnnotation == NullableAnnotation.Annotated)
- {
- WriteNullableEnable();
- }
+ [JsonApiEndpointsCopy.GetCollection] = ("IGetAllService", "getAll"),
+ [JsonApiEndpointsCopy.GetSingle] = ("IGetByIdService", "getById"),
+ [JsonApiEndpointsCopy.GetSecondary] = ("IGetSecondaryService", "getSecondary"),
+ [JsonApiEndpointsCopy.GetRelationship] = ("IGetRelationshipService", "getRelationship"),
+ [JsonApiEndpointsCopy.Post] = ("ICreateService", "create"),
+ [JsonApiEndpointsCopy.PostRelationship] = ("IAddToRelationshipService", "addToRelationship"),
+ [JsonApiEndpointsCopy.Patch] = ("IUpdateService", "update"),
+ [JsonApiEndpointsCopy.PatchRelationship] = ("ISetRelationshipService", "setRelationship"),
+ [JsonApiEndpointsCopy.Delete] = ("IDeleteService", "delete"),
+ [JsonApiEndpointsCopy.DeleteRelationship] = ("IRemoveFromRelationshipService", "removeFromRelationship")
+ };
- WriteNamespaceImports(loggerFactoryInterface, resourceType);
+ private readonly GeneratorExecutionContext _context;
+ private readonly DiagnosticDescriptor _missingIndentInTableErrorDescriptor;
- if (controllerNamespace != null)
- {
- WriteNamespaceDeclaration(controllerNamespace);
- }
+ private readonly StringBuilder _sourceBuilder = new();
+ private int _depth;
- WriteOpenClassDeclaration(controllerName, endpointsToGenerate, resourceType, idType);
- _depth++;
+ public SourceCodeWriter(GeneratorExecutionContext context, DiagnosticDescriptor missingIndentInTableErrorDescriptor)
+ {
+ _context = context;
+ _missingIndentInTableErrorDescriptor = missingIndentInTableErrorDescriptor;
+ }
- WriteConstructor(controllerName, loggerFactoryInterface, endpointsToGenerate, resourceType, idType);
+ public string Write(INamedTypeSymbol resourceType, ITypeSymbol idType, JsonApiEndpointsCopy endpointsToGenerate, string? controllerNamespace,
+ string controllerName, INamedTypeSymbol loggerFactoryInterface)
+ {
+ _sourceBuilder.Clear();
+ _depth = 0;
- _depth--;
- WriteCloseCurly();
+ WriteAutoGeneratedComment();
- return _sourceBuilder.ToString();
+ if (idType.IsReferenceType && idType.NullableAnnotation == NullableAnnotation.Annotated)
+ {
+ WriteNullableEnable();
}
- private void WriteNullableEnable()
+ WriteNamespaceImports(loggerFactoryInterface, resourceType);
+
+ if (controllerNamespace != null)
{
- _sourceBuilder.AppendLine("#nullable enable");
- _sourceBuilder.AppendLine();
+ WriteNamespaceDeclaration(controllerNamespace);
}
- private void WriteNamespaceImports(INamedTypeSymbol loggerFactoryInterface, INamedTypeSymbol resourceType)
- {
- _sourceBuilder.AppendLine($@"using {loggerFactoryInterface.ContainingNamespace};");
+ WriteOpenClassDeclaration(controllerName, endpointsToGenerate, resourceType, idType);
+ _depth++;
- _sourceBuilder.AppendLine("using JsonApiDotNetCore.Configuration;");
- _sourceBuilder.AppendLine("using JsonApiDotNetCore.Controllers;");
- _sourceBuilder.AppendLine("using JsonApiDotNetCore.Services;");
+ WriteConstructor(controllerName, loggerFactoryInterface, endpointsToGenerate, resourceType, idType);
- if (!resourceType.ContainingNamespace.IsGlobalNamespace)
- {
- _sourceBuilder.AppendLine($"using {resourceType.ContainingNamespace};");
- }
+ _depth--;
+ WriteCloseCurly();
- _sourceBuilder.AppendLine();
- }
+ return _sourceBuilder.ToString();
+ }
- private void WriteNamespaceDeclaration(string controllerNamespace)
- {
- _sourceBuilder.AppendLine($"namespace {controllerNamespace};");
- _sourceBuilder.AppendLine();
- }
+ private void WriteAutoGeneratedComment()
+ {
+ _sourceBuilder.AppendLine("// ");
+ _sourceBuilder.AppendLine();
+ }
- private void WriteOpenClassDeclaration(string controllerName, JsonApiEndpointsCopy endpointsToGenerate, INamedTypeSymbol resourceType,
- ITypeSymbol idType)
- {
- string baseClassName = GetControllerBaseClassName(endpointsToGenerate);
+ private void WriteNullableEnable()
+ {
+ _sourceBuilder.AppendLine("#nullable enable");
+ _sourceBuilder.AppendLine();
+ }
- WriteIndent();
- _sourceBuilder.AppendLine($@"public sealed partial class {controllerName} : {baseClassName}<{resourceType.Name}, {idType}>");
+ private void WriteNamespaceImports(INamedTypeSymbol loggerFactoryInterface, INamedTypeSymbol resourceType)
+ {
+ _sourceBuilder.AppendLine($@"using {loggerFactoryInterface.ContainingNamespace};");
- WriteOpenCurly();
- }
+ _sourceBuilder.AppendLine("using JsonApiDotNetCore.Configuration;");
+ _sourceBuilder.AppendLine("using JsonApiDotNetCore.Controllers;");
+ _sourceBuilder.AppendLine("using JsonApiDotNetCore.Services;");
- private static string GetControllerBaseClassName(JsonApiEndpointsCopy endpointsToGenerate)
+ if (!resourceType.ContainingNamespace.IsGlobalNamespace)
{
- switch (endpointsToGenerate)
- {
- case JsonApiEndpointsCopy.Query:
- {
- return "JsonApiQueryController";
- }
- case JsonApiEndpointsCopy.Command:
- {
- return "JsonApiCommandController";
- }
- default:
- {
- return "JsonApiController";
- }
- }
+ _sourceBuilder.AppendLine($"using {resourceType.ContainingNamespace};");
}
- private void WriteConstructor(string controllerName, INamedTypeSymbol loggerFactoryInterface, JsonApiEndpointsCopy endpointsToGenerate,
- INamedTypeSymbol resourceType, ITypeSymbol idType)
- {
- string loggerName = loggerFactoryInterface.Name;
+ _sourceBuilder.AppendLine();
+ }
+
+ private void WriteNamespaceDeclaration(string controllerNamespace)
+ {
+ _sourceBuilder.AppendLine($"namespace {controllerNamespace};");
+ _sourceBuilder.AppendLine();
+ }
+
+ private void WriteOpenClassDeclaration(string controllerName, JsonApiEndpointsCopy endpointsToGenerate, INamedTypeSymbol resourceType, ITypeSymbol idType)
+ {
+ string baseClassName = GetControllerBaseClassName(endpointsToGenerate);
- WriteIndent();
- _sourceBuilder.AppendLine($"public {controllerName}(IJsonApiOptions options, IResourceGraph resourceGraph, {loggerName} loggerFactory,");
+ WriteIndent();
+ _sourceBuilder.AppendLine($@"public sealed partial class {controllerName} : {baseClassName}<{resourceType.Name}, {idType}>");
- _depth++;
+ WriteOpenCurly();
+ }
- if (AggregateEndpointToServiceNameMap.TryGetValue(endpointsToGenerate, out (string ServiceName, string ParameterName) value))
+ private static string GetControllerBaseClassName(JsonApiEndpointsCopy endpointsToGenerate)
+ {
+ switch (endpointsToGenerate)
+ {
+ case JsonApiEndpointsCopy.Query:
+ {
+ return "JsonApiQueryController";
+ }
+ case JsonApiEndpointsCopy.Command:
{
- WriteParameterListForShortConstructor(value.ServiceName, value.ParameterName, resourceType, idType);
+ return "JsonApiCommandController";
}
- else
+ default:
{
- WriteParameterListForLongConstructor(endpointsToGenerate, resourceType, idType);
+ return "JsonApiController";
}
+ }
+ }
- _depth--;
+ private void WriteConstructor(string controllerName, INamedTypeSymbol loggerFactoryInterface, JsonApiEndpointsCopy endpointsToGenerate,
+ INamedTypeSymbol resourceType, ITypeSymbol idType)
+ {
+ string loggerName = loggerFactoryInterface.Name;
- WriteOpenCurly();
- WriteCloseCurly();
- }
+ WriteIndent();
+ _sourceBuilder.AppendLine($"public {controllerName}(IJsonApiOptions options, IResourceGraph resourceGraph, {loggerName} loggerFactory,");
- private void WriteParameterListForShortConstructor(string serviceName, string parameterName, INamedTypeSymbol resourceType, ITypeSymbol idType)
- {
- WriteIndent();
- _sourceBuilder.AppendLine($"{serviceName}<{resourceType.Name}, {idType}> {parameterName})");
+ _depth++;
- WriteIndent();
- _sourceBuilder.AppendLine($": base(options, resourceGraph, loggerFactory, {parameterName})");
+ if (AggregateEndpointToServiceNameMap.TryGetValue(endpointsToGenerate, out (string ServiceName, string ParameterName) value))
+ {
+ WriteParameterListForShortConstructor(value.ServiceName, value.ParameterName, resourceType, idType);
}
-
- private void WriteParameterListForLongConstructor(JsonApiEndpointsCopy endpointsToGenerate, INamedTypeSymbol resourceType, ITypeSymbol idType)
+ else
{
- bool isFirstEntry = true;
+ WriteParameterListForLongConstructor(endpointsToGenerate, resourceType, idType);
+ }
+
+ _depth--;
+
+ WriteOpenCurly();
+ WriteCloseCurly();
+ }
+
+ private void WriteParameterListForShortConstructor(string serviceName, string parameterName, INamedTypeSymbol resourceType, ITypeSymbol idType)
+ {
+ WriteIndent();
+ _sourceBuilder.AppendLine($"{serviceName}<{resourceType.Name}, {idType}> {parameterName})");
+
+ WriteIndent();
+ _sourceBuilder.AppendLine($": base(options, resourceGraph, loggerFactory, {parameterName})");
+ }
+
+ private void WriteParameterListForLongConstructor(JsonApiEndpointsCopy endpointsToGenerate, INamedTypeSymbol resourceType, ITypeSymbol idType)
+ {
+ bool isFirstEntry = true;
- foreach (KeyValuePair entry in EndpointToServiceNameMap)
+ foreach (KeyValuePair entry in EndpointToServiceNameMap)
+ {
+ if ((endpointsToGenerate & entry.Key) == entry.Key)
{
- if ((endpointsToGenerate & entry.Key) == entry.Key)
+ if (isFirstEntry)
+ {
+ isFirstEntry = false;
+ }
+ else
{
- if (isFirstEntry)
- {
- isFirstEntry = false;
- }
- else
- {
- _sourceBuilder.AppendLine(Tokens.Comma);
- }
-
- WriteIndent();
- _sourceBuilder.Append($"{entry.Value.ServiceName}<{resourceType.Name}, {idType}> {entry.Value.ParameterName}");
+ _sourceBuilder.AppendLine(Tokens.Comma);
}
+
+ WriteIndent();
+ _sourceBuilder.Append($"{entry.Value.ServiceName}<{resourceType.Name}, {idType}> {entry.Value.ParameterName}");
}
+ }
- _sourceBuilder.AppendLine(Tokens.CloseParen);
+ _sourceBuilder.AppendLine(Tokens.CloseParen);
- WriteIndent();
- _sourceBuilder.AppendLine(": base(options, resourceGraph, loggerFactory,");
+ WriteIndent();
+ _sourceBuilder.AppendLine(": base(options, resourceGraph, loggerFactory,");
- isFirstEntry = true;
- _depth++;
+ isFirstEntry = true;
+ _depth++;
- foreach (KeyValuePair entry in EndpointToServiceNameMap)
+ foreach (KeyValuePair entry in EndpointToServiceNameMap)
+ {
+ if ((endpointsToGenerate & entry.Key) == entry.Key)
{
- if ((endpointsToGenerate & entry.Key) == entry.Key)
+ if (isFirstEntry)
{
- if (isFirstEntry)
- {
- isFirstEntry = false;
- }
- else
- {
- _sourceBuilder.AppendLine(Tokens.Comma);
- }
-
- WriteIndent();
- _sourceBuilder.Append($"{entry.Value.ParameterName}: {entry.Value.ParameterName}");
+ isFirstEntry = false;
+ }
+ else
+ {
+ _sourceBuilder.AppendLine(Tokens.Comma);
}
- }
- _sourceBuilder.AppendLine(Tokens.CloseParen);
- _depth--;
+ WriteIndent();
+ _sourceBuilder.Append($"{entry.Value.ParameterName}: {entry.Value.ParameterName}");
+ }
}
- private void WriteOpenCurly()
- {
- WriteIndent();
- _sourceBuilder.AppendLine(Tokens.OpenCurly);
- }
+ _sourceBuilder.AppendLine(Tokens.CloseParen);
+ _depth--;
+ }
- private void WriteCloseCurly()
- {
- WriteIndent();
- _sourceBuilder.AppendLine(Tokens.CloseCurly);
- }
+ private void WriteOpenCurly()
+ {
+ WriteIndent();
+ _sourceBuilder.AppendLine(Tokens.OpenCurly);
+ }
- private void WriteIndent()
- {
- // PERF: Reuse pre-calculated indents instead of allocating a new string each time.
- if (!IndentTable.TryGetValue(_depth, out string indent))
- {
- var diagnostic = Diagnostic.Create(_missingIndentInTableErrorDescriptor, Location.None, _depth.ToString());
- _context.ReportDiagnostic(diagnostic);
+ private void WriteCloseCurly()
+ {
+ WriteIndent();
+ _sourceBuilder.AppendLine(Tokens.CloseCurly);
+ }
- indent = new string(' ', _depth * SpacesPerIndent);
- }
+ private void WriteIndent()
+ {
+ // PERF: Reuse pre-calculated indents instead of allocating a new string each time.
+ if (!IndentTable.TryGetValue(_depth, out string? indent))
+ {
+ var diagnostic = Diagnostic.Create(_missingIndentInTableErrorDescriptor, Location.None, _depth.ToString());
+ _context.ReportDiagnostic(diagnostic);
- _sourceBuilder.Append(indent);
+ indent = new string(' ', _depth * SpacesPerIndent);
}
+ _sourceBuilder.Append(indent);
+ }
+
#pragma warning disable AV1008 // Class should not be static
- private static class Tokens
- {
- public const string OpenCurly = "{";
- public const string CloseCurly = "}";
- public const string CloseParen = ")";
- public const string Comma = ",";
- }
-#pragma warning restore AV1008 // Class should not be static
+ private static class Tokens
+ {
+ public const string OpenCurly = "{";
+ public const string CloseCurly = "}";
+ public const string CloseParen = ")";
+ public const string Comma = ",";
}
+#pragma warning restore AV1008 // Class should not be static
}
diff --git a/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs b/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs
index 0fbc18a758..ad7b0d6ad5 100644
--- a/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs
+++ b/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs
@@ -1,41 +1,38 @@
-using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-namespace JsonApiDotNetCore.SourceGenerators
+namespace JsonApiDotNetCore.SourceGenerators;
+
+///
+/// Collects type declarations in the project that have at least one attribute on them. Because this receiver operates at the syntax level, we cannot
+/// check for the expected attribute. This must be done during semantic analysis, because source code may contain any of these:
+/// { }
+///
+/// [ResourceAttribute]
+/// public class ExampleResource2 : Identifiable { }
+///
+/// [AlternateNamespaceName.Annotations.Resource]
+/// public class ExampleResource3 : Identifiable { }
+///
+/// [AlternateTypeName]
+/// public class ExampleResource4 : Identifiable { }
+/// ]]>
+///
+internal sealed class TypeWithAttributeSyntaxReceiver : ISyntaxReceiver
{
- ///
- /// Collects type declarations in the project that have at least one attribute on them. Because this receiver operates at the syntax level, we cannot
- /// check for the expected attribute. This must be done during semantic analysis, because source code may contain any of these:
- /// { }
- ///
- /// [ResourceAttribute]
- /// public class ExampleResource2 : Identifiable { }
- ///
- /// [AlternateNamespaceName.Annotations.Resource]
- /// public class ExampleResource3 : Identifiable { }
- ///
- /// [AlternateTypeName]
- /// public class ExampleResource4 : Identifiable { }
- /// ]]>
- ///
- ///
- internal sealed class TypeWithAttributeSyntaxReceiver : ISyntaxReceiver
- {
- public readonly ISet TypeDeclarations = new HashSet();
+ public readonly ISet TypeDeclarations = new HashSet();
- public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+ {
+ if (syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax && typeDeclarationSyntax.AttributeLists.Any())
{
- if (syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax && typeDeclarationSyntax.AttributeLists.Any())
- {
- TypeDeclarations.Add(typeDeclarationSyntax);
- }
+ TypeDeclarations.Add(typeDeclarationSyntax);
}
}
}
diff --git a/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransaction.cs b/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransaction.cs
index be5125c414..f59f86162d 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransaction.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransaction.cs
@@ -19,8 +19,8 @@ public sealed class EntityFrameworkCoreTransaction : IOperationsTransaction
public EntityFrameworkCoreTransaction(IDbContextTransaction transaction, DbContext dbContext)
{
- ArgumentGuard.NotNull(transaction, nameof(transaction));
- ArgumentGuard.NotNull(dbContext, nameof(dbContext));
+ ArgumentGuard.NotNull(transaction);
+ ArgumentGuard.NotNull(dbContext);
_transaction = transaction;
_dbContext = dbContext;
diff --git a/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransactionFactory.cs b/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransactionFactory.cs
index 96c66e12ab..8ef44cb627 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransactionFactory.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransactionFactory.cs
@@ -15,8 +15,8 @@ public sealed class EntityFrameworkCoreTransactionFactory : IOperationsTransacti
public EntityFrameworkCoreTransactionFactory(IDbContextResolver dbContextResolver, IJsonApiOptions options)
{
- ArgumentGuard.NotNull(dbContextResolver, nameof(dbContextResolver));
- ArgumentGuard.NotNull(options, nameof(options));
+ ArgumentGuard.NotNull(dbContextResolver);
+ ArgumentGuard.NotNull(options);
_dbContextResolver = dbContextResolver;
_options = options;
diff --git a/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs b/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs
index b0a1b3ee2f..2def7bfecc 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs
@@ -17,8 +17,8 @@ public void Reset()
///
public void Declare(string localId, ResourceType resourceType)
{
- ArgumentGuard.NotNullNorEmpty(localId, nameof(localId));
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
+ ArgumentGuard.NotNullNorEmpty(localId);
+ ArgumentGuard.NotNull(resourceType);
AssertIsNotDeclared(localId);
@@ -36,9 +36,9 @@ private void AssertIsNotDeclared(string localId)
///
public void Assign(string localId, ResourceType resourceType, string stringId)
{
- ArgumentGuard.NotNullNorEmpty(localId, nameof(localId));
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
- ArgumentGuard.NotNullNorEmpty(stringId, nameof(stringId));
+ ArgumentGuard.NotNullNorEmpty(localId);
+ ArgumentGuard.NotNull(resourceType);
+ ArgumentGuard.NotNullNorEmpty(stringId);
AssertIsDeclared(localId);
@@ -57,8 +57,8 @@ public void Assign(string localId, ResourceType resourceType, string stringId)
///
public string GetValue(string localId, ResourceType resourceType)
{
- ArgumentGuard.NotNullNorEmpty(localId, nameof(localId));
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
+ ArgumentGuard.NotNullNorEmpty(localId);
+ ArgumentGuard.NotNull(resourceType);
AssertIsDeclared(localId);
diff --git a/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs b/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs
index 9cb463ab10..fb75fe3c7f 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs
@@ -18,8 +18,8 @@ public sealed class LocalIdValidator
public LocalIdValidator(ILocalIdTracker localIdTracker, IResourceGraph resourceGraph)
{
- ArgumentGuard.NotNull(localIdTracker, nameof(localIdTracker));
- ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
+ ArgumentGuard.NotNull(localIdTracker);
+ ArgumentGuard.NotNull(resourceGraph);
_localIdTracker = localIdTracker;
_resourceGraph = resourceGraph;
@@ -27,7 +27,7 @@ public LocalIdValidator(ILocalIdTracker localIdTracker, IResourceGraph resourceG
public void Validate(IEnumerable operations)
{
- ArgumentGuard.NotNull(operations, nameof(operations));
+ ArgumentGuard.NotNull(operations);
_localIdTracker.Reset();
diff --git a/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs b/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs
index c032f78f8d..c2397267b3 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs
@@ -15,7 +15,7 @@ public class OperationProcessorAccessor : IOperationProcessorAccessor
public OperationProcessorAccessor(IServiceProvider serviceProvider)
{
- ArgumentGuard.NotNull(serviceProvider, nameof(serviceProvider));
+ ArgumentGuard.NotNull(serviceProvider);
_serviceProvider = serviceProvider;
}
@@ -23,7 +23,7 @@ public OperationProcessorAccessor(IServiceProvider serviceProvider)
///
public Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken)
{
- ArgumentGuard.NotNull(operation, nameof(operation));
+ ArgumentGuard.NotNull(operation);
IOperationProcessor processor = ResolveProcessor(operation);
return processor.ProcessAsync(operation, cancellationToken);
diff --git a/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs
index 6524252abf..6ecdfd6077 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs
@@ -25,13 +25,13 @@ public OperationsProcessor(IOperationProcessorAccessor operationProcessorAccesso
ILocalIdTracker localIdTracker, IResourceGraph resourceGraph, IJsonApiRequest request, ITargetedFields targetedFields,
ISparseFieldSetCache sparseFieldSetCache)
{
- ArgumentGuard.NotNull(operationProcessorAccessor, nameof(operationProcessorAccessor));
- ArgumentGuard.NotNull(operationsTransactionFactory, nameof(operationsTransactionFactory));
- ArgumentGuard.NotNull(localIdTracker, nameof(localIdTracker));
- ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
- ArgumentGuard.NotNull(request, nameof(request));
- ArgumentGuard.NotNull(targetedFields, nameof(targetedFields));
- ArgumentGuard.NotNull(sparseFieldSetCache, nameof(sparseFieldSetCache));
+ ArgumentGuard.NotNull(operationProcessorAccessor);
+ ArgumentGuard.NotNull(operationsTransactionFactory);
+ ArgumentGuard.NotNull(localIdTracker);
+ ArgumentGuard.NotNull(resourceGraph);
+ ArgumentGuard.NotNull(request);
+ ArgumentGuard.NotNull(targetedFields);
+ ArgumentGuard.NotNull(sparseFieldSetCache);
_operationProcessorAccessor = operationProcessorAccessor;
_operationsTransactionFactory = operationsTransactionFactory;
@@ -46,7 +46,7 @@ public OperationsProcessor(IOperationProcessorAccessor operationProcessorAccesso
///
public virtual async Task> ProcessAsync(IList operations, CancellationToken cancellationToken)
{
- ArgumentGuard.NotNull(operations, nameof(operations));
+ ArgumentGuard.NotNull(operations);
_localIdValidator.Validate(operations);
_localIdTracker.Reset();
diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs
index fc16847eec..c8997be8cd 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs
@@ -13,7 +13,7 @@ public class AddToRelationshipProcessor : IAddToRelationshipProc
public AddToRelationshipProcessor(IAddToRelationshipService service)
{
- ArgumentGuard.NotNull(service, nameof(service));
+ ArgumentGuard.NotNull(service);
_service = service;
}
@@ -21,7 +21,7 @@ public AddToRelationshipProcessor(IAddToRelationshipService serv
///
public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken)
{
- ArgumentGuard.NotNull(operation, nameof(operation));
+ ArgumentGuard.NotNull(operation);
var leftId = (TId)operation.Resource.GetTypedId();
ISet rightResourceIds = operation.GetSecondaryResources();
diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/CreateProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/CreateProcessor.cs
index b06ebd626e..e105f54a50 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/Processors/CreateProcessor.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/CreateProcessor.cs
@@ -14,8 +14,8 @@ public class CreateProcessor : ICreateProcessor
public CreateProcessor(ICreateService service, ILocalIdTracker localIdTracker)
{
- ArgumentGuard.NotNull(service, nameof(service));
- ArgumentGuard.NotNull(localIdTracker, nameof(localIdTracker));
+ ArgumentGuard.NotNull(service);
+ ArgumentGuard.NotNull(localIdTracker);
_service = service;
_localIdTracker = localIdTracker;
@@ -24,7 +24,7 @@ public CreateProcessor(ICreateService service, ILocalIdTracker l
///
public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken)
{
- ArgumentGuard.NotNull(operation, nameof(operation));
+ ArgumentGuard.NotNull(operation);
TResource? newResource = await _service.CreateAsync((TResource)operation.Resource, cancellationToken);
diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs
index e4001b75c1..356742f9b7 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs
@@ -13,7 +13,7 @@ public class DeleteProcessor : IDeleteProcessor
public DeleteProcessor(IDeleteService service)
{
- ArgumentGuard.NotNull(service, nameof(service));
+ ArgumentGuard.NotNull(service);
_service = service;
}
@@ -21,7 +21,7 @@ public DeleteProcessor(IDeleteService service)
///
public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken)
{
- ArgumentGuard.NotNull(operation, nameof(operation));
+ ArgumentGuard.NotNull(operation);
var id = (TId)operation.Resource.GetTypedId();
await _service.DeleteAsync(id, cancellationToken);
diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs
index 493ed2066f..b308d6935a 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs
@@ -13,7 +13,7 @@ public class RemoveFromRelationshipProcessor : IRemoveFromRelati
public RemoveFromRelationshipProcessor(IRemoveFromRelationshipService service)
{
- ArgumentGuard.NotNull(service, nameof(service));
+ ArgumentGuard.NotNull(service);
_service = service;
}
@@ -21,7 +21,7 @@ public RemoveFromRelationshipProcessor(IRemoveFromRelationshipService
public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken)
{
- ArgumentGuard.NotNull(operation, nameof(operation));
+ ArgumentGuard.NotNull(operation);
var leftId = (TId)operation.Resource.GetTypedId();
ISet rightResourceIds = operation.GetSecondaryResources();
diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs
index 5eb09ccbc3..083bd0d0fc 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs
@@ -15,7 +15,7 @@ public class SetRelationshipProcessor : ISetRelationshipProcesso
public SetRelationshipProcessor(ISetRelationshipService service)
{
- ArgumentGuard.NotNull(service, nameof(service));
+ ArgumentGuard.NotNull(service);
_service = service;
}
@@ -23,7 +23,7 @@ public SetRelationshipProcessor(ISetRelationshipService service)
///
public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken)
{
- ArgumentGuard.NotNull(operation, nameof(operation));
+ ArgumentGuard.NotNull(operation);
var leftId = (TId)operation.Resource.GetTypedId();
object? rightValue = GetRelationshipRightValue(operation);
diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs
index a02ac2d3ff..5611f8d1c2 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs
@@ -13,7 +13,7 @@ public class UpdateProcessor : IUpdateProcessor
public UpdateProcessor(IUpdateService service)
{
- ArgumentGuard.NotNull(service, nameof(service));
+ ArgumentGuard.NotNull(service);
_service = service;
}
@@ -21,7 +21,7 @@ public UpdateProcessor(IUpdateService service)
///
public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken)
{
- ArgumentGuard.NotNull(operation, nameof(operation));
+ ArgumentGuard.NotNull(operation);
var resource = (TResource)operation.Resource;
TResource? updated = await _service.UpdateAsync(resource.Id, resource, cancellationToken);
diff --git a/src/JsonApiDotNetCore/AtomicOperations/RevertRequestStateOnDispose.cs b/src/JsonApiDotNetCore/AtomicOperations/RevertRequestStateOnDispose.cs
index 453f78f1f2..1951333d0c 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/RevertRequestStateOnDispose.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/RevertRequestStateOnDispose.cs
@@ -16,7 +16,7 @@ internal sealed class RevertRequestStateOnDispose : IDisposable
public RevertRequestStateOnDispose(IJsonApiRequest request, ITargetedFields? targetedFields)
{
- ArgumentGuard.NotNull(request, nameof(request));
+ ArgumentGuard.NotNull(request);
_sourceRequest = request;
_backupRequest.CopyFrom(request);
diff --git a/src/JsonApiDotNetCore/CollectionExtensions.cs b/src/JsonApiDotNetCore/CollectionExtensions.cs
index a7f5e72ab6..133231eb23 100644
--- a/src/JsonApiDotNetCore/CollectionExtensions.cs
+++ b/src/JsonApiDotNetCore/CollectionExtensions.cs
@@ -18,8 +18,8 @@ public static bool IsNullOrEmpty([NotNullWhen(false)] this IEnumerable? so
public static int FindIndex(this IReadOnlyList source, Predicate match)
{
- ArgumentGuard.NotNull(source, nameof(source));
- ArgumentGuard.NotNull(match, nameof(match));
+ ArgumentGuard.NotNull(source);
+ ArgumentGuard.NotNull(match);
for (int index = 0; index < source.Count; index++)
{
@@ -82,8 +82,8 @@ public static IEnumerable WhereNotNull(this IEnumerable source)
public static void AddRange(this ICollection source, IEnumerable itemsToAdd)
{
- ArgumentGuard.NotNull(source, nameof(source));
- ArgumentGuard.NotNull(itemsToAdd, nameof(itemsToAdd));
+ ArgumentGuard.NotNull(source);
+ ArgumentGuard.NotNull(itemsToAdd);
foreach (T item in itemsToAdd)
{
diff --git a/src/JsonApiDotNetCore/Configuration/ApplicationBuilderExtensions.cs b/src/JsonApiDotNetCore/Configuration/ApplicationBuilderExtensions.cs
index a941e27218..8ed4e42a42 100644
--- a/src/JsonApiDotNetCore/Configuration/ApplicationBuilderExtensions.cs
+++ b/src/JsonApiDotNetCore/Configuration/ApplicationBuilderExtensions.cs
@@ -22,7 +22,7 @@ public static class ApplicationBuilderExtensions
///
public static void UseJsonApi(this IApplicationBuilder builder)
{
- ArgumentGuard.NotNull(builder, nameof(builder));
+ ArgumentGuard.NotNull(builder);
using (IServiceScope scope = builder.ApplicationServices.CreateScope())
{
diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs
index 597d22294d..bc7d17d89f 100644
--- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs
+++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs
@@ -14,15 +14,27 @@ public interface IJsonApiOptions
/// The URL prefix to use for exposed endpoints.
///
///
- /// options.Namespace = "api/v1";
+ ///
///
string? Namespace { get; }
///
- /// Specifies the default query string capabilities that can be used on exposed JSON:API attributes. Defaults to .
+ /// Specifies the default set of allowed capabilities on JSON:API attributes. Defaults to .
///
AttrCapabilities DefaultAttrCapabilities { get; }
+ ///
+ /// Specifies the default set of allowed capabilities on JSON:API to-one relationships. Defaults to .
+ ///
+ HasOneCapabilities DefaultHasOneCapabilities { get; }
+
+ ///
+ /// Specifies the default set of allowed capabilities on JSON:API to-many relationships. Defaults to .
+ ///
+ HasManyCapabilities DefaultHasManyCapabilities { get; }
+
///
/// Indicates whether responses should contain a jsonapi object that contains the highest JSON:API version supported. False by default.
///
@@ -42,10 +54,10 @@ public interface IJsonApiOptions
/// Use relative links for all resources. False by default.
///
///
- ///
+ ///
- ///
+ /// ]]>
+ ///
+ /// ]]>
///
bool UseRelativeLinks { get; }
diff --git a/src/JsonApiDotNetCore/Configuration/InverseNavigationResolver.cs b/src/JsonApiDotNetCore/Configuration/InverseNavigationResolver.cs
index 8e5f3f15a3..821be639f9 100644
--- a/src/JsonApiDotNetCore/Configuration/InverseNavigationResolver.cs
+++ b/src/JsonApiDotNetCore/Configuration/InverseNavigationResolver.cs
@@ -15,8 +15,8 @@ public sealed class InverseNavigationResolver : IInverseNavigationResolver
public InverseNavigationResolver(IResourceGraph resourceGraph, IEnumerable dbContextResolvers)
{
- ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
- ArgumentGuard.NotNull(dbContextResolvers, nameof(dbContextResolvers));
+ ArgumentGuard.NotNull(resourceGraph);
+ ArgumentGuard.NotNull(dbContextResolvers);
_resourceGraph = resourceGraph;
_dbContextResolvers = dbContextResolvers;
diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs
index 7cd4307ad1..82e0ff52e1 100644
--- a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs
+++ b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs
@@ -39,8 +39,8 @@ internal sealed class JsonApiApplicationBuilder : IJsonApiApplicationBuilder, ID
public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mvcBuilder)
{
- ArgumentGuard.NotNull(services, nameof(services));
- ArgumentGuard.NotNull(mvcBuilder, nameof(mvcBuilder));
+ ArgumentGuard.NotNull(services);
+ ArgumentGuard.NotNull(mvcBuilder);
_services = services;
_mvcBuilder = mvcBuilder;
@@ -73,7 +73,7 @@ public void ConfigureAutoDiscovery(Action? configureAuto
///
public void ConfigureResourceGraph(ICollection dbContextTypes, Action? configureResourceGraph)
{
- ArgumentGuard.NotNull(dbContextTypes, nameof(dbContextTypes));
+ ArgumentGuard.NotNull(dbContextTypes);
_serviceDiscoveryFacade.DiscoverResources();
@@ -126,7 +126,7 @@ public void DiscoverInjectables()
///
public void ConfigureServiceContainer(ICollection dbContextTypes)
{
- ArgumentGuard.NotNull(dbContextTypes, nameof(dbContextTypes));
+ ArgumentGuard.NotNull(dbContextTypes);
if (dbContextTypes.Any())
{
diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs
index eda6374acf..778ded8d59 100644
--- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs
+++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs
@@ -11,8 +11,8 @@ namespace JsonApiDotNetCore.Configuration;
[PublicAPI]
public sealed class JsonApiOptions : IJsonApiOptions
{
- private Lazy _lazySerializerWriteOptions;
- private Lazy _lazySerializerReadOptions;
+ private readonly Lazy _lazySerializerWriteOptions;
+ private readonly Lazy _lazySerializerReadOptions;
///
JsonSerializerOptions IJsonApiOptions.SerializerReadOptions => _lazySerializerReadOptions.Value;
@@ -26,6 +26,12 @@ public sealed class JsonApiOptions : IJsonApiOptions
///
public AttrCapabilities DefaultAttrCapabilities { get; set; } = AttrCapabilities.All;
+ ///
+ public HasOneCapabilities DefaultHasOneCapabilities { get; set; } = HasOneCapabilities.All;
+
+ ///
+ public HasManyCapabilities DefaultHasManyCapabilities { get; set; } = HasManyCapabilities.All;
+
///
public bool IncludeJsonApiVersion { get; set; }
@@ -102,15 +108,10 @@ public sealed class JsonApiOptions : IJsonApiOptions
}
};
- static JsonApiOptions()
- {
- // Bug workaround for https://github.com/dotnet/efcore/issues/27436
- AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue26779", true);
- }
-
public JsonApiOptions()
{
- _lazySerializerReadOptions = new Lazy(() => new JsonSerializerOptions(SerializerOptions), LazyThreadSafetyMode.PublicationOnly);
+ _lazySerializerReadOptions =
+ new Lazy(() => new JsonSerializerOptions(SerializerOptions), LazyThreadSafetyMode.ExecutionAndPublication);
_lazySerializerWriteOptions = new Lazy(() => new JsonSerializerOptions(SerializerOptions)
{
@@ -119,6 +120,6 @@ public JsonApiOptions()
new WriteOnlyDocumentConverter(),
new WriteOnlyRelationshipObjectConverter()
}
- }, LazyThreadSafetyMode.PublicationOnly);
+ }, LazyThreadSafetyMode.ExecutionAndPublication);
}
}
diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs b/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs
index 6b193bdc6f..a27acf8ebd 100644
--- a/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs
+++ b/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs
@@ -15,7 +15,7 @@ internal sealed class JsonApiValidationFilter : IPropertyValidationFilter
public JsonApiValidationFilter(IHttpContextAccessor httpContextAccessor)
{
- ArgumentGuard.NotNull(httpContextAccessor, nameof(httpContextAccessor));
+ ArgumentGuard.NotNull(httpContextAccessor);
_httpContextAccessor = httpContextAccessor;
}
diff --git a/src/JsonApiDotNetCore/Configuration/ResourceDescriptor.cs b/src/JsonApiDotNetCore/Configuration/ResourceDescriptor.cs
index 8747cdd18f..885f67567c 100644
--- a/src/JsonApiDotNetCore/Configuration/ResourceDescriptor.cs
+++ b/src/JsonApiDotNetCore/Configuration/ResourceDescriptor.cs
@@ -7,8 +7,8 @@ internal sealed class ResourceDescriptor
public ResourceDescriptor(Type resourceClrType, Type idClrType)
{
- ArgumentGuard.NotNull(resourceClrType, nameof(resourceClrType));
- ArgumentGuard.NotNull(idClrType, nameof(idClrType));
+ ArgumentGuard.NotNull(resourceClrType);
+ ArgumentGuard.NotNull(idClrType);
ResourceClrType = resourceClrType;
IdClrType = idClrType;
diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs
index d693fa2c3c..0dbaeb9623 100644
--- a/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs
+++ b/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs
@@ -18,7 +18,7 @@ public sealed class ResourceGraph : IResourceGraph
public ResourceGraph(IReadOnlySet resourceTypeSet)
{
- ArgumentGuard.NotNull(resourceTypeSet, nameof(resourceTypeSet));
+ ArgumentGuard.NotNull(resourceTypeSet);
_resourceTypeSet = resourceTypeSet;
@@ -51,7 +51,7 @@ public ResourceType GetResourceType(string publicName)
///
public ResourceType? FindResourceType(string publicName)
{
- ArgumentGuard.NotNull(publicName, nameof(publicName));
+ ArgumentGuard.NotNull(publicName);
return _resourceTypesByPublicName.TryGetValue(publicName, out ResourceType? resourceType) ? resourceType : null;
}
@@ -72,7 +72,7 @@ public ResourceType GetResourceType(Type resourceClrType)
///
public ResourceType? FindResourceType(Type resourceClrType)
{
- ArgumentGuard.NotNull(resourceClrType, nameof(resourceClrType));
+ ArgumentGuard.NotNull(resourceClrType);
Type typeToFind = IsLazyLoadingProxyForResourceType(resourceClrType) ? resourceClrType.BaseType! : resourceClrType;
return _resourceTypesByClrType.TryGetValue(typeToFind, out ResourceType? resourceType) ? resourceType : null;
@@ -94,7 +94,7 @@ public ResourceType GetResourceType()
public IReadOnlyCollection GetFields(Expression> selector)
where TResource : class, IIdentifiable
{
- ArgumentGuard.NotNull(selector, nameof(selector));
+ ArgumentGuard.NotNull(selector);
return FilterFields(selector);
}
@@ -103,7 +103,7 @@ public IReadOnlyCollection GetFields(Expressi
public IReadOnlyCollection GetAttributes(Expression> selector)
where TResource : class, IIdentifiable
{
- ArgumentGuard.NotNull(selector, nameof(selector));
+ ArgumentGuard.NotNull(selector);
return FilterFields(selector);
}
@@ -112,7 +112,7 @@ public IReadOnlyCollection GetAttributes(Expression GetRelationships(Expression> selector)
where TResource : class, IIdentifiable
{
- ArgumentGuard.NotNull(selector, nameof(selector));
+ ArgumentGuard.NotNull(selector);
return FilterFields(selector);
}
diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs
index e07318c7f7..2b6f19acd3 100644
--- a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs
+++ b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs
@@ -22,8 +22,8 @@ public class ResourceGraphBuilder
public ResourceGraphBuilder(IJsonApiOptions options, ILoggerFactory loggerFactory)
{
- ArgumentGuard.NotNull(options, nameof(options));
- ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory));
+ ArgumentGuard.NotNull(options);
+ ArgumentGuard.NotNull(loggerFactory);
_options = options;
_logger = loggerFactory.CreateLogger();
@@ -144,7 +144,7 @@ private static void ValidateRelationshipsInDerivedType(ResourceType resourceType
public ResourceGraphBuilder Add(DbContext dbContext)
{
- ArgumentGuard.NotNull(dbContext, nameof(dbContext));
+ ArgumentGuard.NotNull(dbContext);
foreach (IEntityType entityType in dbContext.Model.GetEntityTypes())
{
@@ -200,7 +200,7 @@ public ResourceGraphBuilder Add(string? publicName = null)
public ResourceGraphBuilder Add(Type resourceClrType, Type? idClrType = null, string? publicName = null)
#pragma warning restore AV1553 // Do not use optional parameters with default value null for strings, collections or tasks
{
- ArgumentGuard.NotNull(resourceClrType, nameof(resourceClrType));
+ ArgumentGuard.NotNull(resourceClrType);
if (_resourceTypesByClrType.ContainsKey(resourceClrType))
{
@@ -307,6 +307,7 @@ private IReadOnlyCollection GetRelationships(Type resourc
{
relationship.Property = property;
SetPublicName(relationship, property);
+ SetRelationshipCapabilities(relationship);
IncludeField(relationshipsByName, relationship);
}
@@ -317,10 +318,56 @@ private IReadOnlyCollection GetRelationships(Type resourc
private void SetPublicName(ResourceFieldAttribute field, PropertyInfo property)
{
- // ReSharper disable once ConstantNullCoalescingCondition
+ // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
field.PublicName ??= FormatPropertyName(property);
}
+ private void SetRelationshipCapabilities(RelationshipAttribute relationship)
+ {
+#pragma warning disable CS0618 // Type or member is obsolete
+ bool canInclude = relationship.CanInclude;
+#pragma warning restore CS0618 // Type or member is obsolete
+
+ if (relationship is HasOneAttribute hasOneRelationship)
+ {
+ SetHasOneRelationshipCapabilities(hasOneRelationship, canInclude);
+ }
+ else if (relationship is HasManyAttribute hasManyRelationship)
+ {
+ SetHasManyRelationshipCapabilities(hasManyRelationship, canInclude);
+ }
+ }
+
+ private void SetHasOneRelationshipCapabilities(HasOneAttribute hasOneRelationship, bool canInclude)
+ {
+ if (!hasOneRelationship.HasExplicitCapabilities)
+ {
+ hasOneRelationship.Capabilities = _options.DefaultHasOneCapabilities;
+ }
+
+ if (hasOneRelationship.HasExplicitCanInclude)
+ {
+ hasOneRelationship.Capabilities = canInclude
+ ? hasOneRelationship.Capabilities | HasOneCapabilities.AllowInclude
+ : hasOneRelationship.Capabilities & ~HasOneCapabilities.AllowInclude;
+ }
+ }
+
+ private void SetHasManyRelationshipCapabilities(HasManyAttribute hasManyRelationship, bool canInclude)
+ {
+ if (!hasManyRelationship.HasExplicitCapabilities)
+ {
+ hasManyRelationship.Capabilities = _options.DefaultHasManyCapabilities;
+ }
+
+ if (hasManyRelationship.HasExplicitCanInclude)
+ {
+ hasManyRelationship.Capabilities = canInclude
+ ? hasManyRelationship.Capabilities | HasManyCapabilities.AllowInclude
+ : hasManyRelationship.Capabilities & ~HasManyCapabilities.AllowInclude;
+ }
+ }
+
private IReadOnlyCollection GetEagerLoads(Type resourceClrType, int recursionDepth = 0)
{
AssertNoInfiniteRecursion(recursionDepth);
diff --git a/src/JsonApiDotNetCore/Configuration/ResourceNameFormatter.cs b/src/JsonApiDotNetCore/Configuration/ResourceNameFormatter.cs
index 82a54ff010..a6e12951a9 100644
--- a/src/JsonApiDotNetCore/Configuration/ResourceNameFormatter.cs
+++ b/src/JsonApiDotNetCore/Configuration/ResourceNameFormatter.cs
@@ -19,7 +19,7 @@ public ResourceNameFormatter(JsonNamingPolicy? namingPolicy)
///
public string FormatResourceName(Type resourceClrType)
{
- ArgumentGuard.NotNull(resourceClrType, nameof(resourceClrType));
+ ArgumentGuard.NotNull(resourceClrType);
var resourceAttribute = resourceClrType.GetCustomAttribute(true);
diff --git a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs
index 8e5d15a7c4..7ea42a2470 100644
--- a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs
+++ b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs
@@ -6,8 +6,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
-#pragma warning disable AV1130 // Return type in method signature should be an interface to an unchangeable collection
-
namespace JsonApiDotNetCore.Configuration;
[PublicAPI]
@@ -24,7 +22,7 @@ public static IServiceCollection AddJsonApi(this IServiceCollection services, Ac
ICollection? dbContextTypes = null)
#pragma warning restore AV1553 // Do not use optional parameters with default value null for strings, collections or tasks
{
- ArgumentGuard.NotNull(services, nameof(services));
+ ArgumentGuard.NotNull(services);
SetupApplicationBuilder(services, options, discovery, resources, mvcBuilder, dbContextTypes ?? Array.Empty());
@@ -61,7 +59,7 @@ private static void SetupApplicationBuilder(IServiceCollection services, Action<
///
public static IServiceCollection AddResourceService(this IServiceCollection services)
{
- ArgumentGuard.NotNull(services, nameof(services));
+ ArgumentGuard.NotNull(services);
RegisterTypeForUnboundInterfaces(services, typeof(TService), ServiceDiscoveryFacade.ServiceUnboundInterfaces);
@@ -74,7 +72,7 @@ public static IServiceCollection AddResourceService(this IServiceColle
///
public static IServiceCollection AddResourceRepository(this IServiceCollection services)
{
- ArgumentGuard.NotNull(services, nameof(services));
+ ArgumentGuard.NotNull(services);
RegisterTypeForUnboundInterfaces(services, typeof(TRepository), ServiceDiscoveryFacade.RepositoryUnboundInterfaces);
@@ -87,7 +85,7 @@ public static IServiceCollection AddResourceRepository(this IServic
///
public static IServiceCollection AddResourceDefinition(this IServiceCollection services)
{
- ArgumentGuard.NotNull(services, nameof(services));
+ ArgumentGuard.NotNull(services);
RegisterTypeForUnboundInterfaces(services, typeof(TResourceDefinition), ServiceDiscoveryFacade.ResourceDefinitionUnboundInterfaces);
diff --git a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs
index 7498391afd..85f95c232f 100644
--- a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs
+++ b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs
@@ -52,9 +52,9 @@ public sealed class ServiceDiscoveryFacade
public ServiceDiscoveryFacade(IServiceCollection services, ResourceGraphBuilder resourceGraphBuilder, ILoggerFactory loggerFactory)
{
- ArgumentGuard.NotNull(services, nameof(services));
- ArgumentGuard.NotNull(resourceGraphBuilder, nameof(resourceGraphBuilder));
- ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory));
+ ArgumentGuard.NotNull(services);
+ ArgumentGuard.NotNull(resourceGraphBuilder);
+ ArgumentGuard.NotNull(loggerFactory);
_logger = loggerFactory.CreateLogger();
_services = services;
@@ -74,7 +74,7 @@ public ServiceDiscoveryFacade AddCurrentAssembly()
///
public ServiceDiscoveryFacade AddAssembly(Assembly assembly)
{
- ArgumentGuard.NotNull(assembly, nameof(assembly));
+ ArgumentGuard.NotNull(assembly);
_assemblyCache.RegisterAssembly(assembly);
_logger.LogDebug($"Registering assembly '{assembly.FullName}' for discovery of resources and injectables.");
diff --git a/src/JsonApiDotNetCore/Configuration/TypeLocator.cs b/src/JsonApiDotNetCore/Configuration/TypeLocator.cs
index 2f004ffdf1..981c50c4e4 100644
--- a/src/JsonApiDotNetCore/Configuration/TypeLocator.cs
+++ b/src/JsonApiDotNetCore/Configuration/TypeLocator.cs
@@ -66,9 +66,9 @@ internal sealed class TypeLocator
public (Type implementationType, Type serviceInterface)? GetContainerRegistrationFromAssembly(Assembly assembly, Type unboundInterface,
params Type[] interfaceTypeArguments)
{
- ArgumentGuard.NotNull(assembly, nameof(assembly));
- ArgumentGuard.NotNull(unboundInterface, nameof(unboundInterface));
- ArgumentGuard.NotNull(interfaceTypeArguments, nameof(interfaceTypeArguments));
+ ArgumentGuard.NotNull(assembly);
+ ArgumentGuard.NotNull(unboundInterface);
+ ArgumentGuard.NotNull(interfaceTypeArguments);
if (!unboundInterface.IsInterface || !unboundInterface.IsGenericType || unboundInterface != unboundInterface.GetGenericTypeDefinition())
{
@@ -129,9 +129,9 @@ private static (Type implementationType, Type serviceInterface)? GetContainerReg
///
public IReadOnlyCollection GetDerivedTypesForUnboundType(Assembly assembly, Type unboundType, params Type[] typeArguments)
{
- ArgumentGuard.NotNull(assembly, nameof(assembly));
- ArgumentGuard.NotNull(unboundType, nameof(unboundType));
- ArgumentGuard.NotNull(typeArguments, nameof(typeArguments));
+ ArgumentGuard.NotNull(assembly);
+ ArgumentGuard.NotNull(unboundType);
+ ArgumentGuard.NotNull(typeArguments);
Type closedType = unboundType.MakeGenericType(typeArguments);
return GetDerivedTypes(assembly, closedType).ToArray();
@@ -147,14 +147,14 @@ public IReadOnlyCollection GetDerivedTypesForUnboundType(Assembly assembly
/// The inherited type.
///
///
- ///
+ ///
+ /// ]]>
///
public IEnumerable GetDerivedTypes(Assembly assembly, Type baseType)
{
- ArgumentGuard.NotNull(assembly, nameof(assembly));
- ArgumentGuard.NotNull(baseType, nameof(baseType));
+ ArgumentGuard.NotNull(assembly);
+ ArgumentGuard.NotNull(baseType);
foreach (Type type in assembly.GetTypes())
{
diff --git a/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs b/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs
index 975472ab28..eba4b8340c 100644
--- a/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs
+++ b/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs
@@ -46,7 +46,7 @@ public DisableQueryStringAttribute(JsonApiQueryStringParameters parameters)
///
public DisableQueryStringAttribute(string parameterNames)
{
- ArgumentGuard.NotNullNorEmpty(parameterNames, nameof(parameterNames));
+ ArgumentGuard.NotNullNorEmpty(parameterNames);
ParameterNames = parameterNames.Split(",").ToHashSet();
}
diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs
index cabe4d49d8..22efab2840 100644
--- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs
+++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs
@@ -63,9 +63,9 @@ protected BaseJsonApiController(IJsonApiOptions options, IResourceGraph resource
IUpdateService? update = null, ISetRelationshipService? setRelationship = null,
IDeleteService? delete = null, IRemoveFromRelationshipService? removeFromRelationship = null)
{
- ArgumentGuard.NotNull(options, nameof(options));
- ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
- ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory));
+ ArgumentGuard.NotNull(options);
+ ArgumentGuard.NotNull(resourceGraph);
+ ArgumentGuard.NotNull(loggerFactory);
_options = options;
_resourceGraph = resourceGraph;
@@ -139,7 +139,7 @@ public virtual async Task GetSecondaryAsync(TId id, string relati
relationshipName
});
- ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName));
+ ArgumentGuard.NotNullNorEmpty(relationshipName);
if (_getSecondary == null)
{
@@ -168,7 +168,7 @@ public virtual async Task GetRelationshipAsync(TId id, string rel
relationshipName
});
- ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName));
+ ArgumentGuard.NotNullNorEmpty(relationshipName);
if (_getRelationship == null)
{
@@ -192,7 +192,7 @@ public virtual async Task PostAsync([FromBody] TResource resource
resource
});
- ArgumentGuard.NotNull(resource, nameof(resource));
+ ArgumentGuard.NotNull(resource);
if (_create == null)
{
@@ -245,8 +245,8 @@ public virtual async Task PostRelationshipAsync(TId id, string re
rightResourceIds
});
- ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName));
- ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds));
+ ArgumentGuard.NotNullNorEmpty(relationshipName);
+ ArgumentGuard.NotNull(rightResourceIds);
if (_addToRelationship == null)
{
@@ -272,7 +272,7 @@ public virtual async Task PatchAsync(TId id, [FromBody] TResource
resource
});
- ArgumentGuard.NotNull(resource, nameof(resource));
+ ArgumentGuard.NotNull(resource);
if (_update == null)
{
@@ -320,7 +320,7 @@ public virtual async Task PatchRelationshipAsync(TId id, string r
rightValue
});
- ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName));
+ ArgumentGuard.NotNullNorEmpty(relationshipName);
if (_setRelationship == null)
{
@@ -381,8 +381,8 @@ public virtual async Task DeleteRelationshipAsync(TId id, string
rightResourceIds
});
- ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName));
- ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds));
+ ArgumentGuard.NotNullNorEmpty(relationshipName);
+ ArgumentGuard.NotNull(rightResourceIds);
if (_removeFromRelationship == null)
{
diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs
index a948d67edc..2ea8f89a87 100644
--- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs
+++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs
@@ -27,12 +27,12 @@ public abstract class BaseJsonApiOperationsController : CoreJsonApiController
protected BaseJsonApiOperationsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory,
IOperationsProcessor processor, IJsonApiRequest request, ITargetedFields targetedFields)
{
- ArgumentGuard.NotNull(options, nameof(options));
- ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
- ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory));
- ArgumentGuard.NotNull(processor, nameof(processor));
- ArgumentGuard.NotNull(request, nameof(request));
- ArgumentGuard.NotNull(targetedFields, nameof(targetedFields));
+ ArgumentGuard.NotNull(options);
+ ArgumentGuard.NotNull(resourceGraph);
+ ArgumentGuard.NotNull(loggerFactory);
+ ArgumentGuard.NotNull(processor);
+ ArgumentGuard.NotNull(request);
+ ArgumentGuard.NotNull(targetedFields);
_options = options;
_resourceGraph = resourceGraph;
@@ -109,7 +109,7 @@ public virtual async Task PostOperationsAsync([FromBody] IList errors)
{
IReadOnlyList? errorList = ToErrorList(errors);
- ArgumentGuard.NotNullNorEmpty(errorList, nameof(errors));
+ ArgumentGuard.NotNullNorEmpty(errorList);
return new ObjectResult(errorList)
{
diff --git a/src/JsonApiDotNetCore/Diagnostics/AspNetCodeTimerSession.cs b/src/JsonApiDotNetCore/Diagnostics/AspNetCodeTimerSession.cs
index bef29dba78..a42d734580 100644
--- a/src/JsonApiDotNetCore/Diagnostics/AspNetCodeTimerSession.cs
+++ b/src/JsonApiDotNetCore/Diagnostics/AspNetCodeTimerSession.cs
@@ -37,14 +37,14 @@ public ICodeTimer CodeTimer
public AspNetCodeTimerSession(IHttpContextAccessor httpContextAccessor)
{
- ArgumentGuard.NotNull(httpContextAccessor, nameof(httpContextAccessor));
+ ArgumentGuard.NotNull(httpContextAccessor);
_httpContextAccessor = httpContextAccessor;
}
public AspNetCodeTimerSession(HttpContext httpContext)
{
- ArgumentGuard.NotNull(httpContext, nameof(httpContext));
+ ArgumentGuard.NotNull(httpContext);
_httpContext = httpContext;
}
diff --git a/src/JsonApiDotNetCore/Diagnostics/CascadingCodeTimer.cs b/src/JsonApiDotNetCore/Diagnostics/CascadingCodeTimer.cs
index 44cc2955d9..a2aa7a379b 100644
--- a/src/JsonApiDotNetCore/Diagnostics/CascadingCodeTimer.cs
+++ b/src/JsonApiDotNetCore/Diagnostics/CascadingCodeTimer.cs
@@ -18,7 +18,7 @@ static CascadingCodeTimer()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- // Be default, measurements using Stopwatch can differ 25%-30% on the same function on the same computer.
+ // By default, measurements using Stopwatch can differ 25%-30% on the same function on the same computer.
// The steps below ensure to get an accuracy of 0.1%-0.2%. With this accuracy, algorithms can be tested and compared.
// https://www.codeproject.com/Articles/61964/Performance-Tests-Precise-Run-Time-Measurements-wi
diff --git a/src/JsonApiDotNetCore/Diagnostics/CodeTimingSessionManager.cs b/src/JsonApiDotNetCore/Diagnostics/CodeTimingSessionManager.cs
index d858aa6f4b..5a862409bc 100644
--- a/src/JsonApiDotNetCore/Diagnostics/CodeTimingSessionManager.cs
+++ b/src/JsonApiDotNetCore/Diagnostics/CodeTimingSessionManager.cs
@@ -62,7 +62,7 @@ private static void AssertHasActiveSession()
public static void Capture(ICodeTimerSession session)
{
- ArgumentGuard.NotNull(session, nameof(session));
+ ArgumentGuard.NotNull(session);
AssertNoActiveSession();
diff --git a/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs b/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs
index eede4fed25..be4de87fc8 100644
--- a/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs
+++ b/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs
@@ -26,9 +26,9 @@ public InvalidModelStateException(IReadOnlyDictionary
private static IEnumerable FromModelStateDictionary(IReadOnlyDictionary modelState, Type modelType,
IResourceGraph resourceGraph, bool includeExceptionStackTraceInErrors, Func? getCollectionElementTypeCallback)
{
- ArgumentGuard.NotNull(modelState, nameof(modelState));
- ArgumentGuard.NotNull(modelType, nameof(modelType));
- ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
+ ArgumentGuard.NotNull(modelState);
+ ArgumentGuard.NotNull(modelType);
+ ArgumentGuard.NotNull(resourceGraph);
List errorObjects = new();
@@ -229,8 +229,8 @@ private abstract class ModelStateKeySegment
protected ModelStateKeySegment(Type modelType, bool isInComplexType, string nextKey, string? sourcePointer, ModelStateKeySegment? parent,
Func? getCollectionElementTypeCallback)
{
- ArgumentGuard.NotNull(modelType, nameof(modelType));
- ArgumentGuard.NotNull(nextKey, nameof(nextKey));
+ ArgumentGuard.NotNull(modelType);
+ ArgumentGuard.NotNull(nextKey);
ModelType = modelType;
IsInComplexType = isInComplexType;
@@ -242,15 +242,15 @@ protected ModelStateKeySegment(Type modelType, bool isInComplexType, string next
public ModelStateKeySegment? GetNextSegment(Type modelType, bool isInComplexType, string? sourcePointer)
{
- ArgumentGuard.NotNull(modelType, nameof(modelType));
+ ArgumentGuard.NotNull(modelType);
return _nextKey == string.Empty ? null : CreateSegment(modelType, _nextKey, isInComplexType, this, sourcePointer, GetCollectionElementTypeCallback);
}
public static ModelStateKeySegment Create(Type modelType, string key, Func? getCollectionElementTypeCallback)
{
- ArgumentGuard.NotNull(modelType, nameof(modelType));
- ArgumentGuard.NotNull(key, nameof(key));
+ ArgumentGuard.NotNull(modelType);
+ ArgumentGuard.NotNull(key);
return CreateSegment(modelType, key, false, null, null, getCollectionElementTypeCallback);
}
@@ -359,14 +359,14 @@ public PropertySegment(string propertyName, Type modelType, bool isInComplexType
Func? getCollectionElementTypeCallback)
: base(modelType, isInComplexType, nextKey, sourcePointer, parent, getCollectionElementTypeCallback)
{
- ArgumentGuard.NotNull(propertyName, nameof(propertyName));
+ ArgumentGuard.NotNull(propertyName);
PropertyName = propertyName;
}
public static string GetPublicNameForProperty(PropertyInfo property)
{
- ArgumentGuard.NotNull(property, nameof(property));
+ ArgumentGuard.NotNull(property);
var jsonNameAttribute = property.GetCustomAttribute(true);
return jsonNameAttribute?.Name ?? property.Name;
diff --git a/src/JsonApiDotNetCore/Errors/JsonApiException.cs b/src/JsonApiDotNetCore/Errors/JsonApiException.cs
index 6bb62177dc..4571843e8d 100644
--- a/src/JsonApiDotNetCore/Errors/JsonApiException.cs
+++ b/src/JsonApiDotNetCore/Errors/JsonApiException.cs
@@ -24,7 +24,7 @@ public class JsonApiException : Exception
public JsonApiException(ErrorObject error, Exception? innerException = null)
: base(null, innerException)
{
- ArgumentGuard.NotNull(error, nameof(error));
+ ArgumentGuard.NotNull(error);
Errors = error.AsArray();
}
@@ -33,7 +33,7 @@ public JsonApiException(IEnumerable errors, Exception? innerExcepti
: base(null, innerException)
{
IReadOnlyList? errorList = ToErrorList(errors);
- ArgumentGuard.NotNullNorEmpty(errorList, nameof(errors));
+ ArgumentGuard.NotNullNorEmpty(errorList);
Errors = errorList;
}
diff --git a/src/JsonApiDotNetCore/Errors/MissingResourceInRelationship.cs b/src/JsonApiDotNetCore/Errors/MissingResourceInRelationship.cs
index e80181898d..42082d6126 100644
--- a/src/JsonApiDotNetCore/Errors/MissingResourceInRelationship.cs
+++ b/src/JsonApiDotNetCore/Errors/MissingResourceInRelationship.cs
@@ -11,9 +11,9 @@ public sealed class MissingResourceInRelationship
public MissingResourceInRelationship(string relationshipName, string resourceType, string resourceId)
{
- ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName));
- ArgumentGuard.NotNullNorEmpty(resourceType, nameof(resourceType));
- ArgumentGuard.NotNullNorEmpty(resourceId, nameof(resourceId));
+ ArgumentGuard.NotNullNorEmpty(relationshipName);
+ ArgumentGuard.NotNullNorEmpty(resourceType);
+ ArgumentGuard.NotNullNorEmpty(resourceId);
RelationshipName = relationshipName;
ResourceType = resourceType;
diff --git a/src/JsonApiDotNetCore/Errors/UnsuccessfulActionResultException.cs b/src/JsonApiDotNetCore/Errors/UnsuccessfulActionResultException.cs
index c5c55e4b70..e739ec9cbf 100644
--- a/src/JsonApiDotNetCore/Errors/UnsuccessfulActionResultException.cs
+++ b/src/JsonApiDotNetCore/Errors/UnsuccessfulActionResultException.cs
@@ -26,7 +26,7 @@ public UnsuccessfulActionResultException(ProblemDetails problemDetails)
private static ErrorObject ToError(ProblemDetails problemDetails)
{
- ArgumentGuard.NotNull(problemDetails, nameof(problemDetails));
+ ArgumentGuard.NotNull(problemDetails);
HttpStatusCode status = problemDetails.Status != null ? (HttpStatusCode)problemDetails.Status.Value : HttpStatusCode.InternalServerError;
diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj
index e401db38fa..9b73a09253 100644
--- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj
+++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj
@@ -43,6 +43,5 @@
-
diff --git a/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs b/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs
index cf51a82733..82e443a9af 100644
--- a/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs
+++ b/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs
@@ -10,8 +10,8 @@ public sealed class AsyncConvertEmptyActionResultFilter : IAsyncConvertEmptyActi
///
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
- ArgumentGuard.NotNull(context, nameof(context));
- ArgumentGuard.NotNull(next, nameof(next));
+ ArgumentGuard.NotNull(context);
+ ArgumentGuard.NotNull(next);
if (context.HttpContext.IsJsonApiRequest())
{
diff --git a/src/JsonApiDotNetCore/Middleware/AsyncJsonApiExceptionFilter.cs b/src/JsonApiDotNetCore/Middleware/AsyncJsonApiExceptionFilter.cs
index e87fc98389..58d568a3ec 100644
--- a/src/JsonApiDotNetCore/Middleware/AsyncJsonApiExceptionFilter.cs
+++ b/src/JsonApiDotNetCore/Middleware/AsyncJsonApiExceptionFilter.cs
@@ -13,7 +13,7 @@ public sealed class AsyncJsonApiExceptionFilter : IAsyncJsonApiExceptionFilter
public AsyncJsonApiExceptionFilter(IExceptionHandler exceptionHandler)
{
- ArgumentGuard.NotNull(exceptionHandler, nameof(exceptionHandler));
+ ArgumentGuard.NotNull(exceptionHandler);
_exceptionHandler = exceptionHandler;
}
@@ -21,7 +21,7 @@ public AsyncJsonApiExceptionFilter(IExceptionHandler exceptionHandler)
///
public Task OnExceptionAsync(ExceptionContext context)
{
- ArgumentGuard.NotNull(context, nameof(context));
+ ArgumentGuard.NotNull(context);
if (context.HttpContext.IsJsonApiRequest())
{
diff --git a/src/JsonApiDotNetCore/Middleware/AsyncQueryStringActionFilter.cs b/src/JsonApiDotNetCore/Middleware/AsyncQueryStringActionFilter.cs
index 6f31c28d2a..89164c844f 100644
--- a/src/JsonApiDotNetCore/Middleware/AsyncQueryStringActionFilter.cs
+++ b/src/JsonApiDotNetCore/Middleware/AsyncQueryStringActionFilter.cs
@@ -12,7 +12,7 @@ public sealed class AsyncQueryStringActionFilter : IAsyncQueryStringActionFilter
public AsyncQueryStringActionFilter(IQueryStringReader queryStringReader)
{
- ArgumentGuard.NotNull(queryStringReader, nameof(queryStringReader));
+ ArgumentGuard.NotNull(queryStringReader);
_queryStringReader = queryStringReader;
}
@@ -20,8 +20,8 @@ public AsyncQueryStringActionFilter(IQueryStringReader queryStringReader)
///
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
- ArgumentGuard.NotNull(context, nameof(context));
- ArgumentGuard.NotNull(next, nameof(next));
+ ArgumentGuard.NotNull(context);
+ ArgumentGuard.NotNull(next);
if (context.HttpContext.IsJsonApiRequest())
{
diff --git a/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs b/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs
index ea1d67743d..b8690402a5 100644
--- a/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs
+++ b/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs
@@ -17,8 +17,8 @@ public class ExceptionHandler : IExceptionHandler
public ExceptionHandler(ILoggerFactory loggerFactory, IJsonApiOptions options)
{
- ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory));
- ArgumentGuard.NotNull(options, nameof(options));
+ ArgumentGuard.NotNull(loggerFactory);
+ ArgumentGuard.NotNull(options);
_options = options;
_logger = loggerFactory.CreateLogger();
@@ -26,7 +26,7 @@ public ExceptionHandler(ILoggerFactory loggerFactory, IJsonApiOptions options)
public IReadOnlyList HandleException(Exception exception)
{
- ArgumentGuard.NotNull(exception, nameof(exception));
+ ArgumentGuard.NotNull(exception);
Exception demystified = exception.Demystify();
@@ -45,7 +45,7 @@ private void LogException(Exception exception)
protected virtual LogLevel GetLogLevel(Exception exception)
{
- ArgumentGuard.NotNull(exception, nameof(exception));
+ ArgumentGuard.NotNull(exception);
if (exception is OperationCanceledException)
{
@@ -62,14 +62,14 @@ protected virtual LogLevel GetLogLevel(Exception exception)
protected virtual string GetLogMessage(Exception exception)
{
- ArgumentGuard.NotNull(exception, nameof(exception));
+ ArgumentGuard.NotNull(exception);
return exception is JsonApiException jsonApiException ? jsonApiException.GetSummary() : exception.Message;
}
protected virtual IReadOnlyList CreateErrorResponse(Exception exception)
{
- ArgumentGuard.NotNull(exception, nameof(exception));
+ ArgumentGuard.NotNull(exception);
IReadOnlyList errors = exception is JsonApiException jsonApiException ? jsonApiException.Errors :
exception is OperationCanceledException ? new ErrorObject((HttpStatusCode)499)
diff --git a/src/JsonApiDotNetCore/Middleware/HttpContextExtensions.cs b/src/JsonApiDotNetCore/Middleware/HttpContextExtensions.cs
index b6785e8198..a675aeeaff 100644
--- a/src/JsonApiDotNetCore/Middleware/HttpContextExtensions.cs
+++ b/src/JsonApiDotNetCore/Middleware/HttpContextExtensions.cs
@@ -13,7 +13,7 @@ public static class HttpContextExtensions
///
public static bool IsJsonApiRequest(this HttpContext httpContext)
{
- ArgumentGuard.NotNull(httpContext, nameof(httpContext));
+ ArgumentGuard.NotNull(httpContext);
string? value = httpContext.Items[IsJsonApiRequestKey] as string;
return value == bool.TrueString;
@@ -21,7 +21,7 @@ public static bool IsJsonApiRequest(this HttpContext httpContext)
internal static void RegisterJsonApiRequest(this HttpContext httpContext)
{
- ArgumentGuard.NotNull(httpContext, nameof(httpContext));
+ ArgumentGuard.NotNull(httpContext);
httpContext.Items[IsJsonApiRequestKey] = bool.TrueString;
}
diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs
index 077f0573f0..59563c9268 100644
--- a/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs
+++ b/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs
@@ -10,7 +10,7 @@ public sealed class JsonApiInputFormatter : IJsonApiInputFormatter
///
public bool CanRead(InputFormatterContext context)
{
- ArgumentGuard.NotNull(context, nameof(context));
+ ArgumentGuard.NotNull(context);
return context.HttpContext.IsJsonApiRequest();
}
@@ -18,7 +18,7 @@ public bool CanRead(InputFormatterContext context)
///
public async Task ReadAsync(InputFormatterContext context)
{
- ArgumentGuard.NotNull(context, nameof(context));
+ ArgumentGuard.NotNull(context);
var reader = context.HttpContext.RequestServices.GetRequiredService();
diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs
index 94507750da..b38ad986dd 100644
--- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs
+++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs
@@ -36,11 +36,11 @@ public JsonApiMiddleware(RequestDelegate next, IHttpContextAccessor httpContextA
public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMapping controllerResourceMapping, IJsonApiOptions options,
IJsonApiRequest request, ILogger logger)
{
- ArgumentGuard.NotNull(httpContext, nameof(httpContext));
- ArgumentGuard.NotNull(controllerResourceMapping, nameof(controllerResourceMapping));
- ArgumentGuard.NotNull(options, nameof(options));
- ArgumentGuard.NotNull(request, nameof(request));
- ArgumentGuard.NotNull(logger, nameof(logger));
+ ArgumentGuard.NotNull(httpContext);
+ ArgumentGuard.NotNull(controllerResourceMapping);
+ ArgumentGuard.NotNull(options);
+ ArgumentGuard.NotNull(request);
+ ArgumentGuard.NotNull(logger);
using (CodeTimingSessionManager.Current.Measure("JSON:API middleware"))
{
@@ -72,7 +72,7 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin
return;
}
- SetupOperationsRequest((JsonApiRequest)request, options, httpContext.Request);
+ SetupOperationsRequest((JsonApiRequest)request);
httpContext.RegisterJsonApiRequest();
}
@@ -280,7 +280,7 @@ private static bool IsRouteForOperations(RouteValueDictionary routeValues)
return actionName == "PostOperations";
}
- private static void SetupOperationsRequest(JsonApiRequest request, IJsonApiOptions options, HttpRequest httpRequest)
+ private static void SetupOperationsRequest(JsonApiRequest request)
{
request.IsReadOnly = false;
request.Kind = EndpointKind.AtomicOperations;
diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs
index c32bb9d9f9..8c97a12ea4 100644
--- a/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs
+++ b/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs
@@ -10,7 +10,7 @@ public sealed class JsonApiOutputFormatter : IJsonApiOutputFormatter
///
public bool CanWriteResult(OutputFormatterCanWriteContext context)
{
- ArgumentGuard.NotNull(context, nameof(context));
+ ArgumentGuard.NotNull(context);
return context.HttpContext.IsJsonApiRequest();
}
@@ -18,7 +18,7 @@ public bool CanWriteResult(OutputFormatterCanWriteContext context)
///
public async Task WriteAsync(OutputFormatterWriteContext context)
{
- ArgumentGuard.NotNull(context, nameof(context));
+ ArgumentGuard.NotNull(context);
var writer = context.HttpContext.RequestServices.GetRequiredService();
await writer.WriteAsync(context.Object, context.HttpContext);
diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs b/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs
index a28c01fcd6..98e42823a3 100644
--- a/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs
+++ b/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs
@@ -38,7 +38,7 @@ public sealed class JsonApiRequest : IJsonApiRequest
///
public void CopyFrom(IJsonApiRequest other)
{
- ArgumentGuard.NotNull(other, nameof(other));
+ ArgumentGuard.NotNull(other);
Kind = other.Kind;
PrimaryId = other.PrimaryId;
diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs
index fe95d93446..0f596326f7 100644
--- a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs
+++ b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs
@@ -36,8 +36,8 @@ public sealed class JsonApiRoutingConvention : IJsonApiRoutingConvention
public JsonApiRoutingConvention(IJsonApiOptions options, IResourceGraph resourceGraph)
{
- ArgumentGuard.NotNull(options, nameof(options));
- ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
+ ArgumentGuard.NotNull(options);
+ ArgumentGuard.NotNull(resourceGraph);
_options = options;
_resourceGraph = resourceGraph;
@@ -60,7 +60,7 @@ public JsonApiRoutingConvention(IJsonApiOptions options, IResourceGraph resource
///
public void Apply(ApplicationModel application)
{
- ArgumentGuard.NotNull(application, nameof(application));
+ ArgumentGuard.NotNull(application);
foreach (ControllerModel controller in application.Controllers)
{
@@ -78,7 +78,8 @@ public void Apply(ApplicationModel application)
{
if (_controllerPerResourceTypeMap.ContainsKey(resourceType))
{
- throw new InvalidConfigurationException($"Multiple controllers found for resource type '{resourceType}'.");
+ throw new InvalidConfigurationException(
+ $"Multiple controllers found for resource type '{resourceType}': '{_controllerPerResourceTypeMap[resourceType].ControllerType}' and '{controller.ControllerType}'.");
}
_resourceTypePerControllerTypeMap.Add(controller.ControllerType, resourceType);
diff --git a/src/JsonApiDotNetCore/Queries/ExpressionInScope.cs b/src/JsonApiDotNetCore/Queries/ExpressionInScope.cs
index 37d40f127b..6ac6f75059 100644
--- a/src/JsonApiDotNetCore/Queries/ExpressionInScope.cs
+++ b/src/JsonApiDotNetCore/Queries/ExpressionInScope.cs
@@ -15,7 +15,7 @@ public class ExpressionInScope
public ExpressionInScope(ResourceFieldChainExpression? scope, QueryExpression expression)
{
- ArgumentGuard.NotNull(expression, nameof(expression));
+ ArgumentGuard.NotNull(expression);
Scope = scope;
Expression = expression;
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs
index 980a7846bc..2b855b1bdb 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs
@@ -16,8 +16,8 @@ public class AnyExpression : FilterExpression
public AnyExpression(ResourceFieldChainExpression targetAttribute, IImmutableSet constants)
{
- ArgumentGuard.NotNull(targetAttribute, nameof(targetAttribute));
- ArgumentGuard.NotNull(constants, nameof(constants));
+ ArgumentGuard.NotNull(targetAttribute);
+ ArgumentGuard.NotNull(constants);
if (constants.Count < 2)
{
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/ComparisonExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/ComparisonExpression.cs
index 9bf1c3bde8..cdae713f3d 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/ComparisonExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/ComparisonExpression.cs
@@ -15,8 +15,8 @@ public class ComparisonExpression : FilterExpression
public ComparisonExpression(ComparisonOperator @operator, QueryExpression left, QueryExpression right)
{
- ArgumentGuard.NotNull(left, nameof(left));
- ArgumentGuard.NotNull(right, nameof(right));
+ ArgumentGuard.NotNull(left);
+ ArgumentGuard.NotNull(right);
Operator = @operator;
Left = left;
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/CountExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/CountExpression.cs
index 5de89ead7c..2eff0a86e9 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/CountExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/CountExpression.cs
@@ -13,7 +13,7 @@ public class CountExpression : FunctionExpression
public CountExpression(ResourceFieldChainExpression targetCollection)
{
- ArgumentGuard.NotNull(targetCollection, nameof(targetCollection));
+ ArgumentGuard.NotNull(targetCollection);
TargetCollection = targetCollection;
}
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs
index c5387106d6..825119fe33 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs
@@ -15,7 +15,7 @@ public class HasExpression : FilterExpression
public HasExpression(ResourceFieldChainExpression targetCollection, FilterExpression? filter)
{
- ArgumentGuard.NotNull(targetCollection, nameof(targetCollection));
+ ArgumentGuard.NotNull(targetCollection);
TargetCollection = targetCollection;
Filter = filter;
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs
index b35c48efbd..8b2034a374 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs
@@ -29,7 +29,7 @@ internal sealed class IncludeChainConverter
///
public IReadOnlyCollection GetRelationshipChains(IncludeExpression include)
{
- ArgumentGuard.NotNull(include, nameof(include));
+ ArgumentGuard.NotNull(include);
if (!include.Elements.Any())
{
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs
index e76aaf0946..01c25dad4e 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs
@@ -21,8 +21,8 @@ public IncludeElementExpression(RelationshipAttribute relationship)
public IncludeElementExpression(RelationshipAttribute relationship, IImmutableSet children)
{
- ArgumentGuard.NotNull(relationship, nameof(relationship));
- ArgumentGuard.NotNull(children, nameof(children));
+ ArgumentGuard.NotNull(relationship);
+ ArgumentGuard.NotNull(children);
Relationship = relationship;
Children = children;
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs
index a63d87719d..69373c9abf 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs
@@ -17,7 +17,7 @@ public class IncludeExpression : QueryExpression
public IncludeExpression(IImmutableSet elements)
{
- ArgumentGuard.NotNullNorEmpty(elements, nameof(elements));
+ ArgumentGuard.NotNullNorEmpty(elements);
Elements = elements;
}
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IsTypeExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/IsTypeExpression.cs
index a30e31308b..4e259b358e 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/IsTypeExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/IsTypeExpression.cs
@@ -18,7 +18,7 @@ public class IsTypeExpression : FilterExpression
public IsTypeExpression(ResourceFieldChainExpression? targetToOneRelationship, ResourceType derivedType, FilterExpression? child)
{
- ArgumentGuard.NotNull(derivedType, nameof(derivedType));
+ ArgumentGuard.NotNull(derivedType);
TargetToOneRelationship = targetToOneRelationship;
DerivedType = derivedType;
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs
index 17c62f230f..578643d5db 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs
@@ -12,7 +12,7 @@ public class LiteralConstantExpression : IdentifierExpression
public LiteralConstantExpression(string text)
{
- ArgumentGuard.NotNull(text, nameof(text));
+ ArgumentGuard.NotNull(text);
Value = text;
}
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs
index c8d8ffb24b..08f970aee5 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs
@@ -21,7 +21,7 @@ public LogicalExpression(LogicalOperator @operator, params FilterExpression[] te
public LogicalExpression(LogicalOperator @operator, IImmutableList terms)
{
- ArgumentGuard.NotNull(terms, nameof(terms));
+ ArgumentGuard.NotNull(terms);
if (terms.Count < 2)
{
@@ -34,7 +34,7 @@ public LogicalExpression(LogicalOperator @operator, IImmutableList terms = filters.WhereNotNull().ToImmutableArray();
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs
index a9c598402b..5d9ed08859 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs
@@ -16,8 +16,8 @@ public class MatchTextExpression : FilterExpression
public MatchTextExpression(ResourceFieldChainExpression targetAttribute, LiteralConstantExpression textValue, TextMatchKind matchKind)
{
- ArgumentGuard.NotNull(targetAttribute, nameof(targetAttribute));
- ArgumentGuard.NotNull(textValue, nameof(textValue));
+ ArgumentGuard.NotNull(targetAttribute);
+ ArgumentGuard.NotNull(textValue);
TargetAttribute = targetAttribute;
TextValue = textValue;
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/NotExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/NotExpression.cs
index 4d28c4a9c3..ae198cd3ee 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/NotExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/NotExpression.cs
@@ -13,7 +13,7 @@ public class NotExpression : FilterExpression
public NotExpression(FilterExpression child)
{
- ArgumentGuard.NotNull(child, nameof(child));
+ ArgumentGuard.NotNull(child);
Child = child;
}
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/PaginationExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/PaginationExpression.cs
index 97ff8b1456..2ecd9901a2 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/PaginationExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/PaginationExpression.cs
@@ -14,7 +14,7 @@ public class PaginationExpression : QueryExpression
public PaginationExpression(PageNumber pageNumber, PageSize? pageSize)
{
- ArgumentGuard.NotNull(pageNumber, nameof(pageNumber));
+ ArgumentGuard.NotNull(pageNumber);
PageNumber = pageNumber;
PageSize = pageSize;
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs
index 594dab297a..a65e9c0a15 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs
@@ -13,7 +13,7 @@ public class PaginationQueryStringValueExpression : QueryExpression
public PaginationQueryStringValueExpression(IImmutableList elements)
{
- ArgumentGuard.NotNullNorEmpty(elements, nameof(elements));
+ ArgumentGuard.NotNullNorEmpty(elements);
Elements = elements;
}
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryStringParameterScopeExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryStringParameterScopeExpression.cs
index e567da8778..bc2d018033 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/QueryStringParameterScopeExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryStringParameterScopeExpression.cs
@@ -13,7 +13,7 @@ public class QueryStringParameterScopeExpression : QueryExpression
public QueryStringParameterScopeExpression(LiteralConstantExpression parameterName, ResourceFieldChainExpression? scope)
{
- ArgumentGuard.NotNull(parameterName, nameof(parameterName));
+ ArgumentGuard.NotNull(parameterName);
ParameterName = parameterName;
Scope = scope;
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs
index 4cd035ba2d..872cdb1aac 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs
@@ -15,16 +15,14 @@ public class QueryableHandlerExpression : QueryExpression
public QueryableHandlerExpression(object queryableHandler, StringValues parameterValue)
{
- ArgumentGuard.NotNull(queryableHandler, nameof(queryableHandler));
+ ArgumentGuard.NotNull(queryableHandler);
_queryableHandler = queryableHandler;
_parameterValue = parameterValue;
}
-#pragma warning disable AV1130 // Return type in method signature should be an interface to an unchangeable collection
public IQueryable Apply(IQueryable query)
where TResource : class, IIdentifiable
-#pragma warning restore AV1130 // Return type in method signature should be an interface to an unchangeable collection
{
var handler = (Func, StringValues, IQueryable>)_queryableHandler;
return handler(query, _parameterValue);
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs
index 7decec6221..9224642133 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs
@@ -14,14 +14,14 @@ public class ResourceFieldChainExpression : IdentifierExpression
public ResourceFieldChainExpression(ResourceFieldAttribute field)
{
- ArgumentGuard.NotNull(field, nameof(field));
+ ArgumentGuard.NotNull(field);
Fields = ImmutableArray.Create(field);
}
public ResourceFieldChainExpression(IImmutableList fields)
{
- ArgumentGuard.NotNullNorEmpty(fields, nameof(fields));
+ ArgumentGuard.NotNullNorEmpty(fields);
Fields = fields;
}
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs
index 78de440a42..bfdf30e8d5 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs
@@ -15,7 +15,7 @@ public class SortElementExpression : QueryExpression
public SortElementExpression(ResourceFieldChainExpression targetAttribute, bool isAscending)
{
- ArgumentGuard.NotNull(targetAttribute, nameof(targetAttribute));
+ ArgumentGuard.NotNull(targetAttribute);
TargetAttribute = targetAttribute;
IsAscending = isAscending;
@@ -23,7 +23,7 @@ public SortElementExpression(ResourceFieldChainExpression targetAttribute, bool
public SortElementExpression(CountExpression count, bool isAscending)
{
- ArgumentGuard.NotNull(count, nameof(count));
+ ArgumentGuard.NotNull(count);
Count = count;
IsAscending = isAscending;
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs
index dc0aebd320..53b067d4e8 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs
@@ -13,7 +13,7 @@ public class SortExpression : QueryExpression
public SortExpression(IImmutableList elements)
{
- ArgumentGuard.NotNullNorEmpty(elements, nameof(elements));
+ ArgumentGuard.NotNullNorEmpty(elements);
Elements = elements;
}
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs
index bc1e611bd8..f36427b2e1 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs
@@ -14,7 +14,7 @@ public class SparseFieldSetExpression : QueryExpression
public SparseFieldSetExpression(IImmutableSet fields)
{
- ArgumentGuard.NotNullNorEmpty(fields, nameof(fields));
+ ArgumentGuard.NotNullNorEmpty(fields);
Fields = fields;
}
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs
index 53f9ff0eb6..c7c331eb46 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs
@@ -14,8 +14,8 @@ public static class SparseFieldSetExpressionExtensions
Expression> fieldSelector, IResourceGraph resourceGraph)
where TResource : class, IIdentifiable
{
- ArgumentGuard.NotNull(fieldSelector, nameof(fieldSelector));
- ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
+ ArgumentGuard.NotNull(fieldSelector);
+ ArgumentGuard.NotNull(resourceGraph);
SparseFieldSetExpression? newSparseFieldSet = sparseFieldSet;
@@ -42,8 +42,8 @@ public static class SparseFieldSetExpressionExtensions
Expression> fieldSelector, IResourceGraph resourceGraph)
where TResource : class, IIdentifiable
{
- ArgumentGuard.NotNull(fieldSelector, nameof(fieldSelector));
- ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
+ ArgumentGuard.NotNull(fieldSelector);
+ ArgumentGuard.NotNull(resourceGraph);
SparseFieldSetExpression? newSparseFieldSet = sparseFieldSet;
diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs
index 8e52df9b3b..c69be71292 100644
--- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs
+++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs
@@ -15,7 +15,7 @@ public class SparseFieldTableExpression : QueryExpression
public SparseFieldTableExpression(IImmutableDictionary table)
{
- ArgumentGuard.NotNullNorEmpty(table, nameof(table), "entries");
+ ArgumentGuard.NotNullNorEmpty(table);
Table = table;
}
diff --git a/src/JsonApiDotNetCore/Queries/FieldSelection.cs b/src/JsonApiDotNetCore/Queries/FieldSelection.cs
index 54c59005bf..7f62db1fcf 100644
--- a/src/JsonApiDotNetCore/Queries/FieldSelection.cs
+++ b/src/JsonApiDotNetCore/Queries/FieldSelection.cs
@@ -23,7 +23,7 @@ public IReadOnlySet GetResourceTypes()
public FieldSelectors GetOrCreateSelectors(ResourceType resourceType)
#pragma warning restore AV1130 // Return type in method signature should be an interface to an unchangeable collection
{
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
+ ArgumentGuard.NotNull(resourceType);
if (!ContainsKey(resourceType))
{
diff --git a/src/JsonApiDotNetCore/Queries/FieldSelectors.cs b/src/JsonApiDotNetCore/Queries/FieldSelectors.cs
index a07b4f0c79..ffd95c01bc 100644
--- a/src/JsonApiDotNetCore/Queries/FieldSelectors.cs
+++ b/src/JsonApiDotNetCore/Queries/FieldSelectors.cs
@@ -30,21 +30,21 @@ public bool ContainsOnlyRelationships
public bool ContainsField(ResourceFieldAttribute field)
{
- ArgumentGuard.NotNull(field, nameof(field));
+ ArgumentGuard.NotNull(field);
return ContainsKey(field);
}
public void IncludeAttribute(AttrAttribute attribute)
{
- ArgumentGuard.NotNull(attribute, nameof(attribute));
+ ArgumentGuard.NotNull(attribute);
this[attribute] = null;
}
public void IncludeAttributes(IEnumerable attributes)
{
- ArgumentGuard.NotNull(attributes, nameof(attributes));
+ ArgumentGuard.NotNull(attributes);
foreach (AttrAttribute attribute in attributes)
{
@@ -54,7 +54,7 @@ public void IncludeAttributes(IEnumerable attributes)
public void IncludeRelationship(RelationshipAttribute relationship, QueryLayer? queryLayer)
{
- ArgumentGuard.NotNull(relationship, nameof(relationship));
+ ArgumentGuard.NotNull(relationship);
this[relationship] = queryLayer;
}
diff --git a/src/JsonApiDotNetCore/Queries/Internal/EvaluatedIncludeCache.cs b/src/JsonApiDotNetCore/Queries/Internal/EvaluatedIncludeCache.cs
index 509baf73ee..bbd383fa28 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/EvaluatedIncludeCache.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/EvaluatedIncludeCache.cs
@@ -10,7 +10,7 @@ internal sealed class EvaluatedIncludeCache : IEvaluatedIncludeCache
///
public void Set(IncludeExpression include)
{
- ArgumentGuard.NotNull(include, nameof(include));
+ ArgumentGuard.NotNull(include);
_include = include;
}
diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs
index 705f057bc5..c68e0f77f7 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs
@@ -18,7 +18,7 @@ public class FilterParser : QueryExpressionParser
public FilterParser(IResourceFactory resourceFactory, Action? validateSingleFieldCallback = null)
{
- ArgumentGuard.NotNull(resourceFactory, nameof(resourceFactory));
+ ArgumentGuard.NotNull(resourceFactory);
_resourceFactory = resourceFactory;
_validateSingleFieldCallback = validateSingleFieldCallback;
@@ -26,7 +26,7 @@ public FilterParser(IResourceFactory resourceFactory, Action
{
diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs
index a453921989..3c8be88e46 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs
@@ -15,7 +15,7 @@ public class IncludeParser : QueryExpressionParser
public IncludeExpression Parse(string source, ResourceType resourceTypeInScope, int? maximumDepth)
{
- ArgumentGuard.NotNull(resourceTypeInScope, nameof(resourceTypeInScope));
+ ArgumentGuard.NotNull(resourceTypeInScope);
Tokenize(source);
@@ -30,12 +30,18 @@ public IncludeExpression Parse(string source, ResourceType resourceTypeInScope,
protected IncludeExpression ParseInclude(ResourceType resourceTypeInScope, int? maximumDepth)
{
var treeRoot = IncludeTreeNode.CreateRoot(resourceTypeInScope);
-
- ParseRelationshipChain(treeRoot);
+ bool isAtStart = true;
while (TokenStack.Any())
{
- EatSingleCharacterToken(TokenKind.Comma);
+ if (!isAtStart)
+ {
+ EatSingleCharacterToken(TokenKind.Comma);
+ }
+ else
+ {
+ isAtStart = false;
+ }
ParseRelationshipChain(treeRoot);
}
@@ -106,7 +112,7 @@ private ICollection LookupRelationshipName(string relationshipN
{
relationshipsFound.AddRange(relationships);
- RelationshipAttribute[] relationshipsToInclude = relationships.Where(relationship => relationship.CanInclude).ToArray();
+ RelationshipAttribute[] relationshipsToInclude = relationships.Where(relationship => !relationship.IsIncludeBlocked()).ToArray();
ICollection affectedChildren = parent.EnsureChildren(relationshipsToInclude);
children.AddRange(affectedChildren);
}
@@ -139,7 +145,7 @@ private static void AssertRelationshipsFound(ISet relatio
private static void AssertAtLeastOneCanBeIncluded(ISet relationshipsFound, string relationshipName,
ICollection parents)
{
- if (relationshipsFound.All(relationship => !relationship.CanInclude))
+ if (relationshipsFound.All(relationship => relationship.IsIncludeBlocked()))
{
string parentPath = parents.First().Path;
ResourceType resourceType = relationshipsFound.First().LeftType;
@@ -202,7 +208,7 @@ public string Path
var pathBuilder = new StringBuilder();
IncludeTreeNode? parent = this;
- while (parent is { Relationship: not HiddenRootRelationship })
+ while (parent is { Relationship: not HiddenRootRelationshipAttribute })
{
pathBuilder.Insert(0, pathBuilder.Length > 0 ? $"{parent.Relationship.PublicName}." : parent.Relationship.PublicName);
parent = parent._parent;
@@ -220,7 +226,7 @@ private IncludeTreeNode(RelationshipAttribute relationship, IncludeTreeNode? par
public static IncludeTreeNode CreateRoot(ResourceType resourceType)
{
- var relationship = new HiddenRootRelationship(resourceType);
+ var relationship = new HiddenRootRelationshipAttribute(resourceType);
return new IncludeTreeNode(relationship, null);
}
@@ -242,9 +248,9 @@ public IncludeExpression ToExpression()
{
IncludeElementExpression element = ToElementExpression();
- if (element.Relationship is HiddenRootRelationship)
+ if (element.Relationship is HiddenRootRelationshipAttribute)
{
- return new IncludeExpression(element.Children);
+ return element.Children.Any() ? new IncludeExpression(element.Children) : IncludeExpression.Empty;
}
return new IncludeExpression(ImmutableHashSet.Create(element));
@@ -262,11 +268,11 @@ public override string ToString()
return include.ToFullString();
}
- private sealed class HiddenRootRelationship : RelationshipAttribute
+ private sealed class HiddenRootRelationshipAttribute : RelationshipAttribute
{
- public HiddenRootRelationship(ResourceType rightType)
+ public HiddenRootRelationshipAttribute(ResourceType rightType)
{
- ArgumentGuard.NotNull(rightType, nameof(rightType));
+ ArgumentGuard.NotNull(rightType);
RightType = rightType;
PublicName = "<>";
diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs
index 29c7713b11..50b542de6e 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs
@@ -19,7 +19,7 @@ public PaginationParser(Action? va
public PaginationQueryStringValueExpression Parse(string source, ResourceType resourceTypeInScope)
{
- ArgumentGuard.NotNull(resourceTypeInScope, nameof(resourceTypeInScope));
+ ArgumentGuard.NotNull(resourceTypeInScope);
_resourceTypeInScope = resourceTypeInScope;
diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs
index 3cba8e4515..ef95b3ed92 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs
@@ -22,7 +22,7 @@ public QueryStringParameterScopeParser(FieldChainRequirements chainRequirements,
public QueryStringParameterScopeExpression Parse(string source, ResourceType resourceTypeInScope)
{
- ArgumentGuard.NotNull(resourceTypeInScope, nameof(resourceTypeInScope));
+ ArgumentGuard.NotNull(resourceTypeInScope);
_resourceTypeInScope = resourceTypeInScope;
diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryTokenizer.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryTokenizer.cs
index 3f04ce92aa..cd920554c9 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryTokenizer.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryTokenizer.cs
@@ -27,7 +27,7 @@ public sealed class QueryTokenizer
public QueryTokenizer(string source)
{
- ArgumentGuard.NotNull(source, nameof(source));
+ ArgumentGuard.NotNull(source);
_source = source;
}
diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs
index 84782c2b3e..7f4a142ef0 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs
@@ -19,7 +19,7 @@ public SortParser(Action? validate
public SortExpression Parse(string source, ResourceType resourceTypeInScope)
{
- ArgumentGuard.NotNull(resourceTypeInScope, nameof(resourceTypeInScope));
+ ArgumentGuard.NotNull(resourceTypeInScope);
_resourceTypeInScope = resourceTypeInScope;
diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs
index b4e54f0c46..0cabbcf76e 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs
@@ -19,7 +19,7 @@ public SparseFieldSetParser(Action
public SparseFieldSetExpression? Parse(string source, ResourceType resourceType)
{
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
+ ArgumentGuard.NotNull(resourceType);
_resourceType = resourceType;
diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs
index b23dfdfea1..eceb05d211 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs
@@ -12,7 +12,7 @@ public class SparseFieldTypeParser : QueryExpressionParser
public SparseFieldTypeParser(IResourceGraph resourceGraph)
{
- ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
+ ArgumentGuard.NotNull(resourceGraph);
_resourceGraph = resourceGraph;
}
diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs
index 40af882044..29e0935954 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs
@@ -25,13 +25,13 @@ public QueryLayerComposer(IEnumerable constraintProvid
IJsonApiOptions options, IPaginationContext paginationContext, ITargetedFields targetedFields, IEvaluatedIncludeCache evaluatedIncludeCache,
ISparseFieldSetCache sparseFieldSetCache)
{
- ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders));
- ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor));
- ArgumentGuard.NotNull(options, nameof(options));
- ArgumentGuard.NotNull(paginationContext, nameof(paginationContext));
- ArgumentGuard.NotNull(targetedFields, nameof(targetedFields));
- ArgumentGuard.NotNull(evaluatedIncludeCache, nameof(evaluatedIncludeCache));
- ArgumentGuard.NotNull(sparseFieldSetCache, nameof(sparseFieldSetCache));
+ ArgumentGuard.NotNull(constraintProviders);
+ ArgumentGuard.NotNull(resourceDefinitionAccessor);
+ ArgumentGuard.NotNull(options);
+ ArgumentGuard.NotNull(paginationContext);
+ ArgumentGuard.NotNull(targetedFields);
+ ArgumentGuard.NotNull(evaluatedIncludeCache);
+ ArgumentGuard.NotNull(sparseFieldSetCache);
_constraintProviders = constraintProviders;
_resourceDefinitionAccessor = resourceDefinitionAccessor;
@@ -65,7 +65,7 @@ public QueryLayerComposer(IEnumerable constraintProvid
///
public FilterExpression? GetSecondaryFilterFromConstraints(TId primaryId, HasManyAttribute hasManyRelationship)
{
- ArgumentGuard.NotNull(hasManyRelationship, nameof(hasManyRelationship));
+ ArgumentGuard.NotNull(hasManyRelationship);
if (hasManyRelationship.InverseNavigationProperty == null)
{
@@ -131,7 +131,7 @@ private static FilterExpression GetInverseHasManyRelationshipFilter(TId pri
///
public QueryLayer ComposeFromConstraints(ResourceType requestResourceType)
{
- ArgumentGuard.NotNull(requestResourceType, nameof(requestResourceType));
+ ArgumentGuard.NotNull(requestResourceType);
ExpressionInScope[] constraints = _constraintProviders.SelectMany(provider => provider.GetConstraints()).ToArray();
@@ -268,7 +268,7 @@ private static IImmutableSet ApplyIncludeElementUpdate
///
public QueryLayer ComposeForGetById(TId id, ResourceType primaryResourceType, TopFieldSelection fieldSelection)
{
- ArgumentGuard.NotNull(primaryResourceType, nameof(primaryResourceType));
+ ArgumentGuard.NotNull(primaryResourceType);
AttrAttribute idAttribute = GetIdAttribute(primaryResourceType);
@@ -296,7 +296,7 @@ public QueryLayer ComposeForGetById(TId id, ResourceType primaryResourceTyp
///
public QueryLayer ComposeSecondaryLayerForRelationship(ResourceType secondaryResourceType)
{
- ArgumentGuard.NotNull(secondaryResourceType, nameof(secondaryResourceType));
+ ArgumentGuard.NotNull(secondaryResourceType);
QueryLayer secondaryLayer = ComposeFromConstraints(secondaryResourceType);
secondaryLayer.Selection = GetSelectionForRelationship(secondaryResourceType);
@@ -320,9 +320,9 @@ private FieldSelection GetSelectionForRelationship(ResourceType secondaryResourc
public QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, ResourceType primaryResourceType, TId primaryId,
RelationshipAttribute relationship)
{
- ArgumentGuard.NotNull(secondaryLayer, nameof(secondaryLayer));
- ArgumentGuard.NotNull(primaryResourceType, nameof(primaryResourceType));
- ArgumentGuard.NotNull(relationship, nameof(relationship));
+ ArgumentGuard.NotNull(secondaryLayer);
+ ArgumentGuard.NotNull(primaryResourceType);
+ ArgumentGuard.NotNull(relationship);
IncludeExpression? innerInclude = secondaryLayer.Include;
secondaryLayer.Include = null;
@@ -377,7 +377,7 @@ private IncludeExpression RewriteIncludeForSecondaryEndpoint(IncludeExpression?
///
public QueryLayer ComposeForUpdate(TId id, ResourceType primaryResourceType)
{
- ArgumentGuard.NotNull(primaryResourceType, nameof(primaryResourceType));
+ ArgumentGuard.NotNull(primaryResourceType);
IImmutableSet includeElements = _targetedFields.Relationships
.Select(relationship => new IncludeElementExpression(relationship)).ToImmutableHashSet();
@@ -397,7 +397,7 @@ public QueryLayer ComposeForUpdate(TId id, ResourceType primaryResourceType
///
public IEnumerable<(QueryLayer, RelationshipAttribute)> ComposeForGetTargetedSecondaryResourceIds(IIdentifiable primaryResource)
{
- ArgumentGuard.NotNull(primaryResource, nameof(primaryResource));
+ ArgumentGuard.NotNull(primaryResource);
foreach (RelationshipAttribute relationship in _targetedFields.Relationships)
{
@@ -415,8 +415,8 @@ public QueryLayer ComposeForUpdate(TId id, ResourceType primaryResourceType
///
public QueryLayer ComposeForGetRelationshipRightIds(RelationshipAttribute relationship, ICollection rightResourceIds)
{
- ArgumentGuard.NotNull(relationship, nameof(relationship));
- ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds));
+ ArgumentGuard.NotNull(relationship);
+ ArgumentGuard.NotNull(rightResourceIds);
AttrAttribute rightIdAttribute = GetIdAttribute(relationship.RightType);
@@ -440,8 +440,8 @@ public QueryLayer ComposeForGetRelationshipRightIds(RelationshipAttribute relati
///
public QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, TId leftId, ICollection rightResourceIds)
{
- ArgumentGuard.NotNull(hasManyRelationship, nameof(hasManyRelationship));
- ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds));
+ ArgumentGuard.NotNull(hasManyRelationship);
+ ArgumentGuard.NotNull(rightResourceIds);
AttrAttribute leftIdAttribute = GetIdAttribute(hasManyRelationship.LeftType);
AttrAttribute rightIdAttribute = GetIdAttribute(hasManyRelationship.RightType);
@@ -476,15 +476,15 @@ public QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, T
protected virtual IImmutableSet GetIncludeElements(IImmutableSet includeElements,
ResourceType resourceType)
{
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
+ ArgumentGuard.NotNull(resourceType);
return _resourceDefinitionAccessor.OnApplyIncludes(resourceType, includeElements);
}
protected virtual FilterExpression? GetFilter(IReadOnlyCollection expressionsInScope, ResourceType resourceType)
{
- ArgumentGuard.NotNull(expressionsInScope, nameof(expressionsInScope));
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
+ ArgumentGuard.NotNull(expressionsInScope);
+ ArgumentGuard.NotNull(resourceType);
FilterExpression[] filters = expressionsInScope.OfType().ToArray();
FilterExpression? filter = LogicalExpression.Compose(LogicalOperator.And, filters);
@@ -494,8 +494,8 @@ protected virtual IImmutableSet GetIncludeElements(IIm
protected virtual SortExpression GetSort(IReadOnlyCollection expressionsInScope, ResourceType resourceType)
{
- ArgumentGuard.NotNull(expressionsInScope, nameof(expressionsInScope));
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
+ ArgumentGuard.NotNull(expressionsInScope);
+ ArgumentGuard.NotNull(resourceType);
SortExpression? sort = expressionsInScope.OfType().FirstOrDefault();
@@ -513,8 +513,8 @@ protected virtual SortExpression GetSort(IReadOnlyCollection ex
protected virtual PaginationExpression GetPagination(IReadOnlyCollection expressionsInScope, ResourceType resourceType)
{
- ArgumentGuard.NotNull(expressionsInScope, nameof(expressionsInScope));
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
+ ArgumentGuard.NotNull(expressionsInScope);
+ ArgumentGuard.NotNull(resourceType);
PaginationExpression? pagination = expressionsInScope.OfType().FirstOrDefault();
@@ -529,7 +529,7 @@ protected virtual PaginationExpression GetPagination(IReadOnlyCollection
public IncludeClauseBuilder(Expression source, LambdaScope lambdaScope, ResourceType resourceType)
: base(lambdaScope)
{
- ArgumentGuard.NotNull(source, nameof(source));
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
+ ArgumentGuard.NotNull(source);
+ ArgumentGuard.NotNull(resourceType);
_source = source;
_resourceType = resourceType;
@@ -30,7 +30,7 @@ public IncludeClauseBuilder(Expression source, LambdaScope lambdaScope, Resource
public Expression ApplyInclude(IncludeExpression include)
{
- ArgumentGuard.NotNull(include, nameof(include));
+ ArgumentGuard.NotNull(include);
return Visit(include, null);
}
diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameFactory.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameFactory.cs
index 864b71c843..32691e05ab 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameFactory.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameFactory.cs
@@ -13,7 +13,7 @@ public sealed class LambdaParameterNameFactory
public LambdaParameterNameScope Create(string typeName)
{
- ArgumentGuard.NotNullNorEmpty(typeName, nameof(typeName));
+ ArgumentGuard.NotNullNorEmpty(typeName);
string parameterName = typeName.Camelize();
parameterName = EnsureNameIsUnique(parameterName);
diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameScope.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameScope.cs
index 2bad41d310..031dae0a0f 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameScope.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameScope.cs
@@ -11,8 +11,8 @@ public sealed class LambdaParameterNameScope : IDisposable
public LambdaParameterNameScope(string name, LambdaParameterNameFactory owner)
{
- ArgumentGuard.NotNullNorEmpty(name, nameof(name));
- ArgumentGuard.NotNull(owner, nameof(owner));
+ ArgumentGuard.NotNullNorEmpty(name);
+ ArgumentGuard.NotNull(owner);
Name = name;
_owner = owner;
diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScope.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScope.cs
index e5502031a3..52caddbe62 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScope.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScope.cs
@@ -23,8 +23,8 @@ private LambdaScope(LambdaParameterNameScope parameterNameScope, ParameterExpres
public static LambdaScope Create(LambdaParameterNameFactory nameFactory, Type elementType, Expression? accessorExpression)
{
- ArgumentGuard.NotNull(nameFactory, nameof(nameFactory));
- ArgumentGuard.NotNull(elementType, nameof(elementType));
+ ArgumentGuard.NotNull(nameFactory);
+ ArgumentGuard.NotNull(elementType);
LambdaParameterNameScope parameterNameScope = nameFactory.Create(elementType.Name);
ParameterExpression parameter = Expression.Parameter(elementType, parameterNameScope.Name);
@@ -35,7 +35,7 @@ public static LambdaScope Create(LambdaParameterNameFactory nameFactory, Type el
public LambdaScope WithAccessor(Expression accessorExpression)
{
- ArgumentGuard.NotNull(accessorExpression, nameof(accessorExpression));
+ ArgumentGuard.NotNull(accessorExpression);
return new LambdaScope(_parameterNameScope, Parameter, accessorExpression);
}
diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScopeFactory.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScopeFactory.cs
index 9c13a63d28..6e4955cf40 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScopeFactory.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScopeFactory.cs
@@ -10,14 +10,14 @@ public sealed class LambdaScopeFactory
public LambdaScopeFactory(LambdaParameterNameFactory nameFactory)
{
- ArgumentGuard.NotNull(nameFactory, nameof(nameFactory));
+ ArgumentGuard.NotNull(nameFactory);
_nameFactory = nameFactory;
}
public LambdaScope CreateScope(Type elementType, Expression? accessorExpression = null)
{
- ArgumentGuard.NotNull(elementType, nameof(elementType));
+ ArgumentGuard.NotNull(elementType);
return LambdaScope.Create(_nameFactory, elementType, accessorExpression);
}
diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/OrderClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/OrderClauseBuilder.cs
index 7ae8dd2392..775893adcc 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/OrderClauseBuilder.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/OrderClauseBuilder.cs
@@ -17,8 +17,8 @@ public class OrderClauseBuilder : QueryClauseBuilder
public OrderClauseBuilder(Expression source, LambdaScope lambdaScope, Type extensionType)
: base(lambdaScope)
{
- ArgumentGuard.NotNull(source, nameof(source));
- ArgumentGuard.NotNull(extensionType, nameof(extensionType));
+ ArgumentGuard.NotNull(source);
+ ArgumentGuard.NotNull(extensionType);
_source = source;
_extensionType = extensionType;
@@ -26,7 +26,7 @@ public OrderClauseBuilder(Expression source, LambdaScope lambdaScope, Type exten
public Expression ApplyOrderBy(SortExpression expression)
{
- ArgumentGuard.NotNull(expression, nameof(expression));
+ ArgumentGuard.NotNull(expression);
return Visit(expression, null);
}
diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs
index d04ff57e9d..fdbb3bc0c3 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs
@@ -14,7 +14,7 @@ public abstract class QueryClauseBuilder : QueryExpressionVisitor(Expression accessorExpression, Func action)
{
- ArgumentGuard.NotNull(accessorExpression, nameof(accessorExpression));
- ArgumentGuard.NotNull(action, nameof(action));
+ ArgumentGuard.NotNull(accessorExpression);
+ ArgumentGuard.NotNull(action);
LambdaScope backupScope = LambdaScope;
diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs
index d571ac1dce..a497846285 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs
@@ -24,12 +24,12 @@ public class QueryableBuilder
public QueryableBuilder(Expression source, Type elementType, Type extensionType, LambdaParameterNameFactory nameFactory, IResourceFactory resourceFactory,
IModel entityModel, LambdaScopeFactory? lambdaScopeFactory = null)
{
- ArgumentGuard.NotNull(source, nameof(source));
- ArgumentGuard.NotNull(elementType, nameof(elementType));
- ArgumentGuard.NotNull(extensionType, nameof(extensionType));
- ArgumentGuard.NotNull(nameFactory, nameof(nameFactory));
- ArgumentGuard.NotNull(resourceFactory, nameof(resourceFactory));
- ArgumentGuard.NotNull(entityModel, nameof(entityModel));
+ ArgumentGuard.NotNull(source);
+ ArgumentGuard.NotNull(elementType);
+ ArgumentGuard.NotNull(extensionType);
+ ArgumentGuard.NotNull(nameFactory);
+ ArgumentGuard.NotNull(resourceFactory);
+ ArgumentGuard.NotNull(entityModel);
_source = source;
_elementType = elementType;
@@ -42,7 +42,7 @@ public QueryableBuilder(Expression source, Type elementType, Type extensionType,
public virtual Expression ApplyQuery(QueryLayer layer)
{
- ArgumentGuard.NotNull(layer, nameof(layer));
+ ArgumentGuard.NotNull(layer);
Expression expression = _source;
diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs
index 690c49de24..1f1c10301a 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs
@@ -31,11 +31,11 @@ public SelectClauseBuilder(Expression source, LambdaScope lambdaScope, IModel en
IResourceFactory resourceFactory)
: base(lambdaScope)
{
- ArgumentGuard.NotNull(source, nameof(source));
- ArgumentGuard.NotNull(entityModel, nameof(entityModel));
- ArgumentGuard.NotNull(extensionType, nameof(extensionType));
- ArgumentGuard.NotNull(nameFactory, nameof(nameFactory));
- ArgumentGuard.NotNull(resourceFactory, nameof(resourceFactory));
+ ArgumentGuard.NotNull(source);
+ ArgumentGuard.NotNull(entityModel);
+ ArgumentGuard.NotNull(extensionType);
+ ArgumentGuard.NotNull(nameFactory);
+ ArgumentGuard.NotNull(resourceFactory);
_source = source;
_entityModel = entityModel;
@@ -46,7 +46,7 @@ public SelectClauseBuilder(Expression source, LambdaScope lambdaScope, IModel en
public Expression ApplySelect(FieldSelection selection, ResourceType resourceType)
{
- ArgumentGuard.NotNull(selection, nameof(selection));
+ ArgumentGuard.NotNull(selection);
Expression bodyInitializer = CreateLambdaBodyInitializer(selection, resourceType, LambdaScope, false);
@@ -272,7 +272,7 @@ private sealed class PropertySelector
public PropertySelector(PropertyInfo property, QueryLayer? nextLayer = null)
{
- ArgumentGuard.NotNull(property, nameof(property));
+ ArgumentGuard.NotNull(property);
Property = property;
NextLayer = nextLayer;
diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SkipTakeClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SkipTakeClauseBuilder.cs
index 4bb9bfd6f5..90109dbfec 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SkipTakeClauseBuilder.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SkipTakeClauseBuilder.cs
@@ -17,8 +17,8 @@ public class SkipTakeClauseBuilder : QueryClauseBuilder