diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index d914fc3..197960a 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,28 +3,25 @@
"isRoot": true,
"tools": {
"jetbrains.resharper.globaltools": {
- "version": "2022.2.3",
+ "version": "2024.3.6",
"commands": [
"jb"
- ]
+ ],
+ "rollForward": false
},
"regitlint": {
- "version": "6.1.1",
+ "version": "6.3.13",
"commands": [
"regitlint"
- ]
- },
- "codecov.tool": {
- "version": "1.13.0",
- "commands": [
- "codecov"
- ]
+ ],
+ "rollForward": false
},
"dotnet-reportgenerator-globaltool": {
- "version": "5.1.3",
+ "version": "5.4.5",
"commands": [
"reportgenerator"
- ]
+ ],
+ "rollForward": false
}
}
-}
+}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
index ca191cf..2e5c106 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -4,60 +4,125 @@ root = true
[*]
indent_style = space
indent_size = 4
+tab-width = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
-[*.{config,csproj,css,js,json,props,ruleset,xslt}]
+[*.{config,csproj,css,js,json,props,targets,xml,ruleset,xsd,xslt,html,yml,yaml}]
indent_size = 2
+tab-width = 2
+max_line_length = 160
+
+[*.{cs,cshtml,ascx,aspx}]
-[*.{cs}]
#### C#/.NET Coding Conventions ####
+# Default severity for IDE* analyzers with category 'Style'
+# Note: specific rules below use severity silent, because Resharper code cleanup auto-fixes them.
+dotnet_analyzer_diagnostic.category-Style.severity = warning
+
# 'using' directive preferences
dotnet_sort_system_directives_first = true
-csharp_using_directive_placement = outside_namespace:suggestion
+csharp_using_directive_placement = outside_namespace:silent
+# IDE0005: Remove unnecessary import
+dotnet_diagnostic.IDE0005.severity = silent
# Namespace declarations
-csharp_style_namespace_declarations = file_scoped:suggestion
+csharp_style_namespace_declarations = file_scoped:silent
+# IDE0160: Use block-scoped namespace
+dotnet_diagnostic.IDE0160.severity = silent
+# IDE0161: Use file-scoped namespace
+dotnet_diagnostic.IDE0161.severity = silent
# this. preferences
-dotnet_style_qualification_for_field = false:suggestion
-dotnet_style_qualification_for_property = false:suggestion
-dotnet_style_qualification_for_method = false:suggestion
-dotnet_style_qualification_for_event = false:suggestion
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_property = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_event = false:silent
+# IDE0003: Remove this or Me qualification
+dotnet_diagnostic.IDE0003.severity = silent
+# IDE0009: Add this or Me qualification
+dotnet_diagnostic.IDE0009.severity = silent
# Language keywords vs BCL types preferences
-dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
-dotnet_style_predefined_type_for_member_access = true:suggestion
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+# IDE0049: Use language keywords instead of framework type names for type references
+dotnet_diagnostic.IDE0049.severity = silent
# 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
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+# IDE0040: Add accessibility modifiers
+dotnet_diagnostic.IDE0040.severity = silent
+csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:silent
+# IDE0036: Order modifiers
+dotnet_diagnostic.IDE0036.severity = silent
# Expression-level preferences
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
+dotnet_style_prefer_auto_properties = true:silent
+# IDE0032: Use auto property
+dotnet_diagnostic.IDE0032.severity = silent
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+# IDE0045: Use conditional expression for assignment
+dotnet_diagnostic.IDE0045.severity = silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+# IDE0046: Use conditional expression for return
+dotnet_diagnostic.IDE0046.severity = silent
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+# IDE0058: Remove unused expression value
+dotnet_diagnostic.IDE0058.severity = silent
+
+# Collection expression preferences (note: partially turned off in Directory.Build.props)
+dotnet_style_prefer_collection_expression = when_types_exactly_match
# Parameter preferences
-dotnet_code_quality_unused_parameters = non_public:suggestion
+dotnet_code_quality_unused_parameters = non_public
+
+# Local functions vs lambdas
+csharp_style_prefer_local_over_anonymous_function = false:silent
+# IDE0039: Use local function instead of lambda
+dotnet_diagnostic.IDE0039.severity = silent
# Expression-bodied members
-csharp_style_expression_bodied_accessors = true:suggestion
-csharp_style_expression_bodied_constructors = false:suggestion
-csharp_style_expression_bodied_indexers = true:suggestion
-csharp_style_expression_bodied_lambdas = true:suggestion
-csharp_style_expression_bodied_local_functions = false:suggestion
-csharp_style_expression_bodied_methods = false:suggestion
-csharp_style_expression_bodied_operators = false:suggestion
-csharp_style_expression_bodied_properties = true:suggestion
+csharp_style_expression_bodied_accessors = true:silent
+# IDE0027: Use expression body for accessors
+dotnet_diagnostic.IDE0027.severity = silent
+csharp_style_expression_bodied_constructors = false:silent
+# IDE0021: Use expression body for constructors
+dotnet_diagnostic.IDE0021.severity = silent
+csharp_style_expression_bodied_indexers = true:silent
+# IDE0026: Use expression body for indexers
+dotnet_diagnostic.IDE0026.severity = silent
+csharp_style_expression_bodied_lambdas = true:silent
+# IDE0053: Use expression body for lambdas
+dotnet_diagnostic.IDE0053.severity = silent
+csharp_style_expression_bodied_local_functions = false:silent
+# IDE0061: Use expression body for local functions
+dotnet_diagnostic.IDE0061.severity = silent
+csharp_style_expression_bodied_methods = false:silent
+# IDE0022: Use expression body for methods
+dotnet_diagnostic.IDE0022.severity = silent
+csharp_style_expression_bodied_operators = false:silent
+# IDE0023: Use expression body for conversion operators
+dotnet_diagnostic.IDE0023.severity = silent
+# IDE0024: Use expression body for operators
+dotnet_diagnostic.IDE0024.severity = silent
+csharp_style_expression_bodied_properties = true:silent
+# IDE0025: Use expression body for properties
+dotnet_diagnostic.IDE0025.severity = silent
+
+# Member preferences (these analyzers are unreliable)
+# IDE0051: Remove unused private member
+dotnet_diagnostic.IDE0051.severity = silent
+# IDE0052: Remove unread private member
+dotnet_diagnostic.IDE0052.severity = silent
# Code-block preferences
-csharp_prefer_braces = true:suggestion
+csharp_prefer_braces = true:silent
+# IDE0011: Add braces
+dotnet_diagnostic.IDE0011.severity = silent
# Indentation preferences
csharp_indent_case_contents_when_block = false
@@ -66,16 +131,44 @@ csharp_indent_case_contents_when_block = false
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
+csharp_style_var_for_built_in_types = false:silent
+csharp_style_var_when_type_is_apparent = true:silent
+csharp_style_var_elsewhere = false:silent
+# IDE0007: Use var instead of explicit type
+dotnet_diagnostic.IDE0007.severity = silent
+# IDE0008: Use explicit type instead of var
+dotnet_diagnostic.IDE0008.severity = silent
# 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 Style ####
+dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:silent
+# IDE0047: Remove unnecessary parentheses
+dotnet_diagnostic.IDE0047.severity = silent
+# IDE0048: Add parentheses for clarity
+dotnet_diagnostic.IDE0048.severity = silent
+
+# Switch preferences
+# IDE0010: Add missing cases to switch statement
+dotnet_diagnostic.IDE0010.severity = silent
+# IDE0072: Add missing cases to switch expression
+dotnet_diagnostic.IDE0072.severity = silent
+
+# Null check preferences
+# IDE0029: Null check can be simplified
+dotnet_diagnostic.IDE0029.severity = silent
+# IDE0030: Null check can be simplified
+dotnet_diagnostic.IDE0030.severity = silent
+# IDE0270: Null check can be simplified
+dotnet_diagnostic.IDE0270.severity = silent
+
+# JSON002: Probable JSON string detected
+dotnet_diagnostic.JSON002.severity = silent
+
+# CA1062: Validate arguments of public methods
+dotnet_code_quality.CA1062.excluded_symbol_names = Accept|DefaultVisit|Visit*|Apply*
+
+#### .NET Naming Style ####
dotnet_diagnostic.IDE1006.severity = warning
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..b065d30
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: json-api-dotnet
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index 103a247..0000000
--- a/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,21 +0,0 @@
-#### DESCRIPTION
-
-_Describe your bug or feature request here._
-
-#### 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._
-
-1.
-2.
-3.
-
-#### EXPECTED BEHAVIOR
-
-#### ACTUAL BEHAVIOR
-
-#### VERSIONS USED
-- JsonApiDotNetCore.MongoDb version:
-- JsonApiDotNetCore version:
-- ASP.NET Core version:
-- MongoDB version:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..d204896
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,32 @@
+---
+name: Bug report
+about: Create a report to help us improve.
+title: ''
+labels: 'bug'
+assignees: ''
+
+---
+
+
+
+#### DESCRIPTION
+
+
+#### STEPS TO REPRODUCE
+
+
+1.
+2.
+3.
+
+#### EXPECTED BEHAVIOR
+
+
+#### ACTUAL BEHAVIOR
+
+
+#### VERSIONS USED
+- JsonApiDotNetCore.MongoDb version:
+- JsonApiDotNetCore version:
+- ASP.NET Core version:
+- MongoDB version:
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..8bf0a91
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,14 @@
+blank_issues_enabled: true
+contact_links:
+- name: Documentation
+ url: https://www.jsonapi.net/usage/resources/index.html
+ about: Read our comprehensive documentation.
+- name: Sponsor JsonApiDotNetCore
+ url: https://github.com/sponsors/json-api-dotnet
+ about: Help the continued development.
+- name: Ask on Gitter
+ url: https://gitter.im/json-api-dotnet-core/Lobby
+ about: Get in touch with the whole community.
+- name: Ask on Stack Overflow
+ url: https://stackoverflow.com/questions/tagged/json-api
+ about: The best place for asking general-purpose questions.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..019f7a9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,22 @@
+---
+name: Feature request
+about: Suggest an idea for this project.
+title: ''
+labels: 'enhancement'
+assignees: ''
+
+---
+
+
+
+**Is your feature request related to a problem? Please describe.**
+
+
+**Describe the solution you'd like**
+
+
+**Describe alternatives you've considered**
+
+
+**Additional context**
+
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
new file mode 100644
index 0000000..fcff5ed
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -0,0 +1,39 @@
+---
+name: Question
+about: Ask a question.
+title: ''
+labels: 'question'
+assignees: ''
+
+---
+
+
+
+#### SUMMARY
+
+
+#### DETAILS
+
+
+
+#### STEPS TO REPRODUCE
+
+
+1.
+2.
+3.
+
+#### VERSIONS USED
+- JsonApiDotNetCore.MongoDb version:
+- JsonApiDotNetCore version:
+- ASP.NET Core version:
+- MongoDB version:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..8bd1514
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,22 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ pull-request-branch-name:
+ separator: "-"
+ - package-ecosystem: nuget
+ directory: "/"
+ schedule:
+ interval: daily
+ pull-request-branch-name:
+ separator: "-"
+ open-pull-requests-limit: 25
+ ignore:
+ # Block updates to all exposed dependencies of the NuGet packages we produce, as updating them would be a breaking change.
+ - dependency-name: "JsonApiDotNetCore*"
+ - dependency-name: "MongoDB.Driver*"
+ # Block major updates of packages that require a matching .NET version.
+ - dependency-name: "Microsoft.AspNetCore*"
+ update-types: ["version-update:semver-major"]
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..e6a9569
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,251 @@
+# General links
+# https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables
+# https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
+# https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads
+# https://docs.github.com/en/actions/learn-github-actions/expressions
+# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions
+# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
+
+name: Build
+
+on:
+ push:
+ branches: [ 'master', 'release/**' ]
+ pull_request:
+ branches: [ 'master', 'release/**' ]
+ release:
+ types: [published]
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+
+jobs:
+ build-and-test:
+ timeout-minutes: 60
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+ permissions:
+ contents: read
+ steps:
+ - name: Tune GitHub-hosted runner network
+ uses: smorimoto/tune-github-hosted-runner-network@v1
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 8.0.*
+ 9.0.*
+ - name: Show installed versions
+ shell: pwsh
+ run: |
+ Write-Host "$(pwsh --version) is installed at $PSHOME"
+ Write-Host "Active .NET SDK: $(dotnet --version)"
+ - name: Git checkout
+ uses: actions/checkout@v4
+ - name: Restore tools
+ run: |
+ dotnet tool restore
+ - name: Restore packages
+ run: |
+ dotnet restore
+ - name: Calculate version suffix
+ shell: pwsh
+ run: |
+ if ($env:GITHUB_REF_TYPE -eq 'tag') {
+ # Get the version prefix/suffix from the git tag. For example: 'v1.0.0-preview1-final' => '1.0.0' and 'preview1-final'
+ $segments = $env:GITHUB_REF_NAME -split "-"
+ $versionPrefix = $segments[0].TrimStart('v')
+ $versionSuffix = $segments.Length -eq 1 ? '' : $segments[1..$($segments.Length - 1)] -join '-'
+
+ [xml]$xml = Get-Content Directory.Build.props
+ $configuredVersionPrefix = $xml.Project.PropertyGroup.VersionPrefix | Select-Object -First 1
+
+ if ($configuredVersionPrefix -ne $versionPrefix) {
+ Write-Error "Version prefix from git release tag '$versionPrefix' does not match version prefix '$configuredVersionPrefix' stored in Directory.Build.props."
+ # To recover from this:
+ # - Delete the GitHub release
+ # - Run: git push --delete origin the-invalid-tag-name
+ # - Adjust VersionPrefix in Directory.Build.props, commit and push
+ # - Recreate the GitHub release
+ }
+ }
+ else {
+ # Get the version suffix from the auto-incrementing build number. For example: '123' => 'master-00123'
+ $revision = "{0:D5}" -f [convert]::ToInt32($env:GITHUB_RUN_NUMBER, 10)
+ $branchName = ![string]::IsNullOrEmpty($env:GITHUB_HEAD_REF) ? $env:GITHUB_HEAD_REF : $env:GITHUB_REF_NAME
+ $safeName = $branchName.Replace('/', '-').Replace('_', '-')
+ $versionSuffix = "$safeName-$revision"
+ }
+ Write-Output "Using version suffix: $versionSuffix"
+ Write-Output "PACKAGE_VERSION_SUFFIX=$versionSuffix" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+ - name: Build
+ shell: pwsh
+ run: |
+ dotnet build --no-restore --configuration Release /p:VersionSuffix=$env:PACKAGE_VERSION_SUFFIX
+ - name: Test
+ run: |
+ dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" --logger "GitHubActions;summary.includeSkippedTests=true"
+ - name: Upload coverage to codecov.io
+ if: matrix.os == 'ubuntu-latest'
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+ uses: codecov/codecov-action@v5
+ with:
+ fail_ci_if_error: true
+ verbose: true
+ - name: Generate packages
+ shell: pwsh
+ run: |
+ dotnet pack --no-build --configuration Release --output $env:GITHUB_WORKSPACE/artifacts/packages /p:VersionSuffix=$env:PACKAGE_VERSION_SUFFIX
+ - name: Upload packages to artifacts
+ if: matrix.os == 'ubuntu-latest'
+ uses: actions/upload-artifact@v4
+ with:
+ name: packages
+ path: artifacts/packages
+
+ inspect-code:
+ timeout-minutes: 60
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+ permissions:
+ contents: read
+ steps:
+ - name: Tune GitHub-hosted runner network
+ uses: smorimoto/tune-github-hosted-runner-network@v1
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 8.0.*
+ 9.0.*
+ - name: Git checkout
+ uses: actions/checkout@v4
+ - name: Restore tools
+ run: |
+ dotnet tool restore
+ - name: InspectCode
+ shell: pwsh
+ run: |
+ $inspectCodeOutputPath = Join-Path $env:RUNNER_TEMP 'jetbrains-inspectcode-results.xml'
+ Write-Output "INSPECT_CODE_OUTPUT_PATH=$inspectCodeOutputPath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+ dotnet jb inspectcode JsonApiDotNetCore.MongoDb.sln --build --dotnetcoresdk=$(dotnet --version) --output="$inspectCodeOutputPath" --format="xml" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --properties:ContinuousIntegrationBuild=false --properties:RunAnalyzers=false --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal
+ - name: Verify outcome
+ shell: pwsh
+ run: |
+ [xml]$xml = Get-Content $env:INSPECT_CODE_OUTPUT_PATH
+ if ($xml.report.Issues -and $xml.report.Issues.Project) {
+ foreach ($project in $xml.report.Issues.Project) {
+ if ($project.Issue.Count -gt 0) {
+ $project.ForEach({
+ Write-Output "`nProject $($project.Name)"
+ $failed = $true
+
+ $_.Issue.ForEach({
+ $issueType = $xml.report.IssueTypes.SelectSingleNode("IssueType[@Id='$($_.TypeId)']")
+ $severity = $_.Severity ?? $issueType.Severity
+
+ Write-Output "[$severity] $($_.File):$($_.Line) $($_.TypeId): $($_.Message)"
+ })
+ })
+ }
+ }
+
+ if ($failed) {
+ Write-Error "One or more projects failed code inspection."
+ }
+ }
+
+ cleanup-code:
+ timeout-minutes: 60
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+ permissions:
+ contents: read
+ steps:
+ - name: Tune GitHub-hosted runner network
+ uses: smorimoto/tune-github-hosted-runner-network@v1
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 8.0.*
+ 9.0.*
+ - name: Git checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 2
+ - name: Restore tools
+ run: |
+ dotnet tool restore
+ - name: Restore packages
+ run: |
+ dotnet restore
+ - name: CleanupCode (on PR diff)
+ if: github.event_name == 'pull_request'
+ shell: pwsh
+ run: |
+ # Not using the environment variables for SHAs, because they may be outdated. This may happen on force-push after the build is queued, but before it starts.
+ # The below works because HEAD is detached (at the merge commit), so HEAD~1 is at the base branch. When a PR contains no commits, this job will not run.
+ $headCommitHash = git rev-parse HEAD
+ $baseCommitHash = git rev-parse HEAD~1
+
+ Write-Output "Running code cleanup on commit range $baseCommitHash..$headCommitHash in pull request."
+ dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --skip-tool-check --max-runs=5 --jb --dotnetcoresdk=$(dotnet --version) --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --properties:RunAnalyzers=false --jb --verbosity=WARN -f commits -a $headCommitHash -b $baseCommitHash --fail-on-diff --print-diff
+ - name: CleanupCode (on branch)
+ if: github.event_name == 'push' || github.event_name == 'release'
+ shell: pwsh
+ run: |
+ Write-Output "Running code cleanup on all files."
+ dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --skip-tool-check --jb --dotnetcoresdk=$(dotnet --version) --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --properties:RunAnalyzers=false --jb --verbosity=WARN --fail-on-diff --print-diff
+
+ publish:
+ timeout-minutes: 60
+ runs-on: ubuntu-latest
+ needs: [ build-and-test, inspect-code, cleanup-code ]
+ if: ${{ !github.event.pull_request.head.repo.fork }}
+ permissions:
+ packages: write
+ contents: write
+ steps:
+ - name: Tune GitHub-hosted runner network
+ uses: smorimoto/tune-github-hosted-runner-network@v1
+ - name: Download artifacts
+ uses: actions/download-artifact@v4
+ - name: Publish to GitHub Packages
+ if: github.event_name == 'push' || github.event_name == 'release'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ shell: pwsh
+ run: |
+ dotnet nuget add source --username 'json-api-dotnet' --password "$env:GITHUB_TOKEN" --store-password-in-clear-text --name 'github' 'https://nuget.pkg.github.com/json-api-dotnet/index.json'
+ dotnet nuget push "$env:GITHUB_WORKSPACE/packages/*.nupkg" --api-key "$env:GITHUB_TOKEN" --source 'github'
+ - name: Publish to feedz.io
+ if: github.event_name == 'push' || github.event_name == 'release'
+ env:
+ FEEDZ_IO_API_KEY: ${{ secrets.FEEDZ_IO_API_KEY }}
+ shell: pwsh
+ run: |
+ dotnet nuget add source --name 'feedz-io' 'https://f.feedz.io/json-api-dotnet/jsonapidotnetcore/nuget/index.json'
+ dotnet nuget push "$env:GITHUB_WORKSPACE/packages/*.nupkg" --api-key "$env:FEEDZ_IO_API_KEY" --source 'feedz-io'
+ - name: Publish to NuGet
+ if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/v')
+ env:
+ NUGET_ORG_API_KEY: ${{ secrets.NUGET_ORG_API_KEY }}
+ shell: pwsh
+ run: |
+ dotnet nuget push "$env:GITHUB_WORKSPACE/packages/*.nupkg" --api-key "$env:NUGET_ORG_API_KEY" --source 'nuget.org'
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..d3d4db6
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,43 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ 'master', 'release/**' ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ 'master', 'release/**' ]
+ schedule:
+ - cron: '0 0 * * 5'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: 'ubuntu-latest'
+ timeout-minutes: 60
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'csharp' ]
+ steps:
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 8.0.*
+ 9.0.*
+ - name: Git checkout
+ uses: actions/checkout@v4
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: ${{ matrix.language }}
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v3
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+ with:
+ category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/deps-review.yml b/.github/workflows/deps-review.yml
new file mode 100644
index 0000000..b9d6d20
--- /dev/null
+++ b/.github/workflows/deps-review.yml
@@ -0,0 +1,14 @@
+name: 'Dependency Review'
+on: [pull_request]
+
+permissions:
+ contents: read
+
+jobs:
+ dependency-review:
+ runs-on: ubuntu-latest
+ steps:
+ - name: 'Checkout Repository'
+ uses: actions/checkout@v4
+ - name: 'Dependency Review'
+ uses: actions/dependency-review-action@v4
diff --git a/Build.ps1 b/Build.ps1
index ce11718..6c6ff9c 100644
--- a/Build.ps1
+++ b/Build.ps1
@@ -1,118 +1,27 @@
-function CheckLastExitCode {
- param ([int[]]$SuccessCodes = @(0), [scriptblock]$CleanupScript=$null)
-
- if ($SuccessCodes -notcontains $LastExitCode) {
- throw "Executable returned exit code $LastExitCode"
- }
-}
-
-function RunInspectCode {
- $outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml')
- dotnet jb inspectcode JsonApiDotNetCore.MongoDb.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"
- if ($xml.report.Issues -and $xml.report.Issues.Project) {
- foreach ($project in $xml.report.Issues.Project) {
- if ($project.Issue.Count -gt 0) {
- $project.ForEach({
- Write-Output "`nProject $($project.Name)"
- $failed = $true
-
- $_.Issue.ForEach({
- $issueType = $xml.report.IssueTypes.SelectSingleNode("IssueType[@Id='$($_.TypeId)']")
- $severity = $_.Severity ?? $issueType.Severity
-
- Write-Output "[$severity] $($_.File):$($_.Line) $($_.Message)"
- })
- })
- }
- }
-
- if ($failed) {
- throw "One or more projects failed code inspection.";
- }
- }
-}
-
-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) {
- # 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 a detached head (the PR merge result).
-
- $headCommitHash = git rev-parse HEAD
- CheckLastExitCode
-
- $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.MongoDb.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
- }
+function VerifySuccessExitCode {
+ if ($LastExitCode -ne 0) {
+ throw "Command failed with exit code $LastExitCode."
}
}
-function ReportCodeCoverage {
- if ($env:APPVEYOR) {
- if ($IsWindows) {
- dotnet codecov -f "**\coverage.cobertura.xml"
- }
- }
- else {
- dotnet reportgenerator -reports:**\coverage.cobertura.xml -targetdir:artifacts\coverage
- }
-
- CheckLastExitCode
-}
-
-function CreateNuGetPackage {
- if ($env:APPVEYOR_REPO_TAG -eq $true) {
- # Get the version suffix from the repo tag. Example: v1.0.0-preview1-final => preview1-final
- $segments = $env:APPVEYOR_REPO_TAG_NAME -split "-"
- $suffixSegments = $segments[1..2]
- $versionSuffix = $suffixSegments -join "-"
- }
- else {
- # Get the version suffix from the auto-incrementing build number. Example: "123" => "master-0123".
- if ($env:APPVEYOR_BUILD_NUMBER) {
- $revision = "{0:D4}" -f [convert]::ToInt32($env:APPVEYOR_BUILD_NUMBER, 10)
- $versionSuffix = "$($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH ?? $env:APPVEYOR_REPO_BRANCH)-$revision"
- }
- else {
- $versionSuffix = "pre-0001"
- }
- }
-
- if ([string]::IsNullOrWhitespace($versionSuffix)) {
- dotnet pack --no-restore --no-build --configuration Release --output .\artifacts
- }
- else {
- dotnet pack --no-restore --no-build --configuration Release --output .\artifacts --version-suffix=$versionSuffix
- }
+Write-Host "$(pwsh --version)"
+Write-Host "Active .NET SDK: $(dotnet --version)"
+Write-Host "Using version suffix: $versionSuffix"
- CheckLastExitCode
-}
+Remove-Item -Recurse -Force artifacts -ErrorAction SilentlyContinue
+Remove-Item -Recurse -Force * -Include coverage.cobertura.xml
dotnet tool restore
-CheckLastExitCode
-
-dotnet build -c Release
-CheckLastExitCode
+VerifySuccessExitCode
-# https://youtrack.jetbrains.com/issue/RSRP-488628/Breaking-InspectCode-fails-with-Roslyn-Worker-process-exited-unexpectedly-after-update
-if ($IsWindows) {
- RunInspectCode
- RunCleanupCode
-}
+dotnet build --configuration Release
+VerifySuccessExitCode
-dotnet test -c Release --no-build --collect:"XPlat Code Coverage"
-CheckLastExitCode
+dotnet test --no-build --configuration Release --verbosity quiet --collect:"XPlat Code Coverage"
+VerifySuccessExitCode
-ReportCodeCoverage
+dotnet reportgenerator -reports:**\coverage.cobertura.xml -targetdir:artifacts\coverage -filefilters:-*.g.cs
+VerifySuccessExitCode
-CreateNuGetPackage
+dotnet pack --no-build --configuration Release --output artifacts/packages
+VerifySuccessExitCode
diff --git a/CSharpGuidelinesAnalyzer.config b/CSharpGuidelinesAnalyzer.config
index acd0856..89b568e 100644
--- a/CSharpGuidelinesAnalyzer.config
+++ b/CSharpGuidelinesAnalyzer.config
@@ -1,5 +1,5 @@
-
+
diff --git a/CodingGuidelines.ruleset b/CodingGuidelines.ruleset
index e647ad9..b29d742 100644
--- a/CodingGuidelines.ruleset
+++ b/CodingGuidelines.ruleset
@@ -1,32 +1,54 @@
-
+
+
+
-
-
-
-
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index a9d5421..9619270 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,38 +1,56 @@
- net6.0
- 6.0.*
- 5.1.2
- 2.15.0
- 5.1.2
- $(MSBuildThisFileDirectory)CodingGuidelines.ruleset
- 9999
enable
+ latest
enable
false
false
+ true
+ Recommended
+ $(MSBuildThisFileDirectory)CodingGuidelines.ruleset
+ $(MSBuildThisFileDirectory)tests.runsettings
+ 5.7.2
+ pre
+ direct
-
-
-
-
-
+
+
+ IDE0028;IDE0300;IDE0301;IDE0302;IDE0303;IDE0304;IDE0305;IDE0306
+ $(NoWarn);$(UseCollectionExpressionRules)
+
+
+
+ $(NoWarn);AV2210
+
-
- $(NoWarn);1591;NU5104
+
+ $(NoWarn);1591
true
true
-
- $(NoWarn);AV2210
+
+ true
-
-
- 3.2.0
- 4.16.1
- 17.4.0
+
+ $(NoWarn);CA1707;CA1062
+
+
+
+ $(NoWarn);CA1062
+
+
+
+
+
+
diff --git a/JetBrainsInspectCodeTransform.xslt b/JetBrainsInspectCodeTransform.xslt
index 098821f..28fa772 100644
--- a/JetBrainsInspectCodeTransform.xslt
+++ b/JetBrainsInspectCodeTransform.xslt
@@ -25,6 +25,7 @@
File |
Line Number |
+ Type |
Message |
@@ -35,6 +36,9 @@
|
+
+
+ |
|
diff --git a/JsonApiDotNetCore.MongoDb.sln b/JsonApiDotNetCore.MongoDb.sln
index 0f6c7b7..83efaf6 100644
--- a/JsonApiDotNetCore.MongoDb.sln
+++ b/JsonApiDotNetCore.MongoDb.sln
@@ -23,9 +23,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
+ .github\workflows\build.yml = .github\workflows\build.yml
CodingGuidelines.ruleset = CodingGuidelines.ruleset
CSharpGuidelinesAnalyzer.config = CSharpGuidelinesAnalyzer.config
Directory.Build.props = Directory.Build.props
+ package-versions.props = package-versions.props
+ tests.runsettings = tests.runsettings
EndProjectSection
EndProject
Global
diff --git a/JsonApiDotNetCore.MongoDb.sln.DotSettings b/JsonApiDotNetCore.MongoDb.sln.DotSettings
index 8e80299..cf02da1 100644
--- a/JsonApiDotNetCore.MongoDb.sln.DotSettings
+++ b/JsonApiDotNetCore.MongoDb.sln.DotSettings
@@ -1,19 +1,9 @@
- // Use the following placeholders:
-// $EXPR$ -- source expression
-// $NAME$ -- source name (string literal or 'nameof' expression)
-// $MESSAGE$ -- string literal in the form of "$NAME$ != null"
-JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$);
- 199
- 5000
- 99
- 100
- 200
- 1000
- 500
+ 5000
+ 2000
3000
- 50
False
+ FD312677-2A62-4B8F-A965-879B059F1755/f:ReadOnlySet.cs
SOLUTION
True
True
@@ -28,6 +18,7 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$);
SUGGESTION
SUGGESTION
WARNING
+ WARNING
SUGGESTION
SUGGESTION
SUGGESTION
@@ -53,18 +44,21 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$);
WARNING
WARNING
WARNING
+ DO_NOT_SHOW
WARNING
+ SUGGESTION
HINT
WARNING
+ SUGGESTION
DO_NOT_SHOW
HINT
SUGGESTION
- WARNING
- WARNING
+ SUGGESTION
+ SUGGESTION
WARNING
WARNING
- SUGGESTION
WARNING
+ SUGGESTION
SUGGESTION
SUGGESTION
DO_NOT_SHOW
@@ -76,6 +70,8 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$);
SUGGESTION
SUGGESTION
SUGGESTION
+ WARNING
+ DO_NOT_SHOW
WARNING
WARNING
WARNING
@@ -88,8 +84,14 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$);
WARNING
WARNING
WARNING
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
WARNING
- <?xml version="1.0" encoding="utf-16"?><Profile name="JADNC Full Cleanup"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><HtmlReformatCode>True</HtmlReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags></Profile>
+ True
+ SUGGESTION
+ False
+ <?xml version="1.0" encoding="utf-16"?><Profile name="JADNC Full Cleanup"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" ArrangeNullCheckingPattern="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><HtmlReformatCode>True</HtmlReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSReformatInactiveBranches>True</CSReformatInactiveBranches></Profile>
JADNC Full Cleanup
Required
Required
@@ -109,6 +111,7 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$);
True
True
True
+ INDENT
1
1
False
@@ -116,14 +119,17 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$);
False
False
False
+ False
False
False
False
True
+ 1
NEVER
NEVER
False
NEVER
+ False
False
False
NEVER
@@ -134,14 +140,17 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$);
False
False
CHOP_ALWAYS
+ False
True
True
True
WRAP_IF_LONG
160
+ CHOP_IF_LONG
WRAP_IF_LONG
CHOP_ALWAYS
CHOP_ALWAYS
+ WRAP_IF_LONG
True
True
2
@@ -574,13 +583,17 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$);
False
<Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" />
+ <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy>
+ <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy>
True
True
True
+ True
True
True
+ True
True
- Replace argument null check using throw expression with Guard clause
+ Replace argument null check using throw expression with ArgumentNullException.ThrowIfNull
True
True
False
@@ -599,13 +612,12 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$);
True
CSHARP
False
- Replace argument null check with Guard clause
- JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($argument$);
+ System.ArgumentNullException.ThrowIfNull($argument$);
$left$ = $right$;
$left$ = $right$ ?? throw new ArgumentNullException(nameof($argument$));
WARNING
True
- Replace argument == null check with Guard clause
+ Replace argument == null check with ArgumentNullException.ThrowIfNull
True
True
False
@@ -614,8 +626,7 @@ $left$ = $right$;
True
CSHARP
False
- Replace argument null check with Guard clause
- JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($argument$);
+ System.ArgumentNullException.ThrowIfNull($argument$);
if ($argument$ == null) throw new ArgumentNullException(nameof($argument$));
WARNING
True
@@ -627,12 +638,11 @@ $left$ = $right$;
True
CSHARP
False
- Replace collection null/empty check with extension method
$collection$.IsNullOrEmpty()
$collection$ == null || !$collection$.Any()
WARNING
True
- Replace argument is null check with Guard clause
+ Replace argument is null check with ArgumentNullException.ThrowIfNull
True
True
False
@@ -641,7 +651,7 @@ $left$ = $right$;
True
CSHARP
False
- JsonApiDotNetCore.ArgumentGuard.NotNull($argument$);
+ System.ArgumentNullException.ThrowIfNull($argument$);
if ($argument$ is null) throw new ArgumentNullException(nameof($argument$));
WARNING
True
diff --git a/LICENSE b/LICENSE
index 509975f..54cf85b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,23 @@
-The MIT License (MIT)
Copyright (c) 2020 Alvaro Nicoli
+Copyright (c) 2021 Bart Koelman
-Permission is hereby granted, free of charge, to any person
-obtaining a copy of this software and associated documentation
-files (the "Software"), to deal in the Software without restriction,
-including without limitation the rights to use, copy, modify, merge,
-publish, distribute, sublicense, and/or sell copies of the Software,
-and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
+MIT License
-The above copyright notice and this permission notice shall be included
-in all copies or substantial portions of the Software.
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-USE OR OTHER DEALINGS IN THE SOFTWARE.
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/NuGet.config b/NuGet.config
deleted file mode 100644
index aacab5d..0000000
--- a/NuGet.config
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/PackageReadme.md b/PackageReadme.md
new file mode 100644
index 0000000..a2889e6
--- /dev/null
+++ b/PackageReadme.md
@@ -0,0 +1 @@
+[MongoDB](https://www.mongodb.com/) persistence for [JsonApiDotNetCore](https://www.jsonapi.net/), which is a framework for building JSON:API compliant REST APIs using ASP.NET Core.
diff --git a/README.md b/README.md
index a1f85f9..b7f6f9c 100644
--- a/README.md
+++ b/README.md
@@ -1,109 +1,182 @@
# MongoDB support for JsonApiDotNetCore
-Plug-n-play implementation of `IResourceRepository` allowing you to use [MongoDB](https://www.mongodb.com/) with your [JsonApiDotNetCore](https://github.com/json-api-dotnet/JsonApiDotNetCore) APIs.
-
-[](https://ci.appveyor.com/project/json-api-dotnet/jsonapidotnetcore-mongodb/branch/master)
+[](https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb/actions/workflows/build.yml?query=branch%3Amaster)
[](https://codecov.io/gh/json-api-dotnet/JsonApiDotNetCore.MongoDb)
[](https://www.nuget.org/packages/JsonApiDotNetCore.MongoDb/)
-
-## Installation and Usage
-
-```bash
-dotnet add package JsonApiDotNetCore.MongoDb
-```
-
-### Models
-
-```c#
-#nullable enable
-
-[Resource]
-public class Book : HexStringMongoIdentifiable
-{
- [Attr]
- public string Name { get; set; } = null!;
-}
-```
-
-### Middleware
-
-```c#
-// Program.cs
-
-#nullable enable
-
-WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
-
-// Add services to the container.
-
-builder.Services.AddSingleton(_ =>
-{
- var client = new MongoClient("mongodb://localhost:27017");
- return client.GetDatabase("ExampleDbName");
-});
-
-builder.Services.AddJsonApi(resources: resourceGraphBuilder =>
-{
- resourceGraphBuilder.Add();
-});
-
-builder.Services.AddJsonApiMongoDb();
-
-builder.Services.AddResourceRepository>();
-
-// Configure the HTTP request pipeline.
-
-app.UseRouting();
-app.UseJsonApi();
-app.MapControllers();
-
-app.Run();
-```
-
-Note: If your API project uses MongoDB only (so not in combination with EF Core), then instead of
-registering all MongoDB resources and repositories individually, you can use:
-
-```c#
-builder.Services.AddJsonApi(facade => facade.AddCurrentAssembly());
-builder.Services.AddJsonApiMongoDb();
-
-builder.Services.AddScoped(typeof(IResourceReadRepository<,>), typeof(MongoRepository<,>));
-builder.Services.AddScoped(typeof(IResourceWriteRepository<,>), typeof(MongoRepository<,>));
-builder.Services.AddScoped(typeof(IResourceRepository<,>), typeof(MongoRepository<,>));
-```
+[](LICENSE)
+
+Plug-n-play implementation of `IResourceRepository`, allowing you to use [MongoDB](https://www.mongodb.com/) with your [JsonApiDotNetCore](https://github.com/json-api-dotnet/JsonApiDotNetCore) API projects.
+
+## Getting started
+
+The following steps describe how to create a JSON:API project with MongoDB.
+
+1. Install the JsonApiDotNetCore.MongoDb package:
+ ```bash
+ dotnet add package JsonApiDotNetCore.MongoDb
+ ```
+
+1. Declare your entities, annotated with JsonApiDotNetCore attributes:
+ ```c#
+ #nullable enable
+
+ [Resource]
+ public class Person : HexStringMongoIdentifiable
+ {
+ [Attr] public string? FirstName { get; set; }
+ [Attr] public string LastName { get; set; } = null!;
+ }
+ ```
+
+1. Configure MongoDB and JsonApiDotNetCore in `Program.cs`, seeding the database with sample data:
+ ```c#
+ var builder = WebApplication.CreateBuilder(args);
+ builder.Services.AddSingleton(_ =>
+ new MongoClient("mongodb://localhost:27017").GetDatabase("ExampleDbName"));
+ builder.Services.AddJsonApi(options =>
+ {
+ options.UseRelativeLinks = true;
+ options.IncludeTotalResourceCount = true;
+ }, resources: resourceGraphBuilder => resourceGraphBuilder.Add());
+ builder.Services.AddJsonApiMongoDb();
+ builder.Services.AddResourceRepository>();
+
+ var app = builder.Build();
+ app.UseRouting();
+ app.UseJsonApi();
+ app.MapControllers();
+
+ var database = app.Services.GetRequiredService();
+ await CreateSampleDataAsync(database);
+
+ app.Run();
+
+ static async Task CreateSampleDataAsync(IMongoDatabase database)
+ {
+ await database.DropCollectionAsync(nameof(Person));
+ await database.GetCollection(nameof(Person)).InsertManyAsync(new[]
+ {
+ new Person
+ {
+ FirstName = "John",
+ LastName = "Doe",
+ },
+ new Person
+ {
+ FirstName = "Jane",
+ LastName = "Doe",
+ },
+ new Person
+ {
+ FirstName = "John",
+ LastName = "Smith",
+ }
+ });
+ }
+ ```
+
+1. Start your API
+ ```bash
+ dotnet run
+ ```
+
+1. Send a GET request to retrieve data:
+ ```bash
+ GET http://localhost:5000/people?filter=equals(lastName,'Doe')&fields[people]=firstName HTTP/1.1
+ ```
+
+
+ Expand to view the JSON response
+
+ ```json
+ {
+ "links": {
+ "self": "/people?filter=equals(lastName,%27Doe%27)&fields[people]=firstName",
+ "first": "/people?filter=equals(lastName,%27Doe%27)&fields%5Bpeople%5D=firstName",
+ "last": "/people?filter=equals(lastName,%27Doe%27)&fields%5Bpeople%5D=firstName"
+ },
+ "data": [
+ {
+ "type": "people",
+ "id": "680cae2e1759666c5c1e988c",
+ "attributes": {
+ "firstName": "John"
+ },
+ "links": {
+ "self": "/people/680cae2e1759666c5c1e988c"
+ }
+ },
+ {
+ "type": "people",
+ "id": "680cae2e1759666c5c1e988d",
+ "attributes": {
+ "firstName": "Jane"
+ },
+ "links": {
+ "self": "/people/680cae2e1759666c5c1e988d"
+ }
+ }
+ ],
+ "meta": {
+ "total": 2
+ }
+ }
+ ```
+
+
+
+> [!TIP]
+> If your API project uses MongoDB only (so not in combination with EF Core), then instead of
+> registering all MongoDB resources and repositories individually, you can use:
+>
+> ```c#
+> builder.Services.AddJsonApi(facade => facade.AddCurrentAssembly());
+> builder.Services.AddJsonApiMongoDb();
+>
+> builder.Services.AddScoped(typeof(IResourceReadRepository<,>), typeof(MongoRepository<,>));
+> builder.Services.AddScoped(typeof(IResourceWriteRepository<,>), typeof(MongoRepository<,>));
+> builder.Services.AddScoped(typeof(IResourceRepository<,>), typeof(MongoRepository<,>));
+> ```
## Using client-generated IDs
-Resources that inherit from `HexStringMongoIdentifiable` use auto-generated (performant) 12-byte hexadecimal
+
+Resources that inherit from `HexStringMongoIdentifiable` use auto-generated (high-performance) 12-byte hexadecimal
[Object IDs](https://docs.mongodb.com/manual/reference/bson-types/#objectid).
-You can assign an ID manually, but it must match the 12-byte hexadecimal pattern.
+You can assign an ID explicitly, but it must match the 12-byte hexadecimal pattern.
-To assign free-format string IDs manually, make your resources inherit from `FreeStringMongoIdentifiable` instead.
+To use free-format string IDs, make your resources inherit from `FreeStringMongoIdentifiable` instead.
When creating a resource without assigning an ID, a 12-byte hexadecimal ID will be auto-generated.
-Set `options.AllowClientGeneratedIds` to `true` in Program.cs to allow API clients to assign IDs. This can be combined
+Set `options.ClientIdGeneration` to `Allowed` or `Required` from `Program.cs` to enable API clients to assign IDs. This can be combined
with both base classes, but `FreeStringMongoIdentifiable` probably makes the most sense.
## Limitations
-- JSON:API relationships are currently not supported. You can use complex object graphs though, which are stored in a single document.
+- JSON:API relationships are [currently not supported](https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb/issues/73). You *can* use complex object graphs though, which are stored in a single document.
-## Contributing
+## Trying out the latest build
-Have a question, found a bug or want to submit code changes? See our [contributing guidelines](https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/master/.github/CONTRIBUTING.md).
+After each commit to the master branch, a new pre-release NuGet package is automatically published to [feedz.io](https://feedz.io/docs/package-types/nuget).
+To try it out, follow the steps below:
-## Trying out the latest build
+1. Create a `nuget.config` file in the same directory as your .sln file, with the following contents:
+ ```xml
+
+
+
+
+
+
+
+ ```
+
+1. In your IDE, browse the list of packages from the `json-api-dotnet` feed. Make sure pre-release packages are included in the list.
-After each commit to the master branch, a new prerelease NuGet package is automatically published to AppVeyor at https://ci.appveyor.com/nuget/jsonapidotnetcore-mongodb. To try it out, follow the next steps:
+## Contributing
-* In Visual Studio: **Tools**, **NuGet Package Manager**, **Package Manager Settings**, **Package Sources**
- * Click **+**
- * Name: **AppVeyor JADNC MongoDb**, Source: **https://ci.appveyor.com/nuget/jsonapidotnetcore-mongodb**
- * Click **Update**, **Ok**
-* Open the NuGet package manager console (**Tools**, **NuGet Package Manager**, **Package Manager Console**)
- * Select **AppVeyor JADNC MongoDb** as package source
- * Run command: `Install-Package JonApiDotNetCore -pre`
+Have a question, found a bug or want to submit code changes? See our [contributing guidelines](https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/master/.github/CONTRIBUTING.md).
-## Development
+## Build from source
To build the code from this repository locally, run:
@@ -111,13 +184,14 @@ To build the code from this repository locally, run:
dotnet build
```
-You don't need to have a running instance of MongoDB on your machine to run tests. Just type the following command in your terminal:
+You can run tests without MongoDB on your machine. The following command runs all tests:
```bash
dotnet test
```
-If you want to run the examples and explore them on your own **you are** going to need that running instance of MongoDB. If you have docker installed you can launch it like this:
+A running instance of MongoDB is required to run the examples.
+If you have docker installed, you can launch MongoDB in a container with the following command:
```bash
pwsh run-docker-mongodb.ps1
@@ -129,7 +203,7 @@ And then to run the API:
dotnet run --project src/Examples/GettingStarted
```
-Alternatively, to build and validate the code, run all tests, generate code coverage and produce the NuGet package:
+Alternatively, to build, run all tests, generate code coverage and NuGet packages:
```bash
pwsh Build.ps1
diff --git a/WarningSeverities.DotSettings b/WarningSeverities.DotSettings
index 0d4eeba..5b64971 100644
--- a/WarningSeverities.DotSettings
+++ b/WarningSeverities.DotSettings
@@ -1,4 +1,5 @@
+ WARNING
WARNING
WARNING
WARNING
@@ -13,6 +14,7 @@
WARNING
WARNING
WARNING
+ WARNING
WARNING
WARNING
WARNING
@@ -68,8 +70,10 @@
WARNING
WARNING
WARNING
+ WARNING
WARNING
WARNING
+ WARNING
WARNING
WARNING
WARNING
@@ -82,6 +86,7 @@
WARNING
WARNING
WARNING
+ WARNING
WARNING
WARNING
WARNING
@@ -97,8 +102,13 @@
WARNING
WARNING
WARNING
+ WARNING
WARNING
WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
WARNING
WARNING
WARNING
@@ -109,12 +119,16 @@
WARNING
WARNING
WARNING
+ WARNING
WARNING
WARNING
WARNING
WARNING
+ WARNING
WARNING
WARNING
+ WARNING
+ WARNING
WARNING
WARNING
WARNING
@@ -135,6 +149,7 @@
WARNING
WARNING
WARNING
+ WARNING
WARNING
WARNING
WARNING
@@ -152,6 +167,8 @@
WARNING
WARNING
WARNING
+ WARNING
+ WARNING
WARNING
WARNING
WARNING
@@ -227,6 +244,7 @@
WARNING
WARNING
WARNING
+ WARNING
WARNING
WARNING
WARNING
@@ -240,14 +258,17 @@
WARNING
WARNING
WARNING
+ WARNING
WARNING
WARNING
WARNING
WARNING
+ WARNING
WARNING
WARNING
WARNING
WARNING
WARNING
WARNING
+ WARNING
\ No newline at end of file
diff --git a/appveyor.yml b/appveyor.yml
index 2593740..1ad1455 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,54 +1,12 @@
-image:
- - Ubuntu2004
- - Visual Studio 2022
+image: Visual Studio 2022
version: '{build}'
branches:
only:
- master
- - develop
- - unstable
- /release\/.+/
-pull_requests:
- do_not_increment_build_number: true
-
-nuget:
- disable_publish_on_pr: true
-
-matrix:
- fast_finish: true
-
-for:
--
- matrix:
- only:
- - image: Visual Studio 2022
- artifacts:
- - path: .\**\artifacts\**\*.nupkg
- name: NuGet
- deploy:
- - provider: NuGet
- skip_symbols: false
- api_key:
- secure: S9fkLwmhi7w+DGouXYqYq/1PGocnYo8UBUKwv+BGpWHnzE6yHZEYth3j/XJ9Ydsa
- on:
- branch: master
- appveyor_repo_tag: true
- - provider: NuGet
- skip_symbols: false
- api_key:
- secure: S9fkLwmhi7w+DGouXYqYq/1PGocnYo8UBUKwv+BGpWHnzE6yHZEYth3j/XJ9Ydsa
- on:
- branch: /release\/.+/
- appveyor_repo_tag: true
-
-build_script:
-- pwsh: |
- Write-Output ".NET version:"
- dotnet --version
-
- .\Build.ps1
-
-test: off
\ No newline at end of file
+build: off
+test: off
+deploy: off
diff --git a/cleanupcode.ps1 b/cleanupcode.ps1
index 717f369..9a86aba 100644
--- a/cleanupcode.ps1
+++ b/cleanupcode.ps1
@@ -28,17 +28,17 @@ if ($revision) {
if ($baseCommitHash -eq $headCommitHash) {
Write-Output "Running code cleanup on staged/unstaged files."
- dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified
+ dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --skip-tool-check --max-runs=5 --jb --dotnetcoresdk=$(dotnet --version) --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --properties:RunAnalyzers=false --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.MongoDb.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
+ dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --skip-tool-check --max-runs=5 --jb --dotnetcoresdk=$(dotnet --version) --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --properties:RunAnalyzers=false --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.MongoDb.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN
+ dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --skip-tool-check --jb --dotnetcoresdk=$(dotnet --version) --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --properties:RunAnalyzers=false --jb --verbosity=WARN
VerifySuccessExitCode
}
diff --git a/inspectcode.ps1 b/inspectcode.ps1
index 0c55d7b..25195fe 100644
--- a/inspectcode.ps1
+++ b/inspectcode.ps1
@@ -4,16 +4,16 @@
dotnet tool restore
-if ($LASTEXITCODE -ne 0) {
- throw "Tool restore failed with exit code $LASTEXITCODE"
+if ($LastExitCode -ne 0) {
+ throw "Tool restore failed with exit code $LastExitCode"
}
$outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml')
$resultPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.html')
-dotnet jb inspectcode JsonApiDotNetCore.MongoDb.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.MongoDb.sln --dotnetcoresdk=$(dotnet --version) --build --output="$outputPath" --format="xml" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --properties:RunAnalyzers=false --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal
-if ($LASTEXITCODE -ne 0) {
- throw "Code inspection failed with exit code $LASTEXITCODE"
+if ($LastExitCode -ne 0) {
+ throw "Code inspection failed with exit code $LastExitCode"
}
[xml]$xml = Get-Content "$outputPath"
diff --git a/logo.png b/logo.png
deleted file mode 100644
index 78f1acd..0000000
Binary files a/logo.png and /dev/null differ
diff --git a/package-icon.png b/package-icon.png
new file mode 100644
index 0000000..f95eb77
Binary files /dev/null and b/package-icon.png differ
diff --git a/package-versions.props b/package-versions.props
new file mode 100644
index 0000000..94750ca
--- /dev/null
+++ b/package-versions.props
@@ -0,0 +1,29 @@
+
+
+
+ 5.7.1
+ 3.3.0
+
+
+ 35.6.*
+ 6.0.*
+ 3.1.*
+ 7.2.*
+ 2.4.*
+ 2.0.*
+ 3.3.*
+ 17.13.*
+ 2.9.*
+ 2.8.*
+
+
+
+
+ 9.0.*
+
+
+
+
+ 8.0.*
+
+
diff --git a/run-docker-mongodb.ps1 b/run-docker-mongodb.ps1
index b1092b4..215b048 100644
--- a/run-docker-mongodb.ps1
+++ b/run-docker-mongodb.ps1
@@ -1,9 +1,6 @@
#Requires -Version 7.0
-# This script starts a docker container with MongoDB database, used for running tests.
+# This script starts a MongoDB database in a docker container, which is required for running examples locally.
-docker container stop jsonapi-dotnet-core-mongodb-testing
-
-docker run --rm --name jsonapi-dotnet-core-mongodb-testing `
- -p 27017:27017 `
- mongo:latest
+docker container stop jsonapi-mongo-db
+docker run --pull always --rm --detach --name jsonapi-mongo-db -p 27017:27017 mongo:latest
diff --git a/src/Examples/GettingStarted/GettingStarted.csproj b/src/Examples/GettingStarted/GettingStarted.csproj
index a5b25ea..408a434 100644
--- a/src/Examples/GettingStarted/GettingStarted.csproj
+++ b/src/Examples/GettingStarted/GettingStarted.csproj
@@ -1,8 +1,10 @@
- $(TargetFrameworkName)
+ net9.0;net8.0
+
+
diff --git a/src/Examples/GettingStarted/Program.cs b/src/Examples/GettingStarted/Program.cs
index e28c933..3a20376 100644
--- a/src/Examples/GettingStarted/Program.cs
+++ b/src/Examples/GettingStarted/Program.cs
@@ -2,22 +2,31 @@
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.MongoDb.Configuration;
using JsonApiDotNetCore.MongoDb.Repositories;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using MongoDB.Driver;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Add services to the container.
-builder.Services.AddSingleton(_ =>
+builder.Services.TryAddSingleton(_ =>
{
var client = new MongoClient(builder.Configuration.GetSection("DatabaseSettings:ConnectionString").Value);
return client.GetDatabase(builder.Configuration.GetSection("DatabaseSettings:Database").Value);
});
-builder.Services.AddJsonApi(ConfigureJsonApiOptions, resources: resourceGraphBuilder =>
+builder.Services.AddJsonApi(options =>
{
- resourceGraphBuilder.Add();
-});
+ options.Namespace = "api";
+ options.UseRelativeLinks = true;
+ options.IncludeTotalResourceCount = true;
+
+#if DEBUG
+ options.IncludeExceptionStackTraceInErrors = true;
+ options.IncludeRequestBodyInErrors = true;
+ options.SerializerOptions.WriteIndented = true;
+#endif
+}, resources: resourceGraphBuilder => resourceGraphBuilder.Add());
builder.Services.AddJsonApiMongoDb();
@@ -34,15 +43,7 @@
var database = app.Services.GetRequiredService();
await CreateSampleDataAsync(database);
-app.Run();
-
-static void ConfigureJsonApiOptions(JsonApiOptions options)
-{
- options.Namespace = "api";
- options.UseRelativeLinks = true;
- options.IncludeTotalResourceCount = true;
- options.SerializerOptions.WriteIndented = true;
-}
+await app.RunAsync();
static async Task CreateSampleDataAsync(IMongoDatabase database)
{
diff --git a/src/Examples/GettingStarted/Properties/launchSettings.json b/src/Examples/GettingStarted/Properties/launchSettings.json
index ad97b55..55041b5 100644
--- a/src/Examples/GettingStarted/Properties/launchSettings.json
+++ b/src/Examples/GettingStarted/Properties/launchSettings.json
@@ -1,5 +1,5 @@
{
- "$schema": "http://json.schemastore.org/launchsettings.json",
+ "$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
@@ -10,7 +10,7 @@
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
- "launchBrowser": false,
+ "launchBrowser": true,
"launchUrl": "api/books",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
@@ -18,7 +18,7 @@
},
"Kestrel": {
"commandName": "Project",
- "launchBrowser": false,
+ "launchBrowser": true,
"launchUrl": "api/books",
"applicationUrl": "http://localhost:24141",
"environmentVariables": {
diff --git a/src/Examples/GettingStarted/README.md b/src/Examples/GettingStarted/README.md
index aa8afe0..b076df4 100644
--- a/src/Examples/GettingStarted/README.md
+++ b/src/Examples/GettingStarted/README.md
@@ -7,8 +7,7 @@
You can verify the project is running by checking this endpoint:
`localhost:24141/api/people`
-For further documentation and implementation of a JsonApiDotNetCore Application see the documentation or GitHub page:
+For further documentation and implementation of a JsonApiDotNetCore application, see the documentation or GitHub page:
Repository: https://github.com/json-api-dotnet/JsonApiDotNetCore
-
-Documentation: http://www.jsonapi.net
+Documentation: https://www.jsonapi.net
diff --git a/src/Examples/GettingStarted/appsettings.json b/src/Examples/GettingStarted/appsettings.json
index 31455b7..4db298e 100644
--- a/src/Examples/GettingStarted/appsettings.json
+++ b/src/Examples/GettingStarted/appsettings.json
@@ -6,7 +6,9 @@
"Logging": {
"LogLevel": {
"Default": "Warning",
+ // Include server startup and incoming requests.
"Microsoft.Hosting.Lifetime": "Information",
+ "Microsoft.AspNetCore.Hosting.Diagnostics": "Information",
"Microsoft.EntityFrameworkCore": "Critical"
}
},
diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs
index 8172459..a2a1ca7 100644
--- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs
+++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs
@@ -6,11 +6,7 @@
namespace JsonApiDotNetCoreMongoDbExample.Controllers;
-public sealed class OperationsController : JsonApiOperationsController
-{
- public OperationsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor,
- IJsonApiRequest request, ITargetedFields targetedFields)
- : base(options, resourceGraph, loggerFactory, processor, request, targetedFields)
- {
- }
-}
+public sealed class OperationsController(
+ IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request,
+ ITargetedFields targetedFields, IAtomicOperationFilter operationFilter)
+ : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields, operationFilter);
diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Definitions/TodoItemDefinition.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Definitions/TodoItemDefinition.cs
index 8079def..96c21ee 100644
--- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Definitions/TodoItemDefinition.cs
+++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Definitions/TodoItemDefinition.cs
@@ -5,20 +5,14 @@
using JsonApiDotNetCore.Queries.Expressions;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCoreMongoDbExample.Models;
-using Microsoft.AspNetCore.Authentication;
namespace JsonApiDotNetCoreMongoDbExample.Definitions;
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
-public sealed class TodoItemDefinition : JsonApiResourceDefinition
+public sealed class TodoItemDefinition(IResourceGraph resourceGraph, TimeProvider timeProvider)
+ : JsonApiResourceDefinition(resourceGraph)
{
- private readonly ISystemClock _systemClock;
-
- public TodoItemDefinition(IResourceGraph resourceGraph, ISystemClock systemClock)
- : base(resourceGraph)
- {
- _systemClock = systemClock;
- }
+ private readonly TimeProvider _timeProvider = timeProvider;
public override SortExpression OnApplySort(SortExpression? existingSort)
{
@@ -27,22 +21,21 @@ public override SortExpression OnApplySort(SortExpression? existingSort)
private SortExpression GetDefaultSortOrder()
{
- return CreateSortExpressionFromLambda(new PropertySortOrder
- {
- (todoItem => todoItem.Priority, ListSortDirection.Descending),
+ return CreateSortExpressionFromLambda([
+ (todoItem => todoItem.Priority, ListSortDirection.Ascending),
(todoItem => todoItem.LastModifiedAt, ListSortDirection.Descending)
- });
+ ]);
}
public override Task OnWritingAsync(TodoItem resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
{
if (writeOperation == WriteOperationKind.CreateResource)
{
- resource.CreatedAt = _systemClock.UtcNow;
+ resource.CreatedAt = _timeProvider.GetUtcNow();
}
else if (writeOperation == WriteOperationKind.UpdateResource)
{
- resource.LastModifiedAt = _systemClock.UtcNow;
+ resource.LastModifiedAt = _timeProvider.GetUtcNow();
}
return Task.CompletedTask;
diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj b/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj
index a5b25ea..408a434 100644
--- a/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj
+++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj
@@ -1,8 +1,10 @@
- $(TargetFrameworkName)
+ net9.0;net8.0
+
+
diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItem.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItem.cs
index 7dc654c..9b0e8d6 100644
--- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItem.cs
+++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItem.cs
@@ -16,6 +16,9 @@ public sealed class TodoItem : HexStringMongoIdentifiable
[Required]
public TodoItemPriority? Priority { get; set; }
+ [Attr]
+ public long? DurationInHours { get; set; }
+
[Attr(Capabilities = AttrCapabilities.AllowFilter | AttrCapabilities.AllowSort | AttrCapabilities.AllowView)]
public DateTimeOffset CreatedAt { get; set; }
diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItemPriority.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItemPriority.cs
index a782897..3bb17ed 100644
--- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItemPriority.cs
+++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItemPriority.cs
@@ -5,7 +5,7 @@ namespace JsonApiDotNetCoreMongoDbExample.Models;
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
public enum TodoItemPriority
{
- Low,
- Medium,
- High
+ High = 1,
+ Medium = 2,
+ Low = 3
}
diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs
index 5f4b0dc..ff216b8 100644
--- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs
+++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs
@@ -1,30 +1,33 @@
+using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.MongoDb.Configuration;
using JsonApiDotNetCore.MongoDb.Repositories;
using JsonApiDotNetCore.Repositories;
-using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using MongoDB.Driver;
+[assembly: ExcludeFromCodeCoverage]
+
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Add services to the container.
-builder.Services.AddSingleton();
+builder.Services.TryAddSingleton(TimeProvider.System);
-builder.Services.AddSingleton(_ =>
+builder.Services.TryAddSingleton(_ =>
{
- var client = new MongoClient(builder.Configuration.GetSection("DatabaseSettings:ConnectionString").Value);
- return client.GetDatabase(builder.Configuration.GetSection("DatabaseSettings:Database").Value);
+ var client = new MongoClient(builder.Configuration.GetValue("DatabaseSettings:ConnectionString"));
+ return client.GetDatabase(builder.Configuration.GetValue("DatabaseSettings:Database"));
});
+builder.Services.TryAddScoped(typeof(IResourceReadRepository<,>), typeof(MongoRepository<,>));
+builder.Services.TryAddScoped(typeof(IResourceWriteRepository<,>), typeof(MongoRepository<,>));
+builder.Services.TryAddScoped(typeof(IResourceRepository<,>), typeof(MongoRepository<,>));
+
builder.Services.AddJsonApi(ConfigureJsonApiOptions, facade => facade.AddCurrentAssembly());
builder.Services.AddJsonApiMongoDb();
-builder.Services.AddScoped(typeof(IResourceReadRepository<,>), typeof(MongoRepository<,>));
-builder.Services.AddScoped(typeof(IResourceWriteRepository<,>), typeof(MongoRepository<,>));
-builder.Services.AddScoped(typeof(IResourceRepository<,>), typeof(MongoRepository<,>));
-
WebApplication app = builder.Build();
// Configure the HTTP request pipeline.
@@ -33,17 +36,17 @@
app.UseJsonApi();
app.MapControllers();
-app.Run();
+await app.RunAsync();
static void ConfigureJsonApiOptions(JsonApiOptions options)
{
- options.Namespace = "api/v1";
+ options.Namespace = "api";
options.UseRelativeLinks = true;
options.IncludeTotalResourceCount = true;
- options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
#if DEBUG
options.IncludeExceptionStackTraceInErrors = true;
options.IncludeRequestBodyInErrors = true;
+ options.SerializerOptions.WriteIndented = true;
#endif
}
diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Properties/AssemblyInfo.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Properties/AssemblyInfo.cs
deleted file mode 100644
index 82d1291..0000000
--- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-
-// https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/MSBuildIntegration.md#excluding-from-coverage
-[assembly: ExcludeFromCodeCoverage]
diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Properties/launchSettings.json b/src/Examples/JsonApiDotNetCoreMongoDbExample/Properties/launchSettings.json
index f155249..efcce2e 100644
--- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Properties/launchSettings.json
+++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Properties/launchSettings.json
@@ -1,5 +1,5 @@
{
- "$schema": "http://json.schemastore.org/launchsettings.json",
+ "$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
@@ -11,16 +11,16 @@
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
- "launchBrowser": false,
- "launchUrl": "api/v1/todoItems",
+ "launchBrowser": true,
+ "launchUrl": "api/todoItems",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Kestrel": {
"commandName": "Project",
- "launchBrowser": false,
- "launchUrl": "api/v1/todoItems",
+ "launchBrowser": true,
+ "launchUrl": "api/todoItems",
"applicationUrl": "https://localhost:44340;http://localhost:24140",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/appsettings.json b/src/Examples/JsonApiDotNetCoreMongoDbExample/appsettings.json
index dde4b49..b8ed43e 100644
--- a/src/Examples/JsonApiDotNetCoreMongoDbExample/appsettings.json
+++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/appsettings.json
@@ -6,7 +6,9 @@
"Logging": {
"LogLevel": {
"Default": "Warning",
+ // Include server startup and incoming requests.
"Microsoft.Hosting.Lifetime": "Information",
+ "Microsoft.AspNetCore.Hosting.Diagnostics": "Information",
"Microsoft.EntityFrameworkCore": "Critical"
}
},
diff --git a/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs b/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs
deleted file mode 100644
index c7c9908..0000000
--- a/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-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.MongoDb;
-
-internal static class ArgumentGuard
-{
- [AssertionMethod]
- public static void NotNull([NoEnumeration] [SysNotNull] T? value, [CallerArgumentExpression("value")] string? parameterName = null)
- where T : class
- {
- ArgumentNullException.ThrowIfNull(value, parameterName);
- }
-}
diff --git a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs
index 7aa1388..0e19327 100644
--- a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs
+++ b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs
@@ -4,7 +4,7 @@
namespace JsonApiDotNetCore.MongoDb.AtomicOperations;
-///
+///
[PublicAPI]
public sealed class MongoTransaction : IOperationsTransaction
{
@@ -16,7 +16,7 @@ public sealed class MongoTransaction : IOperationsTransaction
public MongoTransaction(IMongoDataAccess mongoDataAccess, bool ownsTransaction)
{
- ArgumentGuard.NotNull(mongoDataAccess);
+ ArgumentNullException.ThrowIfNull(mongoDataAccess);
_mongoDataAccess = mongoDataAccess;
_ownsTransaction = ownsTransaction;
diff --git a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs
index b51e1a1..d016781 100644
--- a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs
+++ b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs
@@ -12,7 +12,7 @@ public sealed class MongoTransactionFactory : IOperationsTransactionFactory
public MongoTransactionFactory(IMongoDataAccess mongoDataAccess)
{
- ArgumentGuard.NotNull(mongoDataAccess);
+ ArgumentNullException.ThrowIfNull(mongoDataAccess);
_mongoDataAccess = mongoDataAccess;
}
diff --git a/src/JsonApiDotNetCore.MongoDb/Configuration/ResourceGraphExtensions.cs b/src/JsonApiDotNetCore.MongoDb/Configuration/ResourceGraphExtensions.cs
new file mode 100644
index 0000000..5ad31b2
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb/Configuration/ResourceGraphExtensions.cs
@@ -0,0 +1,40 @@
+using System.Reflection;
+using JsonApiDotNetCore.Configuration;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace JsonApiDotNetCore.MongoDb.Configuration;
+
+internal static class ResourceGraphExtensions
+{
+ public static IReadOnlyModel ToEntityModel(this IResourceGraph resourceGraph)
+ {
+ ArgumentNullException.ThrowIfNull(resourceGraph);
+
+ var modelBuilder = new ModelBuilder();
+
+ foreach (ResourceType resourceType in resourceGraph.GetResourceTypes())
+ {
+ IncludeResourceType(resourceType, modelBuilder);
+ }
+
+ return modelBuilder.Model;
+ }
+
+ private static void IncludeResourceType(ResourceType resourceType, ModelBuilder builder)
+ {
+ EntityTypeBuilder entityTypeBuilder = builder.Entity(resourceType.ClrType);
+
+ foreach (PropertyInfo property in resourceType.ClrType.GetProperties().Where(property => !IsIgnored(property)))
+ {
+ entityTypeBuilder.Property(property.PropertyType, property.Name);
+ }
+ }
+
+ private static bool IsIgnored(PropertyInfo property)
+ {
+ return property.GetCustomAttribute() != null;
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs
index f914f91..a49d77e 100644
--- a/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs
+++ b/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs
@@ -1,10 +1,12 @@
using JetBrains.Annotations;
using JsonApiDotNetCore.AtomicOperations;
+using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.MongoDb.AtomicOperations;
-using JsonApiDotNetCore.MongoDb.Queries.Internal;
+using JsonApiDotNetCore.MongoDb.Queries;
using JsonApiDotNetCore.MongoDb.Repositories;
-using JsonApiDotNetCore.Queries.Internal;
+using JsonApiDotNetCore.Queries;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
namespace JsonApiDotNetCore.MongoDb.Configuration;
@@ -16,7 +18,17 @@ public static class ServiceCollectionExtensions
[PublicAPI]
public static IServiceCollection AddJsonApiMongoDb(this IServiceCollection services)
{
- services.AddScoped();
+ ArgumentNullException.ThrowIfNull(services);
+
+ services.TryAddSingleton(serviceProvider =>
+ {
+ var resourceGraph = serviceProvider.GetRequiredService();
+ return resourceGraph.ToEntityModel();
+ });
+
+ services.TryAddScoped();
+
+ // Replace the built-in implementations from JsonApiDotNetCore.
services.AddScoped();
services.AddScoped();
diff --git a/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs b/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs
index 4b7508b..51c7131 100644
--- a/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs
+++ b/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs
@@ -10,13 +10,8 @@ namespace JsonApiDotNetCore.MongoDb.Errors;
/// https://jira.mongodb.org/browse/CSHARP-1592.
///
[PublicAPI]
-public sealed class AttributeComparisonInFilterNotSupportedException : JsonApiException
-{
- public AttributeComparisonInFilterNotSupportedException()
- : base(new ErrorObject(HttpStatusCode.BadRequest)
- {
- Title = "Comparing attributes against each other is not supported when using MongoDB."
- })
+public sealed class AttributeComparisonInFilterNotSupportedException()
+ : JsonApiException(new ErrorObject(HttpStatusCode.BadRequest)
{
- }
-}
+ Title = "Comparing attributes against each other is not supported when using MongoDB."
+ });
diff --git a/src/JsonApiDotNetCore.MongoDb/Errors/UnsupportedRelationshipException.cs b/src/JsonApiDotNetCore.MongoDb/Errors/UnsupportedRelationshipException.cs
index 2526e59..b1401c2 100644
--- a/src/JsonApiDotNetCore.MongoDb/Errors/UnsupportedRelationshipException.cs
+++ b/src/JsonApiDotNetCore.MongoDb/Errors/UnsupportedRelationshipException.cs
@@ -9,13 +9,8 @@ namespace JsonApiDotNetCore.MongoDb.Errors;
/// The error that is thrown when the user attempts to fetch, create or update a relationship.
///
[PublicAPI]
-public sealed class UnsupportedRelationshipException : JsonApiException
-{
- public UnsupportedRelationshipException()
- : base(new ErrorObject(HttpStatusCode.BadRequest)
- {
- Title = "Relationships are not supported when using MongoDB."
- })
+public sealed class UnsupportedRelationshipException()
+ : JsonApiException(new ErrorObject(HttpStatusCode.BadRequest)
{
- }
-}
+ Title = "Relationships are not supported when using MongoDB."
+ });
diff --git a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj
index 2cc225f..13aa46a 100644
--- a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj
+++ b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj
@@ -1,30 +1,29 @@
-
+
- $(TargetFrameworkName)
+ net8.0
true
true
+
+
- $(JsonApiDotNetCoreMongoDbVersionPrefix)
jsonapi;json:api;dotnet;asp.net;rest;web-api;MongoDB
- Persistence layer implementation for use of MongoDB in APIs using JsonApiDotNetCore.
+ MongoDB persistence for JsonApiDotNetCore, which is a framework for building JSON:API compliant REST APIs using ASP.NET Core.
json-api-dotnet
- https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb
+ https://www.jsonapi.net/
MIT
false
See https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb/releases.
- logo.png
+ package-icon.png
+ PackageReadme.md
true
- true
embedded
-
- True
-
-
+
+
@@ -32,9 +31,8 @@
-
-
-
-
+
+
+
diff --git a/src/JsonApiDotNetCore.MongoDb/PolyfillCollectionExtensions.cs b/src/JsonApiDotNetCore.MongoDb/PolyfillCollectionExtensions.cs
new file mode 100644
index 0000000..06adff6
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb/PolyfillCollectionExtensions.cs
@@ -0,0 +1,12 @@
+using System.Collections.ObjectModel;
+
+namespace JsonApiDotNetCore.MongoDb;
+
+// These methods provide polyfills for lower .NET versions.
+internal static class PolyfillCollectionExtensions
+{
+ public static IReadOnlySet AsReadOnly(this HashSet source)
+ {
+ return new ReadOnlySet(source);
+ }
+}
diff --git a/src/JsonApiDotNetCore.MongoDb/Properties/AssemblyInfo.cs b/src/JsonApiDotNetCore.MongoDb/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..58b7ea5
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("TestBuildingBlocks")]
diff --git a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/HideRelationshipsSparseFieldSetCache.cs b/src/JsonApiDotNetCore.MongoDb/Queries/HideRelationshipsSparseFieldSetCache.cs
similarity index 82%
rename from src/JsonApiDotNetCore.MongoDb/Queries/Internal/HideRelationshipsSparseFieldSetCache.cs
rename to src/JsonApiDotNetCore.MongoDb/Queries/HideRelationshipsSparseFieldSetCache.cs
index fcd9d8a..ac5f45c 100644
--- a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/HideRelationshipsSparseFieldSetCache.cs
+++ b/src/JsonApiDotNetCore.MongoDb/Queries/HideRelationshipsSparseFieldSetCache.cs
@@ -2,13 +2,12 @@
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.MongoDb.Resources;
using JsonApiDotNetCore.Queries;
-using JsonApiDotNetCore.Queries.Internal;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
-namespace JsonApiDotNetCore.MongoDb.Queries.Internal;
+namespace JsonApiDotNetCore.MongoDb.Queries;
-///
+///
public sealed class HideRelationshipsSparseFieldSetCache : ISparseFieldSetCache
{
private readonly SparseFieldSetCache _innerCache;
@@ -16,8 +15,8 @@ public sealed class HideRelationshipsSparseFieldSetCache : ISparseFieldSetCache
public HideRelationshipsSparseFieldSetCache(IEnumerable constraintProviders,
IResourceDefinitionAccessor resourceDefinitionAccessor)
{
- ArgumentGuard.NotNull(constraintProviders);
- ArgumentGuard.NotNull(resourceDefinitionAccessor);
+ ArgumentNullException.ThrowIfNull(constraintProviders);
+ ArgumentNullException.ThrowIfNull(resourceDefinitionAccessor);
_innerCache = new SparseFieldSetCache(constraintProviders, resourceDefinitionAccessor);
}
@@ -25,18 +24,24 @@ public HideRelationshipsSparseFieldSetCache(IEnumerable
public IImmutableSet GetSparseFieldSetForQuery(ResourceType resourceType)
{
+ ArgumentNullException.ThrowIfNull(resourceType);
+
return _innerCache.GetSparseFieldSetForQuery(resourceType);
}
///
public IImmutableSet GetIdAttributeSetForRelationshipQuery(ResourceType resourceType)
{
+ ArgumentNullException.ThrowIfNull(resourceType);
+
return _innerCache.GetIdAttributeSetForRelationshipQuery(resourceType);
}
///
public IImmutableSet GetSparseFieldSetForSerializer(ResourceType resourceType)
{
+ ArgumentNullException.ThrowIfNull(resourceType);
+
IImmutableSet fieldSet = _innerCache.GetSparseFieldSetForSerializer(resourceType);
return resourceType.ClrType.IsAssignableTo(typeof(IMongoIdentifiable)) ? RemoveRelationships(fieldSet) : fieldSet;
diff --git a/src/JsonApiDotNetCore.MongoDb/ReadOnlySet.cs b/src/JsonApiDotNetCore.MongoDb/ReadOnlySet.cs
new file mode 100644
index 0000000..9917af9
--- /dev/null
+++ b/src/JsonApiDotNetCore.MongoDb/ReadOnlySet.cs
@@ -0,0 +1,171 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if NET8_0
+#pragma warning disable
+
+// ReadOnlySet was introduced in .NET 9.
+// This file was copied from https://github.com/dotnet/runtime/blob/release/9.0/src/libraries/System.Collections/src/System/Collections/Generic/ReadOnlySet.cs
+// and made internal to enable usage on lower .NET versions.
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Collections.ObjectModel;
+
+/// Represents a read-only, generic set of values.
+/// The type of values in the set.
+[DebuggerDisplay("Count = {Count}")]
+[ExcludeFromCodeCoverage]
+internal class ReadOnlySet : IReadOnlySet, ISet, ICollection
+{
+ /// The wrapped set.
+ private readonly ISet _set;
+
+ /// Initializes a new instance of the class that is a wrapper around the specified set.
+ /// The set to wrap.
+ public ReadOnlySet(ISet set)
+ {
+ ArgumentNullException.ThrowIfNull(set);
+ _set = set;
+ }
+
+ /// Gets an empty .
+ public static ReadOnlySet Empty { get; } = new ReadOnlySet(new HashSet());
+
+ /// Gets the set that is wrapped by this object.
+ protected ISet Set => _set;
+
+ ///
+ public int Count => _set.Count;
+
+ ///
+ public IEnumerator GetEnumerator() =>
+ _set.Count == 0 ? ((IEnumerable)Array.Empty()).GetEnumerator() :
+ _set.GetEnumerator();
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ ///
+ public bool Contains(T item) => _set.Contains(item);
+
+ ///
+ public bool IsProperSubsetOf(IEnumerable other) => _set.IsProperSubsetOf(other);
+
+ ///
+ public bool IsProperSupersetOf(IEnumerable other) => _set.IsProperSupersetOf(other);
+
+ ///
+ public bool IsSubsetOf(IEnumerable other) => _set.IsSubsetOf(other);
+
+ ///
+ public bool IsSupersetOf(IEnumerable other) => _set.IsSupersetOf(other);
+
+ ///
+ public bool Overlaps(IEnumerable other) => _set.Overlaps(other);
+
+ ///
+ public bool SetEquals(IEnumerable other) => _set.SetEquals(other);
+
+ ///
+ void ICollection.CopyTo(T[] array, int arrayIndex) => _set.CopyTo(array, arrayIndex);
+
+ ///
+ void ICollection.CopyTo(Array array, int index) => CollectionHelpers.CopyTo(_set, array, index);
+
+ ///
+ bool ICollection.IsReadOnly => true;
+
+ ///
+ bool ICollection.IsSynchronized => false;
+
+ ///
+ object ICollection.SyncRoot => _set is ICollection c ? c.SyncRoot : this;
+
+ ///
+ bool ISet.Add(T item) => throw new NotSupportedException();
+
+ ///
+ void ISet.ExceptWith(IEnumerable other) => throw new NotSupportedException();
+
+ ///
+ void ISet.IntersectWith(IEnumerable other) => throw new NotSupportedException();
+
+ ///
+ void ISet.SymmetricExceptWith(IEnumerable other) => throw new NotSupportedException();
+
+ ///
+ void ISet.UnionWith(IEnumerable other) => throw new NotSupportedException();
+
+ ///
+ void ICollection.Add(T item) => throw new NotSupportedException();
+
+ ///
+ void ICollection.Clear() => throw new NotSupportedException();
+
+ ///
+ bool ICollection.Remove(T item) => throw new NotSupportedException();
+
+ private static class CollectionHelpers
+ {
+ private static void ValidateCopyToArguments(int sourceCount, Array array, int index)
+ {
+ ArgumentNullException.ThrowIfNull(array);
+
+ if (array.Rank != 1)
+ {
+ throw new ArgumentException("Only single dimensional arrays are supported for the requested action.", nameof(array));
+ }
+
+ if (array.GetLowerBound(0) != 0)
+ {
+ throw new ArgumentException("The lower bound of target array must be zero.", nameof(array));
+ }
+
+ ArgumentOutOfRangeException.ThrowIfNegative(index);
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(index, array.Length);
+
+ if (array.Length - index < sourceCount)
+ {
+ throw new ArgumentException("Destination array is not long enough to copy all the items in the collection. Check array index and length.");
+ }
+ }
+
+ internal static void CopyTo(ICollection collection, Array array, int index)
+ {
+ ValidateCopyToArguments(collection.Count, array, index);
+
+ if (collection is ICollection nonGenericCollection)
+ {
+ // Easy out if the ICollection implements the non-generic ICollection
+ nonGenericCollection.CopyTo(array, index);
+ }
+ else if (array is T[] items)
+ {
+ collection.CopyTo(items, index);
+ }
+ else
+ {
+ // We can't cast array of value type to object[], so we don't support widening of primitive types here.
+ if (array is not object?[] objects)
+ {
+ throw new ArgumentException("Target array type is not compatible with the type of items in the collection.", nameof(array));
+ }
+
+ try
+ {
+ foreach (T item in collection)
+ {
+ objects[index++] = item;
+ }
+ }
+ catch (ArrayTypeMismatchException)
+ {
+ throw new ArgumentException("Target array type is not compatible with the type of items in the collection.", nameof(array));
+ }
+ }
+ }
+ }
+}
+#endif
diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/IMongoDataAccess.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/IMongoDataAccess.cs
index ff2d348..c0f5852 100644
--- a/src/JsonApiDotNetCore.MongoDb/Repositories/IMongoDataAccess.cs
+++ b/src/JsonApiDotNetCore.MongoDb/Repositories/IMongoDataAccess.cs
@@ -1,3 +1,4 @@
+using Microsoft.EntityFrameworkCore.Metadata;
using MongoDB.Driver;
namespace JsonApiDotNetCore.MongoDb.Repositories;
@@ -7,6 +8,11 @@ namespace JsonApiDotNetCore.MongoDb.Repositories;
///
public interface IMongoDataAccess : IAsyncDisposable
{
+ ///
+ /// Provides access to the entity model, which is built at startup.
+ ///
+ IReadOnlyModel EntityModel { get; }
+
///
/// Provides access to the underlying MongoDB database, which data changes can be applied on.
///
diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs
index fcbff6d..a92db8c 100644
--- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs
+++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs
@@ -1,10 +1,14 @@
+using Microsoft.EntityFrameworkCore.Metadata;
using MongoDB.Driver;
namespace JsonApiDotNetCore.MongoDb.Repositories;
-///
+///
public sealed class MongoDataAccess : IMongoDataAccess
{
+ ///
+ public IReadOnlyModel EntityModel { get; }
+
///
public IMongoDatabase MongoDatabase { get; }
@@ -14,10 +18,12 @@ public sealed class MongoDataAccess : IMongoDataAccess
///
public string? TransactionId => ActiveSession is { IsInTransaction: true } ? ActiveSession.GetHashCode().ToString() : null;
- public MongoDataAccess(IMongoDatabase mongoDatabase)
+ public MongoDataAccess(IReadOnlyModel entityModel, IMongoDatabase mongoDatabase)
{
- ArgumentGuard.NotNull(mongoDatabase);
+ ArgumentNullException.ThrowIfNull(entityModel);
+ ArgumentNullException.ThrowIfNull(mongoDatabase);
+ EntityModel = entityModel;
MongoDatabase = mongoDatabase;
}
diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoModel.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoModel.cs
deleted file mode 100644
index 336a098..0000000
--- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoModel.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System.Reflection;
-using JsonApiDotNetCore.Configuration;
-using Microsoft.EntityFrameworkCore.Metadata;
-using MongoDB.Bson.Serialization.Attributes;
-
-namespace JsonApiDotNetCore.MongoDb.Repositories;
-
-internal sealed class MongoModel : RuntimeModel
-{
- public MongoModel(IResourceGraph resourceGraph)
- {
- ArgumentGuard.NotNull(resourceGraph);
-
- foreach (ResourceType resourceType in resourceGraph.GetResourceTypes())
- {
- RuntimeEntityType entityType = AddEntityType(resourceType.ClrType.FullName!, resourceType.ClrType);
- SetEntityProperties(entityType, resourceType);
- }
- }
-
- private static void SetEntityProperties(RuntimeEntityType entityType, ResourceType resourceType)
- {
- foreach (PropertyInfo property in resourceType.ClrType.GetProperties().Where(property => !IsIgnored(property)))
- {
- entityType.AddProperty(property.Name, property.PropertyType, property);
- }
- }
-
- private static bool IsIgnored(PropertyInfo property)
- {
- return property.GetCustomAttribute() != null;
- }
-}
diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs
index 58081c6..c1deeed 100644
--- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs
+++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs
@@ -10,9 +10,9 @@ internal sealed class MongoQueryExpressionValidator : QueryExpressionRewriter 0;
if (hasIncludes || HasSparseRelationshipSets(layer.Selection))
{
@@ -50,9 +50,9 @@ private void ValidateExpression(QueryExpression? expression)
}
}
- public override QueryExpression? VisitResourceFieldChain(ResourceFieldChainExpression expression, object? argument)
+ public override QueryExpression VisitResourceFieldChain(ResourceFieldChainExpression expression, object? argument)
{
- if (expression.Fields.Count > 1 || expression.Fields.First() is RelationshipAttribute)
+ if (expression.Fields.Count > 1 || expression.Fields[0] is RelationshipAttribute)
{
throw new UnsupportedRelationshipException();
}
@@ -62,7 +62,7 @@ private void ValidateExpression(QueryExpression? expression)
public override QueryExpression? VisitComparison(ComparisonExpression expression, object? argument)
{
- if (expression.Left is ResourceFieldChainExpression && expression.Right is ResourceFieldChainExpression)
+ if (expression is { Left: ResourceFieldChainExpression, Right: ResourceFieldChainExpression })
{
throw new AttributeComparisonInFilterNotSupportedException();
}
diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs
index 6bd64e3..3a0edfc 100644
--- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs
+++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs
@@ -1,3 +1,5 @@
+using System.Data;
+using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using JetBrains.Annotations;
using JsonApiDotNetCore.Configuration;
@@ -7,7 +9,7 @@
using JsonApiDotNetCore.MongoDb.Resources;
using JsonApiDotNetCore.Queries;
using JsonApiDotNetCore.Queries.Expressions;
-using JsonApiDotNetCore.Queries.Internal.QueryableBuilding;
+using JsonApiDotNetCore.Queries.QueryableBuilding;
using JsonApiDotNetCore.Repositories;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
@@ -19,6 +21,12 @@ namespace JsonApiDotNetCore.MongoDb.Repositories;
///
/// Implements the foundational Repository layer in the JsonApiDotNetCore architecture that uses MongoDB.
///
+///
+/// The resource type.
+///
+///
+/// The resource identifier type.
+///
[PublicAPI]
public class MongoRepository : IResourceRepository, IRepositorySupportsTransaction
where TResource : class, IIdentifiable
@@ -27,8 +35,9 @@ public class MongoRepository : IResourceRepository _constraintProviders;
+ private readonly IQueryConstraintProvider[] _constraintProviders;
private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor;
+ private readonly IQueryableBuilder _queryableBuilder;
protected virtual IMongoCollection Collection => _mongoDataAccess.MongoDatabase.GetCollection(typeof(TResource).Name);
@@ -36,21 +45,23 @@ public class MongoRepository : IResourceRepository _mongoDataAccess.TransactionId;
public MongoRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceGraph resourceGraph, IResourceFactory resourceFactory,
- IEnumerable constraintProviders, IResourceDefinitionAccessor resourceDefinitionAccessor)
+ IEnumerable constraintProviders, IResourceDefinitionAccessor resourceDefinitionAccessor, IQueryableBuilder queryableBuilder)
{
- ArgumentGuard.NotNull(mongoDataAccess);
- ArgumentGuard.NotNull(targetedFields);
- ArgumentGuard.NotNull(resourceGraph);
- ArgumentGuard.NotNull(resourceFactory);
- ArgumentGuard.NotNull(constraintProviders);
- ArgumentGuard.NotNull(resourceDefinitionAccessor);
+ ArgumentNullException.ThrowIfNull(mongoDataAccess);
+ ArgumentNullException.ThrowIfNull(targetedFields);
+ ArgumentNullException.ThrowIfNull(resourceGraph);
+ ArgumentNullException.ThrowIfNull(resourceFactory);
+ ArgumentNullException.ThrowIfNull(constraintProviders);
+ ArgumentNullException.ThrowIfNull(resourceDefinitionAccessor);
+ ArgumentNullException.ThrowIfNull(queryableBuilder);
_mongoDataAccess = mongoDataAccess;
_targetedFields = targetedFields;
_resourceGraph = resourceGraph;
_resourceFactory = resourceFactory;
- _constraintProviders = constraintProviders;
+ _constraintProviders = constraintProviders as IQueryConstraintProvider[] ?? constraintProviders.ToArray();
_resourceDefinitionAccessor = resourceDefinitionAccessor;
+ _queryableBuilder = queryableBuilder;
if (!typeof(TResource).IsAssignableTo(typeof(IMongoIdentifiable)))
{
@@ -61,10 +72,11 @@ public MongoRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targete
///
public virtual async Task> GetAsync(QueryLayer queryLayer, CancellationToken cancellationToken)
{
- ArgumentGuard.NotNull(queryLayer);
+ ArgumentNullException.ThrowIfNull(queryLayer);
- IMongoQueryable query = ApplyQueryLayer(queryLayer);
- return await query.ToListAsync(cancellationToken);
+ IQueryable query = ApplyQueryLayer(queryLayer);
+ List? resources = await query.ToListAsync(cancellationToken);
+ return resources.AsReadOnly();
}
///
@@ -77,15 +89,15 @@ public virtual Task CountAsync(FilterExpression? topFilter, CancellationTok
Filter = topFilter
};
- IMongoQueryable query = ApplyQueryLayer(layer);
+ IQueryable query = ApplyQueryLayer(layer);
return query.CountAsync(cancellationToken);
}
#pragma warning disable AV1130 // Return type in method signature should be an interface to an unchangeable collection
- protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer queryLayer)
+ protected virtual IQueryable ApplyQueryLayer(QueryLayer queryLayer)
#pragma warning restore AV1130 // Return type in method signature should be an interface to an unchangeable collection
{
- ArgumentGuard.NotNull(queryLayer);
+ ArgumentNullException.ThrowIfNull(queryLayer);
var queryExpressionValidator = new MongoQueryExpressionValidator();
queryExpressionValidator.Validate(queryLayer);
@@ -95,7 +107,7 @@ protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer queryLay
IQueryable source = GetAll();
// @formatter:wrap_chained_method_calls chop_always
- // @formatter:keep_existing_linebreaks true
+ // @formatter:wrap_before_first_method_call true
QueryableHandlerExpression[] queryableHandlers = _constraintProviders
.SelectMany(provider => provider.GetConstraints())
@@ -104,7 +116,7 @@ protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer queryLay
.OfType()
.ToArray();
- // @formatter:keep_existing_linebreaks restore
+ // @formatter:wrap_before_first_method_call restore
// @formatter:wrap_chained_method_calls restore
foreach (QueryableHandlerExpression queryableHandler in queryableHandlers)
@@ -112,13 +124,10 @@ protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer queryLay
source = queryableHandler.Apply(source);
}
- var nameFactory = new LambdaParameterNameFactory();
+ var context = QueryableBuilderContext.CreateRoot(source, typeof(Queryable), _mongoDataAccess.EntityModel, null);
+ Expression expression = _queryableBuilder.ApplyQuery(queryLayer, context);
- var builder = new QueryableBuilder(source.Expression, source.ElementType, typeof(Queryable), nameFactory, _resourceFactory,
- new MongoModel(_resourceGraph));
-
- Expression expression = builder.ApplyQuery(queryLayer);
- return (IMongoQueryable)source.Provider.CreateQuery(expression);
+ return source.Provider.CreateQuery(expression);
}
protected virtual IQueryable GetAll()
@@ -131,17 +140,16 @@ private void AssertNoRelationshipsInSparseFieldSets()
ResourceType resourceType = _resourceGraph.GetResourceType();
// @formatter:wrap_chained_method_calls chop_always
- // @formatter:keep_existing_linebreaks true
+ // @formatter:wrap_before_first_method_call true
bool hasRelationshipSelectors = _constraintProviders
.SelectMany(provider => provider.GetConstraints())
.Select(expressionInScope => expressionInScope.Expression)
.OfType()
- .Any(fieldTable =>
- fieldTable.Table.Keys.Any(targetResourceType => !resourceType.Equals(targetResourceType)) ||
+ .Any(fieldTable => fieldTable.Table.Keys.Any(targetResourceType => !resourceType.Equals(targetResourceType)) ||
fieldTable.Table.Values.Any(fieldSet => fieldSet.Fields.Any(field => field is RelationshipAttribute)));
- // @formatter:keep_existing_linebreaks restore
+ // @formatter:wrap_before_first_method_call restore
// @formatter:wrap_chained_method_calls restore
if (hasRelationshipSelectors)
@@ -151,8 +159,10 @@ private void AssertNoRelationshipsInSparseFieldSets()
}
///
- public virtual Task GetForCreateAsync(Type resourceClrType, TId id, CancellationToken cancellationToken)
+ public virtual Task GetForCreateAsync(Type resourceClrType, [DisallowNull] TId id, CancellationToken cancellationToken)
{
+ ArgumentNullException.ThrowIfNull(resourceClrType);
+
var resource = (TResource)_resourceFactory.CreateInstance(resourceClrType);
resource.Id = id;
@@ -162,8 +172,8 @@ public virtual Task GetForCreateAsync(Type resourceClrType, TId id, C
///
public virtual async Task CreateAsync(TResource resourceFromRequest, TResource resourceForDatabase, CancellationToken cancellationToken)
{
- ArgumentGuard.NotNull(resourceFromRequest);
- ArgumentGuard.NotNull(resourceForDatabase);
+ ArgumentNullException.ThrowIfNull(resourceFromRequest);
+ ArgumentNullException.ThrowIfNull(resourceForDatabase);
AssertNoRelationshipsAreTargeted();
@@ -174,19 +184,17 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r
await _resourceDefinitionAccessor.OnWritingAsync(resourceForDatabase, WriteOperationKind.CreateResource, cancellationToken);
- await SaveChangesAsync(async () =>
- {
- await (_mongoDataAccess.ActiveSession != null
+ await SaveChangesAsync(
+ async () => await (_mongoDataAccess.ActiveSession != null
? Collection.InsertOneAsync(_mongoDataAccess.ActiveSession, resourceForDatabase, cancellationToken: cancellationToken)
- : Collection.InsertOneAsync(resourceForDatabase, cancellationToken: cancellationToken));
- }, cancellationToken);
+ : Collection.InsertOneAsync(resourceForDatabase, cancellationToken: cancellationToken)), cancellationToken);
await _resourceDefinitionAccessor.OnWriteSucceededAsync(resourceForDatabase, WriteOperationKind.CreateResource, cancellationToken);
}
private void AssertNoRelationshipsAreTargeted()
{
- if (_targetedFields.Relationships.Any())
+ if (_targetedFields.Relationships.Count > 0)
{
throw new UnsupportedRelationshipException();
}
@@ -195,7 +203,7 @@ private void AssertNoRelationshipsAreTargeted()
///
public virtual async Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken)
{
- ArgumentGuard.NotNull(queryLayer);
+ ArgumentNullException.ThrowIfNull(queryLayer);
IReadOnlyCollection resources = await GetAsync(queryLayer, cancellationToken);
return resources.FirstOrDefault();
@@ -204,8 +212,8 @@ private void AssertNoRelationshipsAreTargeted()
///
public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase, CancellationToken cancellationToken)
{
- ArgumentGuard.NotNull(resourceFromRequest);
- ArgumentGuard.NotNull(resourceFromDatabase);
+ ArgumentNullException.ThrowIfNull(resourceFromRequest);
+ ArgumentNullException.ThrowIfNull(resourceFromDatabase);
AssertNoRelationshipsAreTargeted();
@@ -218,18 +226,16 @@ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource r
FilterDefinition filter = Builders.Filter.Eq(resource => resource.Id, resourceFromDatabase.Id);
- await SaveChangesAsync(async () =>
- {
- await (_mongoDataAccess.ActiveSession != null
+ await SaveChangesAsync(
+ () => _mongoDataAccess.ActiveSession != null
? Collection.ReplaceOneAsync(_mongoDataAccess.ActiveSession, filter, resourceFromDatabase, cancellationToken: cancellationToken)
- : Collection.ReplaceOneAsync(filter, resourceFromDatabase, cancellationToken: cancellationToken));
- }, cancellationToken);
+ : Collection.ReplaceOneAsync(filter, resourceFromDatabase, cancellationToken: cancellationToken), cancellationToken);
await _resourceDefinitionAccessor.OnWriteSucceededAsync(resourceFromDatabase, WriteOperationKind.UpdateResource, cancellationToken);
}
///
- public virtual async Task DeleteAsync(TResource? resourceFromDatabase, TId id, CancellationToken cancellationToken)
+ public virtual async Task DeleteAsync(TResource? resourceFromDatabase, [DisallowNull] TId id, CancellationToken cancellationToken)
{
TResource placeholderResource = resourceFromDatabase ?? _resourceFactory.CreateInstance();
placeholderResource.Id = id;
@@ -239,19 +245,19 @@ public virtual async Task DeleteAsync(TResource? resourceFromDatabase, TId id, C
FilterDefinition filter = Builders.Filter.Eq(resource => resource.Id, id);
DeleteResult result = await SaveChangesAsync(
- async () => _mongoDataAccess.ActiveSession != null
- ? await Collection.DeleteOneAsync(_mongoDataAccess.ActiveSession, filter, cancellationToken: cancellationToken)
- : await Collection.DeleteOneAsync(filter, cancellationToken), cancellationToken);
+ () => _mongoDataAccess.ActiveSession != null
+ ? Collection.DeleteOneAsync(_mongoDataAccess.ActiveSession, filter, cancellationToken: cancellationToken)
+ : Collection.DeleteOneAsync(filter, cancellationToken), cancellationToken);
if (!result.IsAcknowledged)
{
throw new DataStoreUpdateException(
- new Exception($"Failed to delete document with id '{id}', because the operation was not acknowledged by MongoDB."));
+ new DataException($"Failed to delete document with id '{id}', because the operation was not acknowledged by MongoDB."));
}
if (result.DeletedCount == 0)
{
- throw new DataStoreUpdateException(new Exception($"Failed to delete document with id '{id}', because it does not exist."));
+ throw new DataStoreUpdateException(new DataException($"Failed to delete document with id '{id}', because it does not exist."));
}
await _resourceDefinitionAccessor.OnWriteSucceededAsync(placeholderResource, WriteOperationKind.DeleteResource, cancellationToken);
@@ -264,7 +270,7 @@ public virtual Task SetRelationshipAsync(TResource leftResource, object? rightVa
}
///
- public virtual Task AddToToManyRelationshipAsync(TResource? leftResource, TId leftId, ISet rightResourceIds,
+ public virtual Task AddToToManyRelationshipAsync(TResource? leftResource, [DisallowNull] TId leftId, ISet rightResourceIds,
CancellationToken cancellationToken)
{
throw new UnsupportedRelationshipException();
@@ -278,6 +284,8 @@ public virtual Task RemoveFromToManyRelationshipAsync(TResource leftResource, IS
protected virtual async Task SaveChangesAsync(Func asyncSaveAction, CancellationToken cancellationToken)
{
+ ArgumentNullException.ThrowIfNull(asyncSaveAction);
+
_ = await SaveChangesAsync