diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 575923d62c..435201037d 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"jetbrains.resharper.globaltools": {
- "version": "2023.3.2",
+ "version": "2024.1.4",
"commands": [
"jb"
]
@@ -15,13 +15,13 @@
]
},
"dotnet-reportgenerator-globaltool": {
- "version": "5.2.0",
+ "version": "5.3.6",
"commands": [
"reportgenerator"
]
},
"docfx": {
- "version": "2.74.0",
+ "version": "2.76.0",
"commands": [
"docfx"
]
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 4205b1ceec..e7d3ce2494 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -39,7 +39,7 @@ When you are creating an enhancement suggestion, please include as many details
- **Use a clear and descriptive title** for the issue to identify the suggestion.
- **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
-- **Provide specific examples to demonstrate the usage.** Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://docs.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks).
+- **Provide specific examples to demonstrate the usage.** Include copy/pasteable snippets which you use in those examples as [Markdown code blocks](https://docs.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks).
- **Describe the current behavior and explain which behavior you expected to see instead** and why.
- **Explain why this enhancement would be useful** to most users and isn't something that can or should be implemented in your API project directly.
- **Verify that your enhancement does not conflict** with the [JSON:API specification](https://jsonapi.org/).
@@ -56,7 +56,7 @@ Please follow these steps to have your contribution considered by the maintainer
- Follow all instructions in the template. Don't forget to add tests and update documentation.
- After you submit your pull request, verify that all status checks are passing. In release builds, all compiler warnings are treated as errors, so you should address them before push.
-We use [CSharpGuidelines](https://csharpcodingguidelines.com/) as our coding standard (with a few minor exceptions). Coding style is validated during PR build, where we inject an extra settings layer that promotes various suggestions to warning level. This ensures a high-quality codebase without interfering too much when editing code.
+We use [CSharpGuidelines](https://csharpcodingguidelines.com/) as our coding standard. Coding style is validated during PR build, where we inject an extra settings layer that promotes various IDE suggestions to warning level. This ensures a high-quality codebase without interfering too much while editing code.
You can run the following [PowerShell scripts](https://github.com/PowerShell/PowerShell/releases) locally:
- `pwsh ./inspectcode.ps1`: Scans the code for style violations and opens the result in your web browser.
- `pwsh ./cleanupcode.ps1 [branch-name-or-commit-hash]`: Reformats the codebase to match with our configured style, optionally only changed files since the specified branch (usually master).
@@ -86,13 +86,39 @@ public sealed class AppDbContext : DbContext
}
```
+### Pull request workflow
+
+Please follow the steps and guidelines below for a smooth experience.
+
+Authors:
+- When opening a new pull request, create it in **Draft** mode.
+- After you've completed the work *and* all checks are green, click the **Ready for review** button.
+ - If you have permissions to do so, ask a team member for review.
+- Once the review has started, don't force-push anymore.
+- When you've addressed feedback from a conversation, mark it with a thumbs-up or add a some text.
+- Don't close a conversation you didn't start. The creator closes it after verifying the concern has been addressed.
+- Apply suggestions in a batch, instead of individual commits (to minimize email notifications).
+- Re-request review when you're ready for another round.
+- If you want to clean up your commits before merge, let the reviewer know in time. This is optional.
+
+Reviewers:
+- If you're unable to review within a few days, let the author know what to expect.
+- Use **Start a review** instead of **Add single comment** (to minimize email notifications).
+- Consider to use suggestions (the ± button).
+- Don't close a conversation you didn't start. Close the ones you opened after verifying the concern has been addressed.
+- Once approved, use a merge commit only if all commits are clean. Otherwise, squash them into a single commit.
+ A commit is considered clean when:
+ - It is properly documented and covers all aspects of an isolated change (code, style, tests, docs).
+ - Checking out the commit results in a green build.
+ - Having this commit show up in the history is helpful (and can potentially be reverted).
+
## Creating a release (for maintainers)
- Verify documentation is up-to-date
-- Bump the package version in Directory.Build.props
+- Bump the package version in `Directory.Build.props`
- Create a GitHub release
-- Update https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb to consume the new version and release
-- Create a new branch in https://github.com/json-api-dotnet/MigrationGuide and update README.md in master
+- Update [JsonApiDotNetCore.MongoDb](https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb) to consume the new version and release
+- Create a new branch in [MigrationGuide](https://github.com/json-api-dotnet/MigrationGuide) and update README.md in master, if major version change
## Backporting and hotfixes (for maintainers)
@@ -101,7 +127,7 @@ public sealed class AppDbContext : DbContext
git checkout tags/v2.5.1 -b release/2.5.2
```
- Cherrypick the merge commit: `git cherry-pick {git commit SHA}`
-- Bump the package version in Directory.Build.props
+- Bump the package version in `Directory.Build.props`
- Make any other compatibility, documentation, or tooling related changes
- Push the branch to origin and verify the build
- Once the build is verified, create a GitHub release, tagging the release branch
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index eb3aab63b2..da0993c185 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -38,7 +38,7 @@ jobs:
- name: Tune GitHub-hosted runner network
uses: smorimoto/tune-github-hosted-runner-network@v1
- name: Setup PostgreSQL
- uses: ikalnytskyi/action-setup-postgres@v5
+ uses: ikalnytskyi/action-setup-postgres@v6
with:
username: postgres
password: postgres
@@ -48,36 +48,6 @@ jobs:
dotnet-version: |
6.0.x
8.0.x
- - name: Setup PowerShell (Ubuntu)
- if: matrix.os == 'ubuntu-latest'
- run: |
- dotnet tool install --global PowerShell
- - name: Find latest PowerShell version (Windows)
- if: matrix.os == 'windows-latest'
- shell: pwsh
- run: |
- $packageName = "powershell"
- $outputText = dotnet tool search $packageName --take 1
- $outputLine = ("" + $outputText)
- $indexOfVersionLine = $outputLine.IndexOf($packageName)
- $latestVersion = $outputLine.substring($indexOfVersionLine + $packageName.length).trim().split(" ")[0].trim()
-
- Write-Output "Found PowerShell version: $latestVersion"
- Write-Output "POWERSHELL_LATEST_VERSION=$latestVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- - name: Setup PowerShell (Windows)
- if: matrix.os == 'windows-latest'
- shell: cmd
- run: |
- set DOWNLOAD_LINK=https://github.com/PowerShell/PowerShell/releases/download/v%POWERSHELL_LATEST_VERSION%/PowerShell-%POWERSHELL_LATEST_VERSION%-win-x64.msi
- set OUTPUT_PATH=%RUNNER_TEMP%\PowerShell-%POWERSHELL_LATEST_VERSION%-win-x64.msi
- echo Downloading from: %DOWNLOAD_LINK% to: %OUTPUT_PATH%
- curl --location --output %OUTPUT_PATH% %DOWNLOAD_LINK%
- msiexec.exe /package %OUTPUT_PATH% /quiet USE_MU=1 ENABLE_MU=1 ADD_PATH=1 DISABLE_TELEMETRY=1
- - name: Setup PowerShell (macOS)
- if: matrix.os == 'macos-latest'
- run: |
- /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
- brew install --cask powershell
- name: Show installed versions
shell: pwsh
run: |
@@ -138,7 +108,9 @@ jobs:
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'
- uses: codecov/codecov-action@v3
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+ uses: codecov/codecov-action@v4
with:
fail_ci_if_error: true
verbose: true
@@ -204,7 +176,7 @@ jobs:
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.sln --build --dotnetcoresdk=$(dotnet --version) --output="$inspectCodeOutputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --properties:ContinuousIntegrationBuild=false --properties:RunAnalyzers=false --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal
+ dotnet jb inspectcode JsonApiDotNetCore.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: |
@@ -300,7 +272,7 @@ jobs:
dotnet nuget push "$env:GITHUB_WORKSPACE/packages/*.nupkg" --api-key "$env:GITHUB_TOKEN" --source 'github'
- name: Publish documentation
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
- uses: peaceiris/actions-gh-pages@v3
+ uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
diff --git a/.github/workflows/deps-review.yml b/.github/workflows/deps-review.yml
index b9945082d5..b9d6d20fff 100644
--- a/.github/workflows/deps-review.yml
+++ b/.github/workflows/deps-review.yml
@@ -11,4 +11,4 @@ jobs:
- name: 'Checkout Repository'
uses: actions/checkout@v4
- name: 'Dependency Review'
- uses: actions/dependency-review-action@v3
+ uses: actions/dependency-review-action@v4
diff --git a/.github/workflows/qodana.yml b/.github/workflows/qodana.yml
index f1a64da824..8ce0acd5db 100644
--- a/.github/workflows/qodana.yml
+++ b/.github/workflows/qodana.yml
@@ -22,7 +22,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit
fetch-depth: 0 # a full history is required for pull request analysis
- name: 'Qodana Scan'
- uses: JetBrains/qodana-action@v2023.3
+ uses: JetBrains/qodana-action@v2024.1
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
- name: Upload results to artifacts on failure
diff --git a/.gitignore b/.gitignore
index 85bd0f1080..c1757fc159 100644
--- a/.gitignore
+++ b/.gitignore
@@ -423,3 +423,6 @@ FodyWeavers.xsd
**/.idea/**/httpRequests/
**/.idea/**/dataSources/
!**/.idea/**/codeStyles/*
+
+# Workaround for https://github.com/microsoft/kiota/issues/4228
+kiota-lock.json
diff --git a/Directory.Build.props b/Directory.Build.props
index 0c2b51e13b..860217f52e 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -27,6 +27,6 @@
-
-
- The goal of this library is to simplify the development of APIs that leverage the full range of features provided by the JSON:API specification. + The goal of this library is to simplify the development of APIs that leverage the full range of features + provided by the JSON:API specification. You just need to focus on defining the resources and implementing your custom business logic.
We strive to eliminate as much boilerplate as possible by offering out-of-the-box features such as sorting, filtering and pagination.
The following features are supported, from HTTP all the way down to the database
Perform compound filtering using the filter
query string parameter
Order resources on one or multiple attributes using the sort
query string parameter
Leverage the benefits of paginated resources with the page
query string parameter
Side-load related resources of nested relationships using the include
query string parameter
Configure permissions, such as view/create/change/sort/filter of attributes and relationships
+Configure permissions, such as viewing, creating, modifying, sorting and filtering of attributes and relationships
Validate incoming requests using built-in ASP.NET Core ModelState
validation, which works seamlessly with partial updates
Validate incoming requests using built-in ASP.NET Model Validation, which works seamlessly with partial updates
Use various extensibility points to intercept and run custom code, besides just model annotations
#nullable enable
@@ -179,16 +211,16 @@ Resource
-
+
Request
GET /articles?filter=contains(summary,'web')&sort=-lastModifiedAt&fields[articles]=title,summary&include=author HTTP/1.1
-
+
-
+
Response
{
@@ -259,15 +291,15 @@ Response
Sponsors
-
-
+
+
-
-
+
+
diff --git a/docs/request-examples/index.md b/docs/request-examples/index.md
index c34b3d713a..614aa4814f 100644
--- a/docs/request-examples/index.md
+++ b/docs/request-examples/index.md
@@ -1,3 +1,7 @@
+---
+_disableToc: true
+---
+
# Example requests
These requests have been generated against the "GettingStarted" application and are updated on every deployment.
diff --git a/docs/request-examples/toc.md b/docs/request-examples/toc.md
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/docs/usage/extensibility/controllers.md b/docs/usage/extensibility/controllers.md
index 7e54d3fb9c..68e1d86ea3 100644
--- a/docs/usage/extensibility/controllers.md
+++ b/docs/usage/extensibility/controllers.md
@@ -2,6 +2,8 @@
To expose API endpoints, ASP.NET controllers need to be defined.
+## Auto-generated controllers
+
_since v5_
Controllers are auto-generated (using [source generators](https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview)) when you add `[Resource]` on your model class:
@@ -14,7 +16,12 @@ public class Article : Identifiable
}
```
-## Resource Access Control
+> [!NOTE]
+> Auto-generated controllers are convenient to get started, but may not work as expected with certain customizations.
+> For example, when model classes are defined in a separate project, the controllers are generated in that project as well, which is probably not what you want.
+> In such cases, it's perfectly fine to use [explicit controllers](#explicit-controllers) instead.
+
+### Resource Access Control
It is often desirable to limit which endpoints are exposed on your controller.
A subset can be specified too:
@@ -52,7 +59,7 @@ DELETE http://localhost:14140/articles/1 HTTP/1.1
}
```
-## Augmenting controllers
+### Augmenting controllers
Auto-generated controllers can easily be augmented because they are partial classes. For example:
@@ -91,9 +98,9 @@ partial class ArticlesController
In case you don't want to use auto-generated controllers and define them yourself (see below), remove
`[Resource]` from your models or use `[Resource(GenerateControllerEndpoints = JsonApiEndpoints.None)]`.
-## Earlier versions
+## Explicit controllers
-In earlier versions of JsonApiDotNetCore, you needed to create controllers that inherit from `JsonApiController`. For example:
+To define your own controller class, inherit from `JsonApiController`. For example:
```c#
public class ArticlesController : JsonApiController
diff --git a/docs/usage/writing/bulk-batch-operations.md b/docs/usage/writing/bulk-batch-operations.md
index 1ac35fd3fc..5756755b51 100644
--- a/docs/usage/writing/bulk-batch-operations.md
+++ b/docs/usage/writing/bulk-batch-operations.md
@@ -19,13 +19,24 @@ 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)
+ IJsonApiRequest request, ITargetedFields targetedFields,
+ IAtomicOperationFilter operationFilter)
+ : base(options, resourceGraph, loggerFactory, processor, request, targetedFields,
+ operationFilter)
{
}
}
```
+> [!IMPORTANT]
+> Since v5.6.0, the set of exposed operations is based on
+> [`GenerateControllerEndpoints` usage](~/usage/extensibility/controllers.md#resource-access-control).
+> Earlier versions always exposed all operations for all resource types.
+> If you're using [explicit controllers](~/usage/extensibility/controllers.md#explicit-controllers),
+> register and implement your own
+> [`IAtomicOperationFilter`](~/api/JsonApiDotNetCore.AtomicOperations.IAtomicOperationFilter.yml)
+> to indicate which operations to expose.
+
You'll need to send the next Content-Type in a POST request for operations:
```
diff --git a/docs/usage/writing/creating.md b/docs/usage/writing/creating.md
index ba0a21d52b..8cc0c03e49 100644
--- a/docs/usage/writing/creating.md
+++ b/docs/usage/writing/creating.md
@@ -16,8 +16,8 @@ POST /articles HTTP/1.1
}
```
-When using client-generated IDs and only attributes from the request have changed, the server returns `204 No Content`.
-Otherwise, the server returns `200 OK`, along with the updated resource and its newly assigned ID.
+When using client-generated IDs and all attributes of the created resource are the same as in the request, the server
+returns `204 No Content`. Otherwise, the server returns `201 Created`, along with the stored attributes and its newly assigned ID.
In both cases, a `Location` header is returned that contains the URL to the new resource.
diff --git a/docs/usage/writing/updating.md b/docs/usage/writing/updating.md
index ea27e1a220..30e1b4fa7d 100644
--- a/docs/usage/writing/updating.md
+++ b/docs/usage/writing/updating.md
@@ -5,7 +5,7 @@
To modify the attributes of a single resource, send a PATCH request. The next example changes the article caption:
```http
-PATCH /articles HTTP/1.1
+PATCH /articles/1 HTTP/1.1
{
"data": {
diff --git a/inspectcode.ps1 b/inspectcode.ps1
index aa816da5e9..21e96eac67 100644
--- a/inspectcode.ps1
+++ b/inspectcode.ps1
@@ -10,7 +10,7 @@ if ($LastExitCode -ne 0) {
$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.sln --dotnetcoresdk=$(dotnet --version) --build --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --properties:RunAnalyzers=false --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal
+dotnet jb inspectcode JsonApiDotNetCore.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"
diff --git a/logo.png b/logo.png
deleted file mode 100644
index 78f1acd521..0000000000
Binary files a/logo.png and /dev/null differ
diff --git a/package-icon.png b/package-icon.png
new file mode 100644
index 0000000000..f95eb770e8
Binary files /dev/null and b/package-icon.png differ
diff --git a/package-versions.props b/package-versions.props
index 07bbfed960..9ba0b34bbb 100644
--- a/package-versions.props
+++ b/package-versions.props
@@ -7,17 +7,16 @@
0.13.*
- 35.2.*
- 4.8.*
+ 35.5.*
+ 4.10.*
6.0.*
2.1.*
6.12.*
2.3.*
2.0.*
8.0.*
- 17.8.*
- 2.6.*
- 2.5.*
+ 17.10.*
+ 2.8.*
@@ -26,7 +25,7 @@
8.0.*
- 8.0.*-*
+ 8.0.*
$(AspNetCoreVersion)
diff --git a/src/Examples/DapperExample/Controllers/OperationsController.cs b/src/Examples/DapperExample/Controllers/OperationsController.cs
index 6fe0eedd1d..2b9daf492f 100644
--- a/src/Examples/DapperExample/Controllers/OperationsController.cs
+++ b/src/Examples/DapperExample/Controllers/OperationsController.cs
@@ -8,4 +8,5 @@ namespace DapperExample.Controllers;
public sealed class OperationsController(
IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request,
- ITargetedFields targetedFields) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields);
+ ITargetedFields targetedFields, IAtomicOperationFilter operationFilter) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor,
+ request, targetedFields, operationFilter);
diff --git a/src/Examples/DapperExample/Repositories/DapperRepository.cs b/src/Examples/DapperExample/Repositories/DapperRepository.cs
index c263ad7767..0c54b34353 100644
--- a/src/Examples/DapperExample/Repositories/DapperRepository.cs
+++ b/src/Examples/DapperExample/Repositories/DapperRepository.cs
@@ -1,4 +1,5 @@
using System.Data.Common;
+using System.Diagnostics.CodeAnalysis;
using Dapper;
using DapperExample.AtomicOperations;
using DapperExample.TranslationToSql;
@@ -161,7 +162,7 @@ public async Task> GetAsync(QueryLayer queryLayer
}
///
- public Task CountAsync(FilterExpression? filter, CancellationToken cancellationToken)
+ public async Task CountAsync(FilterExpression? filter, CancellationToken cancellationToken)
{
var queryLayer = new QueryLayer(ResourceType)
{
@@ -173,11 +174,11 @@ public Task CountAsync(FilterExpression? filter, CancellationToken cancella
CommandDefinition sqlCommand = _dapperFacade.GetSqlCommand(selectNode, cancellationToken);
LogSqlCommand(sqlCommand);
- return ExecuteQueryAsync(connection => connection.ExecuteScalarAsync(sqlCommand), cancellationToken);
+ return await ExecuteQueryAsync(async connection => await connection.ExecuteScalarAsync(sqlCommand), cancellationToken);
}
///
- public Task GetForCreateAsync(Type resourceClrType, TId id, CancellationToken cancellationToken)
+ public Task GetForCreateAsync(Type resourceClrType, [DisallowNull] TId id, CancellationToken cancellationToken)
{
ArgumentGuard.NotNull(resourceClrType);
@@ -355,7 +356,7 @@ await ExecuteInTransactionAsync(async transaction =>
}
///
- public async Task DeleteAsync(TResource? resourceFromDatabase, TId id, CancellationToken cancellationToken)
+ public async Task DeleteAsync(TResource? resourceFromDatabase, [DisallowNull] TId id, CancellationToken cancellationToken)
{
TResource placeholderResource = resourceFromDatabase ?? _resourceFactory.CreateInstance();
placeholderResource.Id = id;
@@ -451,7 +452,7 @@ await ExecuteInTransactionAsync(async transaction =>
}
///
- public async Task AddToToManyRelationshipAsync(TResource? leftResource, TId leftId, ISet rightResourceIds,
+ public async Task AddToToManyRelationshipAsync(TResource? leftResource, [DisallowNull] TId leftId, ISet rightResourceIds,
CancellationToken cancellationToken)
{
ArgumentGuard.NotNull(rightResourceIds);
diff --git a/src/Examples/DapperExample/TranslationToSql/DataModel/FromEntitiesDataModelService.cs b/src/Examples/DapperExample/TranslationToSql/DataModel/FromEntitiesDataModelService.cs
index b0ba38f3a5..711ad8517c 100644
--- a/src/Examples/DapperExample/TranslationToSql/DataModel/FromEntitiesDataModelService.cs
+++ b/src/Examples/DapperExample/TranslationToSql/DataModel/FromEntitiesDataModelService.cs
@@ -38,12 +38,12 @@ public void Initialize(DbContext dbContext)
Initialize();
}
- private void ScanForeignKeys(IModel entityModel)
+ private void ScanForeignKeys(IReadOnlyModel entityModel)
{
foreach (RelationshipAttribute relationship in ResourceGraph.GetResourceTypes().SelectMany(resourceType => resourceType.Relationships))
{
- IEntityType? leftEntityType = entityModel.FindEntityType(relationship.LeftType.ClrType);
- INavigation? navigation = leftEntityType?.FindNavigation(relationship.Property.Name);
+ IReadOnlyEntityType? leftEntityType = entityModel.FindEntityType(relationship.LeftType.ClrType);
+ IReadOnlyNavigation? navigation = leftEntityType?.FindNavigation(relationship.Property.Name);
if (navigation != null)
{
@@ -57,7 +57,7 @@ private void ScanForeignKeys(IModel entityModel)
}
}
- private void ScanColumnNullability(IModel entityModel)
+ private void ScanColumnNullability(IReadOnlyModel entityModel)
{
foreach (ResourceType resourceType in ResourceGraph.GetResourceTypes())
{
@@ -65,15 +65,15 @@ private void ScanColumnNullability(IModel entityModel)
}
}
- private void ScanColumnNullability(ResourceType resourceType, IModel entityModel)
+ private void ScanColumnNullability(ResourceType resourceType, IReadOnlyModel entityModel)
{
- IEntityType? entityType = entityModel.FindEntityType(resourceType.ClrType);
+ IReadOnlyEntityType? entityType = entityModel.FindEntityType(resourceType.ClrType);
if (entityType != null)
{
foreach (AttrAttribute attribute in resourceType.Attributes)
{
- IProperty? property = entityType.FindProperty(attribute.Property.Name);
+ IReadOnlyProperty? property = entityType.FindProperty(attribute.Property.Name);
if (property != null)
{
diff --git a/src/Examples/DapperExample/appsettings.json b/src/Examples/DapperExample/appsettings.json
index b4ddb2dac9..7854646e7f 100644
--- a/src/Examples/DapperExample/appsettings.json
+++ b/src/Examples/DapperExample/appsettings.json
@@ -4,9 +4,9 @@
// docker run --rm --detach --name dapper-example-postgresql-db -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:latest
// docker run --rm --detach --name dapper-example-postgresql-management --link dapper-example-postgresql-db:db -e PGADMIN_DEFAULT_EMAIL=admin@admin.com -e PGADMIN_DEFAULT_PASSWORD=postgres -p 5050:80 dpage/pgadmin4:latest
"DapperExamplePostgreSql": "Host=localhost;Database=DapperExample;User ID=postgres;Password=postgres;Include Error Detail=true",
- // docker run --rm --detach --name dapper-example-mysql-db -e MYSQL_ROOT_PASSWORD=mysql -e MYSQL_DATABASE=DapperExample -e MYSQL_USER=mysql -e MYSQL_PASSWORD=mysql -p 3306:3306 mysql:latest --default-authentication-plugin=mysql_native_password
+ // docker run --rm --detach --name dapper-example-mysql-db -e MYSQL_ROOT_PASSWORD=mysql -e MYSQL_DATABASE=DapperExample -e MYSQL_USER=mysql -e MYSQL_PASSWORD=mysql -p 3306:3306 mysql:latest
// docker run --rm --detach --name dapper-example-mysql-management --link dapper-example-mysql-db:db -p 8081:80 phpmyadmin/phpmyadmin
- "DapperExampleMySql": "Host=localhost;Database=DapperExample;User ID=mysql;Password=mysql",
+ "DapperExampleMySql": "Host=localhost;Database=DapperExample;User ID=mysql;Password=mysql;SSL Mode=None;AllowPublicKeyRetrieval=True",
// docker run --rm --detach --name dapper-example-sqlserver -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=Passw0rd!" -p 1433:1433 mcr.microsoft.com/mssql/server:2022-latest
"DapperExampleSqlServer": "Server=localhost;Database=DapperExample;User ID=sa;Password=Passw0rd!;TrustServerCertificate=true"
},
diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs
index e38b30d861..9d8d944967 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs
@@ -8,4 +8,5 @@ namespace JsonApiDotNetCoreExample.Controllers;
public sealed class OperationsController(
IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request,
- ITargetedFields targetedFields) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields);
+ ITargetedFields targetedFields, IAtomicOperationFilter operationFilter) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor,
+ request, targetedFields, operationFilter);
diff --git a/src/Examples/NoEntityFrameworkExample/Data/InMemoryModel.cs b/src/Examples/NoEntityFrameworkExample/Data/InMemoryModel.cs
deleted file mode 100644
index c81aa07b8f..0000000000
--- a/src/Examples/NoEntityFrameworkExample/Data/InMemoryModel.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System.Reflection;
-using JsonApiDotNetCore.Configuration;
-using Microsoft.EntityFrameworkCore.Metadata;
-
-namespace NoEntityFrameworkExample.Data;
-
-internal sealed class InMemoryModel : RuntimeModel
-{
- public InMemoryModel(IResourceGraph 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())
- {
- entityType.AddProperty(property.Name, property.PropertyType, property);
- }
- }
-}
diff --git a/src/Examples/NoEntityFrameworkExample/Data/ResourceGraphExtensions.cs b/src/Examples/NoEntityFrameworkExample/Data/ResourceGraphExtensions.cs
new file mode 100644
index 0000000000..ff35f0ab0d
--- /dev/null
+++ b/src/Examples/NoEntityFrameworkExample/Data/ResourceGraphExtensions.cs
@@ -0,0 +1,32 @@
+using System.Reflection;
+using JsonApiDotNetCore.Configuration;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace NoEntityFrameworkExample.Data;
+
+internal static class ResourceGraphExtensions
+{
+ public static IReadOnlyModel ToEntityModel(this IResourceGraph 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())
+ {
+ entityTypeBuilder.Property(property.PropertyType, property.Name);
+ }
+ }
+}
diff --git a/src/Examples/NoEntityFrameworkExample/NullSafeExpressionRewriter.cs b/src/Examples/NoEntityFrameworkExample/NullSafeExpressionRewriter.cs
index 67b04d1d3d..61ebb8b8b0 100644
--- a/src/Examples/NoEntityFrameworkExample/NullSafeExpressionRewriter.cs
+++ b/src/Examples/NoEntityFrameworkExample/NullSafeExpressionRewriter.cs
@@ -300,7 +300,7 @@ private static ConstantExpression CreateConstantForMemberIsNull(Type type)
if (getter != null)
{
- object? value = getter.Invoke(null, Array.Empty