From e515746f8ed7e6361d20800dabf250a0151c4bfe Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 22 Nov 2023 03:59:46 +0100 Subject: [PATCH 01/91] Increment version to 5.5.1 (used for pre-release builds from ci) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index cdd3c5d232..0c2b51e13b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -27,6 +27,6 @@ false $(MSBuildThisFileDirectory)CodingGuidelines.ruleset $(MSBuildThisFileDirectory)tests.runsettings - 5.5.0 + 5.5.1 From ded32a720d517ad541ba4636770661f31ec30537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Harrtell?= Date: Thu, 23 Nov 2023 01:14:31 +0100 Subject: [PATCH 02/91] Allow to override valueconverter on FilterParser (#1401) --- .../Queries/Parsing/ConstantValueConverter.cs | 15 +++++++++++++++ .../Queries/Parsing/FilterParser.cs | 16 ++++++++-------- 2 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 src/JsonApiDotNetCore/Queries/Parsing/ConstantValueConverter.cs diff --git a/src/JsonApiDotNetCore/Queries/Parsing/ConstantValueConverter.cs b/src/JsonApiDotNetCore/Queries/Parsing/ConstantValueConverter.cs new file mode 100644 index 0000000000..d624964ddd --- /dev/null +++ b/src/JsonApiDotNetCore/Queries/Parsing/ConstantValueConverter.cs @@ -0,0 +1,15 @@ +namespace JsonApiDotNetCore.Queries.Parsing; + +/// +/// Converts a constant value within a query string parameter to an . +/// +/// +/// The constant value to convert from. +/// +/// +/// The zero-based position of in the query string parameter value. +/// +/// +/// The converted object instance. +/// +public delegate object ConstantValueConverter(string value, int position); diff --git a/src/JsonApiDotNetCore/Queries/Parsing/FilterParser.cs b/src/JsonApiDotNetCore/Queries/Parsing/FilterParser.cs index cbd6ee4b21..19757bbd08 100644 --- a/src/JsonApiDotNetCore/Queries/Parsing/FilterParser.cs +++ b/src/JsonApiDotNetCore/Queries/Parsing/FilterParser.cs @@ -252,13 +252,13 @@ private QueryExpression ParseComparisonRightTerm(QueryExpression leftTerm) var leftAttribute = (AttrAttribute)leftLastField; - Func constantValueConverter = GetConstantValueConverterForAttribute(leftAttribute); + ConstantValueConverter constantValueConverter = GetConstantValueConverterForAttribute(leftAttribute); return ParseTypedComparisonRightTerm(leftAttribute.Property.PropertyType, constantValueConverter); } if (leftTerm is FunctionExpression leftFunction) { - Func constantValueConverter = GetConstantValueConverterForType(leftFunction.ReturnType); + ConstantValueConverter constantValueConverter = GetConstantValueConverterForType(leftFunction.ReturnType); return ParseTypedComparisonRightTerm(leftFunction.ReturnType, constantValueConverter); } @@ -266,7 +266,7 @@ private QueryExpression ParseComparisonRightTerm(QueryExpression leftTerm) $"Internal error: Expected left term to be a function or field chain, instead of '{leftTerm.GetType().Name}': '{leftTerm}'."); } - private QueryExpression ParseTypedComparisonRightTerm(Type leftType, Func constantValueConverter) + private QueryExpression ParseTypedComparisonRightTerm(Type leftType, ConstantValueConverter constantValueConverter) { bool allowNull = RuntimeTypeConverter.CanContainNull(leftType); @@ -329,7 +329,7 @@ protected virtual MatchTextExpression ParseTextMatch(string operatorName) EatSingleCharacterToken(TokenKind.Comma); - Func constantValueConverter = GetConstantValueConverterForAttribute(targetAttribute); + ConstantValueConverter constantValueConverter = GetConstantValueConverterForAttribute(targetAttribute); LiteralConstantExpression constant = ParseConstant(constantValueConverter); EatSingleCharacterToken(TokenKind.CloseParen); @@ -352,7 +352,7 @@ protected virtual AnyExpression ParseAny() ImmutableHashSet.Builder constantsBuilder = ImmutableHashSet.CreateBuilder(); - Func constantValueConverter = GetConstantValueConverterForAttribute(targetAttribute); + ConstantValueConverter constantValueConverter = GetConstantValueConverterForAttribute(targetAttribute); LiteralConstantExpression constant = ParseConstant(constantValueConverter); constantsBuilder.Add(constant); @@ -489,7 +489,7 @@ private static ResourceType ResolveDerivedType(ResourceType baseType, string der return filter; } - private LiteralConstantExpression ParseConstant(Func constantValueConverter) + private LiteralConstantExpression ParseConstant(ConstantValueConverter constantValueConverter) { int position = GetNextTokenPositionOrEnd(); @@ -514,7 +514,7 @@ private NullConstantExpression ParseNull() throw new QueryParseException("null expected.", position); } - private static Func GetConstantValueConverterForType(Type destinationType) + protected virtual ConstantValueConverter GetConstantValueConverterForType(Type destinationType) { return (stringValue, position) => { @@ -529,7 +529,7 @@ private static Func GetConstantValueConverterForType(Type d }; } - private Func GetConstantValueConverterForAttribute(AttrAttribute attribute) + private ConstantValueConverter GetConstantValueConverterForAttribute(AttrAttribute attribute) { if (attribute is { Property.Name: nameof(Identifiable.Id) }) { From 60713bf069b3090d8d666c38ef7297dd32601164 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 23 Nov 2023 05:00:40 +0100 Subject: [PATCH 03/91] Add line break --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4d7442a3c9..3db9a71101 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -105,6 +105,7 @@ jobs: [xml]$xml = Get-Content Directory.Build.props $configuredVersionPrefix = $xml.Project.PropertyGroup.JsonApiDotNetCoreVersionPrefix | 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: From ebbdcae2abfeba8d09de74f841da6b7a0d940495 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Nov 2023 04:46:14 +0000 Subject: [PATCH 04/91] Bump Microsoft.CodeAnalysis.CSharp from 4.1.0 to 4.8.0 (#1402) --- package-versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-versions.props b/package-versions.props index dd842f59f6..06cd8b9e3a 100644 --- a/package-versions.props +++ b/package-versions.props @@ -8,7 +8,7 @@ 0.13.* 34.0.* - 4.7.* + 4.8.* 6.0.* 2.1.* 6.12.* From e57971b0a695069826501fb390a520db64aaba47 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Fri, 24 Nov 2023 12:14:06 +0100 Subject: [PATCH 05/91] Revert workaround for Resharper bug on nullability --- .../QueryStrings/IncludeQueryStringParameterReader.cs | 4 ---- .../QueryStrings/PaginationQueryStringParameterReader.cs | 4 ---- .../QueryStrings/SortQueryStringParameterReader.cs | 4 ---- .../QueryStrings/SparseFieldSetQueryStringParameterReader.cs | 4 ---- 4 files changed, 16 deletions(-) diff --git a/src/JsonApiDotNetCore/QueryStrings/IncludeQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/IncludeQueryStringParameterReader.cs index 76bcc4a7b4..ba0f889809 100644 --- a/src/JsonApiDotNetCore/QueryStrings/IncludeQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/IncludeQueryStringParameterReader.cs @@ -47,14 +47,10 @@ public virtual void Read(string parameterName, StringValues parameterValue) { try { - // Workaround for https://youtrack.jetbrains.com/issue/RSRP-493256/Incorrect-possible-null-assignment - // ReSharper disable once AssignNullToNotNullAttribute _includeExpression = GetInclude(parameterValue.ToString()); } catch (QueryParseException exception) { - // Workaround for https://youtrack.jetbrains.com/issue/RSRP-493256/Incorrect-possible-null-assignment - // ReSharper disable once AssignNullToNotNullAttribute string specificMessage = exception.GetMessageWithPosition(parameterValue.ToString()); throw new InvalidQueryStringParameterException(parameterName, "The specified include is invalid.", specificMessage, exception); } diff --git a/src/JsonApiDotNetCore/QueryStrings/PaginationQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/PaginationQueryStringParameterReader.cs index 3364217efb..e46e59ce18 100644 --- a/src/JsonApiDotNetCore/QueryStrings/PaginationQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/PaginationQueryStringParameterReader.cs @@ -58,8 +58,6 @@ public virtual void Read(string parameterName, StringValues parameterValue) try { - // Workaround for https://youtrack.jetbrains.com/issue/RSRP-493256/Incorrect-possible-null-assignment - // ReSharper disable once AssignNullToNotNullAttribute PaginationQueryStringValueExpression constraint = GetPageConstraint(parameterValue.ToString()); if (constraint.Elements.Any(element => element.Scope == null)) @@ -82,8 +80,6 @@ public virtual void Read(string parameterName, StringValues parameterValue) } catch (QueryParseException exception) { - // Workaround for https://youtrack.jetbrains.com/issue/RSRP-493256/Incorrect-possible-null-assignment - // ReSharper disable once AssignNullToNotNullAttribute string specificMessage = exception.GetMessageWithPosition(isParameterNameValid ? parameterValue.ToString() : parameterName); throw new InvalidQueryStringParameterException(parameterName, "The specified pagination is invalid.", specificMessage, exception); } diff --git a/src/JsonApiDotNetCore/QueryStrings/SortQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/SortQueryStringParameterReader.cs index d068ae7ce2..d1ff5fface 100644 --- a/src/JsonApiDotNetCore/QueryStrings/SortQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/SortQueryStringParameterReader.cs @@ -59,16 +59,12 @@ public virtual void Read(string parameterName, StringValues parameterValue) ResourceFieldChainExpression? scope = GetScope(parameterName); parameterNameIsValid = true; - // Workaround for https://youtrack.jetbrains.com/issue/RSRP-493256/Incorrect-possible-null-assignment - // ReSharper disable once AssignNullToNotNullAttribute SortExpression sort = GetSort(parameterValue.ToString(), scope); var expressionInScope = new ExpressionInScope(scope, sort); _constraints.Add(expressionInScope); } catch (QueryParseException exception) { - // Workaround for https://youtrack.jetbrains.com/issue/RSRP-493256/Incorrect-possible-null-assignment - // ReSharper disable once AssignNullToNotNullAttribute string specificMessage = exception.GetMessageWithPosition(parameterNameIsValid ? parameterValue.ToString() : parameterName); throw new InvalidQueryStringParameterException(parameterName, "The specified sort is invalid.", specificMessage, exception); } diff --git a/src/JsonApiDotNetCore/QueryStrings/SparseFieldSetQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/SparseFieldSetQueryStringParameterReader.cs index 559da09f38..c147285636 100644 --- a/src/JsonApiDotNetCore/QueryStrings/SparseFieldSetQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/SparseFieldSetQueryStringParameterReader.cs @@ -63,15 +63,11 @@ public virtual void Read(string parameterName, StringValues parameterValue) ResourceType resourceType = GetScope(parameterName); parameterNameIsValid = true; - // Workaround for https://youtrack.jetbrains.com/issue/RSRP-493256/Incorrect-possible-null-assignment - // ReSharper disable once AssignNullToNotNullAttribute SparseFieldSetExpression sparseFieldSet = GetSparseFieldSet(parameterValue.ToString(), resourceType); _sparseFieldTableBuilder[resourceType] = sparseFieldSet; } catch (QueryParseException exception) { - // Workaround for https://youtrack.jetbrains.com/issue/RSRP-493256/Incorrect-possible-null-assignment - // ReSharper disable once AssignNullToNotNullAttribute string specificMessage = exception.GetMessageWithPosition(parameterNameIsValid ? parameterValue.ToString() : parameterName); throw new InvalidQueryStringParameterException(parameterName, "The specified fieldset is invalid.", specificMessage, exception); } From 5f5cfc1cf57427648a97926a531e62dfb8fd3a3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 01:14:27 +0000 Subject: [PATCH 06/91] Update SauceControl.InheritDoc requirement from 1.3.* to 1.4.* (#1409) --- package-versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-versions.props b/package-versions.props index 06cd8b9e3a..37c2377bf4 100644 --- a/package-versions.props +++ b/package-versions.props @@ -13,7 +13,7 @@ 2.1.* 6.12.* 2.3.* - 1.3.* + 1.4.* 8.0.* 17.8.* 2.5.* From c4736527a851148e3ee3539eb2c24e572cf2b925 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 30 Nov 2023 02:52:24 +0100 Subject: [PATCH 07/91] Update Resharper to RC1 (#1412) * Update Resharper to RC1 * Revert workaround --- .config/dotnet-tools.json | 2 +- .../Configuration/DependencyContainerRegistrationTests.cs | 3 --- .../FieldChains/FieldChainPatternInheritanceMatchTests.cs | 3 --- .../UnitTests/FieldChains/FieldChainPatternMatchTests.cs | 3 --- .../UnitTests/Middleware/JsonApiMiddlewareTests.cs | 3 --- .../ModelStateValidation/ModelStateValidationTests.cs | 3 --- .../ResourceDefinitions/CreateSortExpressionFromLambdaTests.cs | 3 --- .../UnitTests/ResourceGraph/ResourceGraphBuilderTests.cs | 3 --- .../Serialization/Response/IncompleteResourceGraphTests.cs | 3 --- test/NoEntityFrameworkTests/NullSafeExpressionRewriterTests.cs | 3 --- 10 files changed, 1 insertion(+), 28 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 7f98da7f1a..34033af1b2 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2023.3.0-eap08", + "version": "2023.3.0-rc01", "commands": [ "jb" ] diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs index 9e96bc718a..0289585dfb 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs @@ -15,9 +15,6 @@ using TestBuildingBlocks; using Xunit; -// Workaround for Resharper bug at https://youtrack.jetbrains.com/issue/RSRP-494909/Breaking-UsedImplicitly-and-PublicAPI-on-types-no-longer-respected. -// ReSharper disable PropertyCanBeMadeInitOnly.Local - namespace JsonApiDotNetCoreTests.UnitTests.Configuration; public sealed class DependencyContainerRegistrationTests diff --git a/test/JsonApiDotNetCoreTests/UnitTests/FieldChains/FieldChainPatternInheritanceMatchTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/FieldChains/FieldChainPatternInheritanceMatchTests.cs index f308b40d2d..067f8167d8 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/FieldChains/FieldChainPatternInheritanceMatchTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/FieldChains/FieldChainPatternInheritanceMatchTests.cs @@ -10,9 +10,6 @@ using Xunit; using Xunit.Abstractions; -// Workaround for Resharper bug at https://youtrack.jetbrains.com/issue/RSRP-494909/Breaking-UsedImplicitly-and-PublicAPI-on-types-no-longer-respected. -// ReSharper disable PropertyCanBeMadeInitOnly.Local - // ReSharper disable InconsistentNaming #pragma warning disable AV1706 // Identifier contains an abbreviation or is too short diff --git a/test/JsonApiDotNetCoreTests/UnitTests/FieldChains/FieldChainPatternMatchTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/FieldChains/FieldChainPatternMatchTests.cs index dec0e7c33d..613dd4fd41 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/FieldChains/FieldChainPatternMatchTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/FieldChains/FieldChainPatternMatchTests.cs @@ -10,9 +10,6 @@ using Xunit; using Xunit.Abstractions; -// Workaround for Resharper bug at https://youtrack.jetbrains.com/issue/RSRP-494909/Breaking-UsedImplicitly-and-PublicAPI-on-types-no-longer-respected. -// ReSharper disable PropertyCanBeMadeInitOnly.Local - // ReSharper disable InconsistentNaming #pragma warning disable AV1706 // Identifier contains an abbreviation or is too short diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Middleware/JsonApiMiddlewareTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Middleware/JsonApiMiddlewareTests.cs index 4293a894df..d11eb53a19 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Middleware/JsonApiMiddlewareTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Middleware/JsonApiMiddlewareTests.cs @@ -13,9 +13,6 @@ using TestBuildingBlocks; using Xunit; -// Workaround for Resharper bug at https://youtrack.jetbrains.com/issue/RSRP-494909/Breaking-UsedImplicitly-and-PublicAPI-on-types-no-longer-respected. -// ReSharper disable PropertyCanBeMadeInitOnly.Local - #pragma warning disable AV1561 // Signature contains too many parameters namespace JsonApiDotNetCoreTests.UnitTests.Middleware; diff --git a/test/JsonApiDotNetCoreTests/UnitTests/ModelStateValidation/ModelStateValidationTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/ModelStateValidation/ModelStateValidationTests.cs index a1daade80a..34bbc16d17 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/ModelStateValidation/ModelStateValidationTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/ModelStateValidation/ModelStateValidationTests.cs @@ -10,9 +10,6 @@ using TestBuildingBlocks; using Xunit; -// Workaround for Resharper bug at https://youtrack.jetbrains.com/issue/RSRP-494909/Breaking-UsedImplicitly-and-PublicAPI-on-types-no-longer-respected. -// ReSharper disable PropertyCanBeMadeInitOnly.Local - namespace JsonApiDotNetCoreTests.UnitTests.ModelStateValidation; public sealed class ModelStateValidationTests diff --git a/test/JsonApiDotNetCoreTests/UnitTests/ResourceDefinitions/CreateSortExpressionFromLambdaTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/ResourceDefinitions/CreateSortExpressionFromLambdaTests.cs index bedb5398f8..337940b75c 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/ResourceDefinitions/CreateSortExpressionFromLambdaTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/ResourceDefinitions/CreateSortExpressionFromLambdaTests.cs @@ -11,9 +11,6 @@ using TestBuildingBlocks; using Xunit; -// Workaround for Resharper bug at https://youtrack.jetbrains.com/issue/RSRP-494909/Breaking-UsedImplicitly-and-PublicAPI-on-types-no-longer-respected. -// ReSharper disable PropertyCanBeMadeInitOnly.Local - namespace JsonApiDotNetCoreTests.UnitTests.ResourceDefinitions; public sealed class CreateSortExpressionFromLambdaTests diff --git a/test/JsonApiDotNetCoreTests/UnitTests/ResourceGraph/ResourceGraphBuilderTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/ResourceGraph/ResourceGraphBuilderTests.cs index 0f270ddfeb..ebce906b9d 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/ResourceGraph/ResourceGraphBuilderTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/ResourceGraph/ResourceGraphBuilderTests.cs @@ -10,9 +10,6 @@ using TestBuildingBlocks; using Xunit; -// Workaround for Resharper bug at https://youtrack.jetbrains.com/issue/RSRP-494909/Breaking-UsedImplicitly-and-PublicAPI-on-types-no-longer-respected. -// ReSharper disable PropertyCanBeMadeInitOnly.Local - namespace JsonApiDotNetCoreTests.UnitTests.ResourceGraph; public sealed class ResourceGraphBuilderTests diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/IncompleteResourceGraphTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/IncompleteResourceGraphTests.cs index 5d6551e2ad..85329874ea 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/IncompleteResourceGraphTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/IncompleteResourceGraphTests.cs @@ -10,9 +10,6 @@ using Microsoft.Extensions.Logging.Abstractions; using Xunit; -// Workaround for Resharper bug at https://youtrack.jetbrains.com/issue/RSRP-494909/Breaking-UsedImplicitly-and-PublicAPI-on-types-no-longer-respected. -// ReSharper disable PropertyCanBeMadeInitOnly.Local - namespace JsonApiDotNetCoreTests.UnitTests.Serialization.Response; public sealed class IncompleteResourceGraphTests diff --git a/test/NoEntityFrameworkTests/NullSafeExpressionRewriterTests.cs b/test/NoEntityFrameworkTests/NullSafeExpressionRewriterTests.cs index 27a52c0732..e8fe1585ca 100644 --- a/test/NoEntityFrameworkTests/NullSafeExpressionRewriterTests.cs +++ b/test/NoEntityFrameworkTests/NullSafeExpressionRewriterTests.cs @@ -6,9 +6,6 @@ using NoEntityFrameworkExample; using Xunit; -// Workaround for Resharper bug at https://youtrack.jetbrains.com/issue/RSRP-494909/Breaking-UsedImplicitly-and-PublicAPI-on-types-no-longer-respected. -// ReSharper disable PropertyCanBeMadeInitOnly.Local - namespace NoEntityFrameworkTests; public sealed class NullSafeExpressionRewriterTests From 558d8b4a43ae6512d31afb4d30905bb267e38407 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 2 Dec 2023 01:17:58 +0100 Subject: [PATCH 08/91] Resharper tweaks (#1413) * Add directive to correct formatting style * Promote Global:PropertyCanBeMadeInitOnly to warning, so it aligns with what Resharper shows in VS, and address new violations --- WarningSeverities.DotSettings | 1 + .../Serialization/Objects/Document.cs | 2 ++ .../IntegrationTests/FrozenClock.cs | 2 ++ test/UnitTests/Internal/ErrorObjectTests.cs | 29 +++++-------------- 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/WarningSeverities.DotSettings b/WarningSeverities.DotSettings index 5c641e606f..f8ac8e1515 100644 --- a/WarningSeverities.DotSettings +++ b/WarningSeverities.DotSettings @@ -122,6 +122,7 @@ WARNING WARNING WARNING + WARNING WARNING WARNING WARNING diff --git a/src/JsonApiDotNetCore/Serialization/Objects/Document.cs b/src/JsonApiDotNetCore/Serialization/Objects/Document.cs index e0d7d5def3..f21334f5c4 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/Document.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/Document.cs @@ -1,10 +1,12 @@ using System.Text.Json.Serialization; +using JetBrains.Annotations; namespace JsonApiDotNetCore.Serialization.Objects; /// /// See https://jsonapi.org/format#document-top-level and https://jsonapi.org/ext/atomic/#document-structure. /// +[PublicAPI] public sealed class Document { [JsonPropertyName("jsonapi")] diff --git a/test/DapperTests/IntegrationTests/FrozenClock.cs b/test/DapperTests/IntegrationTests/FrozenClock.cs index 0de2390b71..c7ca4ea851 100644 --- a/test/DapperTests/IntegrationTests/FrozenClock.cs +++ b/test/DapperTests/IntegrationTests/FrozenClock.cs @@ -1,8 +1,10 @@ using DapperExample; using FluentAssertions.Extensions; +using JetBrains.Annotations; namespace DapperTests.IntegrationTests; +[UsedImplicitly(ImplicitUseTargetFlags.Members)] internal sealed class FrozenClock : IClock { private static readonly DateTimeOffset DefaultTime = 1.January(2020).At(1, 1, 1).AsUtc(); diff --git a/test/UnitTests/Internal/ErrorObjectTests.cs b/test/UnitTests/Internal/ErrorObjectTests.cs index bb8c4e3bcc..9d022b0caa 100644 --- a/test/UnitTests/Internal/ErrorObjectTests.cs +++ b/test/UnitTests/Internal/ErrorObjectTests.cs @@ -7,28 +7,15 @@ namespace UnitTests.Internal; public sealed class ErrorObjectTests { - // Formatting below is broken due to Resharper bug at https://youtrack.jetbrains.com/issue/RSRP-494897/Formatter-directive-broken-in-2023.3-EAP7. - // This no longer works: @formatter:wrap_array_initializer_style wrap_if_long + // @formatter:wrap_array_initializer_style wrap_if_long + // @formatter:max_array_initializer_elements_on_line 10 [Theory] - [InlineData(new[] - { - HttpStatusCode.UnprocessableEntity - }, HttpStatusCode.UnprocessableEntity)] - [InlineData(new[] - { - HttpStatusCode.UnprocessableEntity, - HttpStatusCode.UnprocessableEntity - }, HttpStatusCode.UnprocessableEntity)] - [InlineData(new[] - { - HttpStatusCode.UnprocessableEntity, - HttpStatusCode.Unauthorized - }, HttpStatusCode.BadRequest)] - [InlineData(new[] - { - HttpStatusCode.UnprocessableEntity, - HttpStatusCode.BadGateway - }, HttpStatusCode.InternalServerError)] + [InlineData(new[] { HttpStatusCode.UnprocessableEntity }, HttpStatusCode.UnprocessableEntity)] + [InlineData(new[] { HttpStatusCode.UnprocessableEntity, HttpStatusCode.UnprocessableEntity }, HttpStatusCode.UnprocessableEntity)] + [InlineData(new[] { HttpStatusCode.UnprocessableEntity, HttpStatusCode.Unauthorized }, HttpStatusCode.BadRequest)] + [InlineData(new[] { HttpStatusCode.UnprocessableEntity, HttpStatusCode.BadGateway }, HttpStatusCode.InternalServerError)] + // @formatter:max_array_initializer_elements_on_line restore + // @formatter:wrap_array_initializer_style restore public void ErrorDocument_GetErrorStatusCode_IsCorrect(HttpStatusCode[] errorCodes, HttpStatusCode expected) { // Arrange From e60f60bb3d016cc957b89fc7c69a89fd7dba78df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 03:04:01 +0000 Subject: [PATCH 09/91] Bump jetbrains.resharper.globaltools from 2023.3.0-rc01 to 2023.3.0 (#1416) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 34033af1b2..90ac9675f8 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2023.3.0-rc01", + "version": "2023.3.0", "commands": [ "jb" ] From 337a2a9635d975c09aa9b6ae66550392a8e56c73 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 10 Dec 2023 19:29:50 +0100 Subject: [PATCH 10/91] Try workaround for flaky network in GitHub hosted runners (#1403) https://github.com/actions/runner-images/issues/1187 --- .github/workflows/build.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3db9a71101..083411ba25 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,6 +39,8 @@ jobs: permissions: contents: read steps: + - name: Tune GitHub-hosted runner network + uses: smorimoto/tune-github-hosted-runner-network@v1 - name: Setup PostgreSQL uses: ikalnytskyi/action-setup-postgres@v4 with: @@ -181,6 +183,8 @@ jobs: 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@v3 with: @@ -234,6 +238,8 @@ jobs: 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@v3 with: @@ -277,6 +283,8 @@ jobs: 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@v3 - name: Publish to GitHub Packages From a7fcefb955110963a47a3b31796fed7084ad145c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 09:06:46 +0000 Subject: [PATCH 11/91] Bump actions/setup-dotnet from 3 to 4 (#1419) --- .github/workflows/build.yml | 6 +++--- .github/workflows/codeql.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 083411ba25..d553ef71da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,7 +47,7 @@ jobs: username: postgres password: postgres - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 6.0.x @@ -186,7 +186,7 @@ jobs: - name: Tune GitHub-hosted runner network uses: smorimoto/tune-github-hosted-runner-network@v1 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 6.0.x @@ -241,7 +241,7 @@ jobs: - name: Tune GitHub-hosted runner network uses: smorimoto/tune-github-hosted-runner-network@v1 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 6.0.x diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5b1868eae5..9a58eeb4a7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,7 +24,7 @@ jobs: language: [ 'csharp' ] steps: - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 6.0.x From e4303301f307e6be36ff3929f87718e1e4a200a3 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 14 Dec 2023 12:07:22 +0100 Subject: [PATCH 12/91] Speed up InspectCode/CleanupCode by turning off roslyn analyzers (#1420) See recommendation at https://youtrack.jetbrains.com/issue/RSRP-493882/inspectcode.exe-performance-degradation --- .config/dotnet-tools.json | 2 +- .github/workflows/build.yml | 6 +++--- JsonApiDotNetCore.sln | 3 ++- cleanupcode.ps1 | 6 +++--- inspectcode.ps1 | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 90ac9675f8..32e76a6e81 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2023.3.0", + "version": "2023.3.1", "commands": [ "jb" ] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d553ef71da..7efd2cef0e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -201,7 +201,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 --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal + 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 - name: Verify outcome shell: pwsh run: | @@ -266,13 +266,13 @@ jobs: $baseCommitHash = git rev-parse HEAD~1 Write-Output "Running code cleanup on commit range $baseCommitHash..$headCommitHash in pull request." - dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --max-runs=5 --jb --dotnetcoresdk=$(dotnet --version) --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $headCommitHash -b $baseCommitHash --fail-on-diff --print-diff + dotnet regitlint -s JsonApiDotNetCore.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.sln --print-command --skip-tool-check --jb --dotnetcoresdk=$(dotnet --version) --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN --fail-on-diff --print-diff + dotnet regitlint -s JsonApiDotNetCore.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 diff --git a/JsonApiDotNetCore.sln b/JsonApiDotNetCore.sln index e821d4175d..c555610364 100644 --- a/JsonApiDotNetCore.sln +++ b/JsonApiDotNetCore.sln @@ -10,11 +10,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 - tests.runsettings = tests.runsettings package-versions.props = package-versions.props + tests.runsettings = tests.runsettings EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{026FBC6C-AF76-4568-9B87-EC73457899FD}" diff --git a/cleanupcode.ps1 b/cleanupcode.ps1 index 3ab4d620ae..237f854be3 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.sln --print-command --skip-tool-check --max-runs=5 --jb --dotnetcoresdk=$(dotnet --version) --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified + dotnet regitlint -s JsonApiDotNetCore.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.sln --print-command --skip-tool-check --max-runs=5 --jb --dotnetcoresdk=$(dotnet --version) --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified,commits -a $headCommitHash -b $baseCommitHash + dotnet regitlint -s JsonApiDotNetCore.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.sln --print-command --skip-tool-check --jb --dotnetcoresdk=$(dotnet --version) --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN + dotnet regitlint -s JsonApiDotNetCore.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 14c3eb1736..aa816da5e9 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 --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal +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 if ($LastExitCode -ne 0) { throw "Code inspection failed with exit code $LastExitCode" From f33c20df45f5b8db2c24f71b04c0063f5e74eb32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 02:09:35 +0000 Subject: [PATCH 13/91] Bump jetbrains.resharper.globaltools from 2023.3.1 to 2023.3.2 Bumps jetbrains.resharper.globaltools from 2023.3.1 to 2023.3.2. --- updated-dependencies: - dependency-name: jetbrains.resharper.globaltools dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .config/dotnet-tools.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 32e76a6e81..575923d62c 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2023.3.1", + "version": "2023.3.2", "commands": [ "jb" ] @@ -27,4 +27,4 @@ ] } } -} +} \ No newline at end of file From 89a655f3ae10c56bb9ff53f0a4d956c89b342107 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 21 Dec 2023 11:44:05 +0100 Subject: [PATCH 14/91] Resharper: Fix new warning about usage of collection expressions --- benchmarks/Program.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs index 818b9ab5e5..b1e3307931 100644 --- a/benchmarks/Program.cs +++ b/benchmarks/Program.cs @@ -9,14 +9,13 @@ internal static class Program { private static void Main(string[] args) { - var switcher = new BenchmarkSwitcher(new[] - { + var switcher = new BenchmarkSwitcher([ typeof(ResourceDeserializationBenchmarks), typeof(OperationsDeserializationBenchmarks), typeof(ResourceSerializationBenchmarks), typeof(OperationsSerializationBenchmarks), typeof(QueryStringParserBenchmarks) - }); + ]); switcher.Run(args); } From 1f71c6f2d824515fb0078672b66b0ee4d803552f Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 21 Dec 2023 12:26:00 +0100 Subject: [PATCH 15/91] Resharper: use primary constructors --- JsonApiDotNetCore.sln.DotSettings | 2 + WarningSeverities.DotSettings | 2 + .../Controllers/OperationsController.cs | 11 ++---- .../DapperExample/Data/RotatingList.cs | 9 +---- .../Repositories/ResultSetMapper.cs | 9 +---- .../DeleteOneToOneStatementBuilder.cs | 7 +--- .../DeleteResourceStatementBuilder.cs | 7 +--- .../Builders/InsertStatementBuilder.cs | 7 +--- .../Builders/SqlQueryBuilder.cs | 18 ++------- .../UpdateClearOneToOneStatementBuilder.cs | 7 +--- .../UpdateResourceStatementBuilder.cs | 7 +--- .../DataModel/FromEntitiesDataModelService.cs | 7 +--- .../Generators/ParameterGenerator.cs | 8 +--- .../Generators/TableAliasGenerator.cs | 8 +--- .../StaleColumnReferenceRewriter.cs | 9 +---- .../TreeNodes/ColumnInSelectNode.cs | 11 ++---- .../TreeNodes/ColumnInTableNode.cs | 7 +--- .../TreeNodes/CountSelectorNode.cs | 7 +--- .../TranslationToSql/TreeNodes/FromNode.cs | 7 +--- .../TreeNodes/OneSelectorNode.cs | 7 +--- .../TreeNodes/OrderByTermNode.cs | 9 +---- .../TreeNodes/SelectorNode.cs | 9 +---- .../TreeNodes/TableSourceNode.cs | 9 +---- .../Data/AppDbContext.cs | 14 ++----- .../GettingStarted/Data/SampleDbContext.cs | 7 +--- .../Controllers/OperationsController.cs | 11 ++---- .../Data/AppDbContext.cs | 7 +--- .../Data/RotatingList.cs | 9 +---- .../Definitions/TodoItemDefinition.cs | 23 +++++------ .../MultiDbContextExample/Data/DbContextA.cs | 7 +--- .../MultiDbContextExample/Data/DbContextB.cs | 7 +--- .../Repositories/DbContextARepository.cs | 16 +++----- .../Repositories/DbContextBRepository.cs | 16 +++----- .../InMemoryInverseNavigationResolver.cs | 9 +---- .../QueryLayerToLinqConverter.cs | 12 ++---- .../Repositories/PersonRepository.cs | 8 +--- .../Repositories/TagRepository.cs | 8 +--- .../Repositories/TodoItemRepository.cs | 8 +--- .../Services/TodoItemService.cs | 12 +++--- .../ReportsExample/Services/ReportService.cs | 9 +---- .../SourceCodeWriter.cs | 12 ++---- .../AtomicOperations/LocalIdTracker.cs | 9 +---- .../Configuration/ResourceNameFormatter.cs | 9 +---- .../JsonApiOperationsController.cs | 10 ++--- ...annotClearRequiredRelationshipException.cs | 15 +++---- .../Errors/DuplicateLocalIdValueException.cs | 14 ++----- .../Errors/FailedOperationException.cs | 23 +++++------ .../IncompatibleLocalIdTypeException.cs | 15 +++---- .../Errors/InvalidConfigurationException.cs | 8 +--- .../Errors/InvalidModelStateException.cs | 25 +++++------- .../Errors/InvalidQueryException.cs | 14 ++----- .../InvalidQueryStringParameterException.cs | 27 ++++++------- .../Errors/InvalidRequestBodyException.cs | 39 ++++++++----------- .../Errors/LocalIdSingleOperationException.cs | 14 ++----- .../MissingTransactionSupportException.cs | 14 ++----- .../NonParticipatingTransactionException.cs | 14 ++----- .../Errors/RelationshipNotFoundException.cs | 14 ++----- .../Errors/ResourceAlreadyExistsException.cs | 14 ++----- .../Errors/ResourceNotFoundException.cs | 14 ++----- ...sourcesInRelationshipsNotFoundException.cs | 8 +--- .../Errors/RouteNotAvailableException.cs | 18 +++------ .../Errors/UnknownLocalIdValueException.cs | 14 ++----- .../Middleware/TraceLogWriter.cs | 9 +---- ...nationElementQueryStringValueExpression.cs | 15 ++----- .../Queries/IndentingStringWriter.cs | 9 +---- .../Queries/Parsing/FilterParser.cs | 9 +---- .../Queries/Parsing/Token.cs | 12 ++---- .../FieldChains/FieldChainFormatException.cs | 10 +---- .../FieldChains/PatternFormatException.cs | 13 ++----- .../Repositories/DataStoreUpdateException.cs | 8 +--- .../Serialization/Objects/ErrorObject.cs | 9 +---- .../Adapters/AtomicReferenceAdapter.cs | 8 +--- .../Adapters/RequestAdapterPosition.cs | 9 +---- .../ResourceDataInOperationsRequestAdapter.cs | 8 +--- .../ResourceIdentifierObjectAdapter.cs | 8 +--- .../IntegrationTests/SqlTextAdapter.cs | 9 +---- .../PrivateResourceDefinition.cs | 8 +--- .../PrivateResourceRepository.cs | 14 +++---- test/DiscoveryTests/PrivateResourceService.cs | 14 +++---- .../TelevisionBroadcastDefinition.cs | 19 +++------ .../Archiving/TelevisionDbContext.cs | 7 +--- .../CreateMusicTrackOperationsController.cs | 10 ++--- ...mplicitlyChangingTextLanguageDefinition.cs | 11 ++---- .../Meta/MusicTrackMetaDefinition.cs | 8 +--- .../Meta/TextLanguageMetaDefinition.cs | 8 +--- .../Mixed/AtomicLoggingTests.cs | 9 +---- .../AtomicOperations/OperationsController.cs | 11 ++---- .../AtomicOperations/OperationsDbContext.cs | 7 +--- .../Serialization/RecordCompanyDefinition.cs | 8 +--- .../SparseFieldSets/LyricTextDefinition.cs | 11 ++---- .../Transactions/ExtraDbContext.cs | 8 +--- .../Transactions/MusicTrackRepository.cs | 13 +++---- .../Scopes/OperationsController.cs | 10 ++--- .../Scopes/ScopesAuthorizationFilter.cs | 9 +---- .../Authorization/Scopes/ScopesDbContext.cs | 7 +--- .../IntegrationTests/Blobs/BlobDbContext.cs | 7 +--- .../CarCompositeKeyAwareRepository.cs | 16 +++----- .../CompositeKeys/CompositeDbContext.cs | 7 +--- .../OperationsController.cs | 11 ++---- .../ContentNegotiation/PolicyDbContext.cs | 7 +--- .../ActionResultDbContext.cs | 7 +--- .../CustomRoutes/CustomRouteDbContext.cs | 7 +--- .../EagerLoading/BuildingRepository.cs | 13 +++---- .../EagerLoading/EagerLoadingDbContext.cs | 7 +--- .../AlternateExceptionHandler.cs | 7 +--- ...umerArticleIsNoLongerAvailableException.cs | 19 ++++----- .../ConsumerArticleService.cs | 13 +++---- .../ExceptionHandling/ErrorDbContext.cs | 7 +--- .../HostingInIIS/HostingDbContext.cs | 7 +--- .../IdObfuscation/BankAccountsController.cs | 11 ++---- .../IdObfuscation/DebitCardsController.cs | 11 ++---- .../ObfuscatedIdentifiableController.cs | 10 ++--- .../IdObfuscation/ObfuscationDbContext.cs | 7 +--- .../ModelState/ModelStateDbContext.cs | 7 +--- .../RequestBody/WorkflowDbContext.cs | 7 +--- .../RequestBody/WorkflowDefinition.cs | 7 +--- .../IntegrationTests/Links/LinksDbContext.cs | 7 +--- .../Logging/LoggingDbContext.cs | 7 +--- .../IntegrationTests/Meta/MetaDbContext.cs | 7 +--- .../Meta/SupportTicketDefinition.cs | 8 +--- .../FireForgetDbContext.cs | 7 +--- .../FireForgetGroupDefinition.cs | 13 ++----- .../FireForgetUserDefinition.cs | 13 ++----- .../Messages/GroupCreatedContent.cs | 12 ++---- .../Messages/GroupDeletedContent.cs | 9 +---- .../Messages/GroupRenamedContent.cs | 15 ++----- .../Messages/UserAddedToGroupContent.cs | 12 ++---- .../Messages/UserCreatedContent.cs | 15 ++----- .../Messages/UserDeletedContent.cs | 9 +---- .../Messages/UserDisplayNameChangedContent.cs | 15 ++----- .../Messages/UserLoginNameChangedContent.cs | 15 ++----- .../Messages/UserMovedToGroupContent.cs | 15 ++----- .../Messages/UserRemovedFromGroupContent.cs | 12 ++---- .../Microservices/MessagingGroupDefinition.cs | 16 +++----- .../Microservices/MessagingUserDefinition.cs | 11 ++---- .../OutboxDbContext.cs | 7 +--- .../OutboxGroupDefinition.cs | 11 ++---- .../OutboxUserDefinition.cs | 11 ++---- .../MultiTenancy/MultiTenancyDbContext.cs | 10 +---- .../MultiTenantResourceService.cs | 17 ++++---- .../MultiTenancy/RouteTenantProvider.cs | 9 +---- .../DivingBoardsController.cs | 11 ++---- .../NamingConventions/NamingDbContext.cs | 7 +--- .../SwimmingPoolsController.cs | 11 ++---- .../DuplicateKnownResourcesController.cs | 11 ++---- .../NonJsonApiControllers/EmptyDbContext.cs | 8 +--- .../NonJsonApiControllers/KnownDbContext.cs | 7 +--- .../IsUpperCase/IsUpperCaseFilterParser.cs | 7 +--- .../StringLength/LengthFilterParser.cs | 7 +--- .../CustomFunctions/Sum/SumFilterParser.cs | 7 +--- .../FilterRewritingResourceDefinition.cs | 11 ++---- .../TimeOffset/FilterTimeOffsetRewriter.cs | 9 +---- .../TimeOffset/TimeOffsetFilterParser.cs | 7 +--- .../QueryStrings/Filtering/FilterDbContext.cs | 7 +--- .../QueryStrings/QueryStringDbContext.cs | 7 +--- .../ResultCapturingRepository.cs | 16 +++----- .../ImplicitlyChangingWorkItemDefinition.cs | 11 ++---- ...plicitlyChangingWorkItemGroupDefinition.cs | 11 ++---- .../ReadWrite/ReadWriteDbContext.cs | 7 +--- .../RemoveFromToManyRelationshipTests.cs | 7 +--- .../DefaultBehaviorDbContext.cs | 7 +--- .../GiftCertificate.cs | 9 +---- .../PostOffice.cs | 9 +---- .../Reading/MoonDefinition.cs | 15 ++----- .../Reading/PlanetDefinition.cs | 15 ++----- .../Reading/StarDefinition.cs | 11 ++---- .../Reading/UniverseDbContext.cs | 7 +--- .../Serialization/SerializationDbContext.cs | 7 +--- .../Serialization/StudentDefinition.cs | 15 ++----- .../ChangeTracking/ChangeTrackingDbContext.cs | 7 +--- .../ResourceInheritanceDbContext.cs | 7 +--- .../TablePerConcreteTypeDbContext.cs | 7 +--- .../TablePerConcreteTypeReadTests.cs | 10 ++--- .../TablePerConcreteTypeWriteTests.cs | 10 ++--- .../TablePerHierarchyDbContext.cs | 8 +--- .../TablePerHierarchyReadTests.cs | 9 +---- .../TablePerHierarchyWriteTests.cs | 9 +---- .../TablePerType/TablePerTypeDbContext.cs | 7 +--- .../TablePerType/TablePerTypeReadTests.cs | 9 +---- .../TablePerType/TablePerTypeWriteTests.cs | 9 +---- .../RestrictionDbContext.cs | 7 +--- .../Serialization/SerializationDbContext.cs | 7 +--- .../SoftDeletionAwareResourceService.cs | 26 +++++-------- .../SoftDeletion/SoftDeletionDbContext.cs | 7 +--- .../ZeroKeys/ZeroKeyDbContext.cs | 7 +--- .../DependencyContainerRegistrationTests.cs | 8 +--- .../ServiceCollectionExtensionsTests.cs | 7 +--- .../Middleware/JsonApiMiddlewareTests.cs | 12 ++---- .../QueryStringParameters/FilterParseTests.cs | 14 +------ .../CreateSortExpressionFromLambdaTests.cs | 7 +--- test/MultiDbContextTests/ResourceTests.cs | 9 +---- test/NoEntityFrameworkTests/PersonTests.cs | 9 +---- test/NoEntityFrameworkTests/TagTests.cs | 9 +---- test/NoEntityFrameworkTests/TodoItemTests.cs | 9 +---- .../GeneratorDriverRunResultExtensions.cs | 8 +--- test/TestBuildingBlocks/FakeLogMessage.cs | 12 ++---- test/TestBuildingBlocks/FakeLoggerFactory.cs | 18 ++------- test/TestBuildingBlocks/TestableDbContext.cs | 7 +--- .../TestBuildingBlocks/XUnitLoggerProvider.cs | 15 ++----- 199 files changed, 528 insertions(+), 1543 deletions(-) diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings index a945c3a40d..ce137df506 100644 --- a/JsonApiDotNetCore.sln.DotSettings +++ b/JsonApiDotNetCore.sln.DotSettings @@ -95,6 +95,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$); SUGGESTION SUGGESTION WARNING + True SUGGESTION <?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 @@ -154,6 +155,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$); WRAP_IF_LONG CHOP_ALWAYS CHOP_ALWAYS + WRAP_IF_LONG True True 2 diff --git a/WarningSeverities.DotSettings b/WarningSeverities.DotSettings index f8ac8e1515..a0fae5acd2 100644 --- a/WarningSeverities.DotSettings +++ b/WarningSeverities.DotSettings @@ -69,6 +69,7 @@ WARNING WARNING WARNING + WARNING WARNING WARNING WARNING @@ -197,6 +198,7 @@ WARNING WARNING WARNING + WARNING WARNING WARNING WARNING diff --git a/src/Examples/DapperExample/Controllers/OperationsController.cs b/src/Examples/DapperExample/Controllers/OperationsController.cs index 979e6c9cd7..6fe0eedd1d 100644 --- a/src/Examples/DapperExample/Controllers/OperationsController.cs +++ b/src/Examples/DapperExample/Controllers/OperationsController.cs @@ -6,11 +6,6 @@ namespace DapperExample.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) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields); diff --git a/src/Examples/DapperExample/Data/RotatingList.cs b/src/Examples/DapperExample/Data/RotatingList.cs index 67c19bea4a..278c34140a 100644 --- a/src/Examples/DapperExample/Data/RotatingList.cs +++ b/src/Examples/DapperExample/Data/RotatingList.cs @@ -16,16 +16,11 @@ public static RotatingList Create(int count, Func createElement) } } -internal sealed class RotatingList +internal sealed class RotatingList(IList elements) { private int _index = -1; - public IList Elements { get; } - - public RotatingList(IList elements) - { - Elements = elements; - } + public IList Elements { get; } = elements; public T GetNext() { diff --git a/src/Examples/DapperExample/Repositories/ResultSetMapper.cs b/src/Examples/DapperExample/Repositories/ResultSetMapper.cs index 61421d7331..4352b6c552 100644 --- a/src/Examples/DapperExample/Repositories/ResultSetMapper.cs +++ b/src/Examples/DapperExample/Repositories/ResultSetMapper.cs @@ -166,14 +166,9 @@ public IReadOnlyCollection GetResources() return _primaryResourcesInOrder.DistinctBy(resource => resource.Id).ToList(); } - private sealed class IncludeElementWalker + private sealed class IncludeElementWalker(IncludeExpression include) { - private readonly IncludeExpression _include; - - public IncludeElementWalker(IncludeExpression include) - { - _include = include; - } + private readonly IncludeExpression _include = include; public IEnumerable BreadthFirstEnumerate() { diff --git a/src/Examples/DapperExample/TranslationToSql/Builders/DeleteOneToOneStatementBuilder.cs b/src/Examples/DapperExample/TranslationToSql/Builders/DeleteOneToOneStatementBuilder.cs index 5a1293d41b..3ea368b299 100644 --- a/src/Examples/DapperExample/TranslationToSql/Builders/DeleteOneToOneStatementBuilder.cs +++ b/src/Examples/DapperExample/TranslationToSql/Builders/DeleteOneToOneStatementBuilder.cs @@ -6,13 +6,8 @@ namespace DapperExample.TranslationToSql.Builders; -internal sealed class DeleteOneToOneStatementBuilder : StatementBuilder +internal sealed class DeleteOneToOneStatementBuilder(IDataModelService dataModelService) : StatementBuilder(dataModelService) { - public DeleteOneToOneStatementBuilder(IDataModelService dataModelService) - : base(dataModelService) - { - } - public DeleteNode Build(ResourceType resourceType, string whereColumnName, object? whereValue) { ArgumentGuard.NotNull(resourceType); diff --git a/src/Examples/DapperExample/TranslationToSql/Builders/DeleteResourceStatementBuilder.cs b/src/Examples/DapperExample/TranslationToSql/Builders/DeleteResourceStatementBuilder.cs index 41794e8883..1910075a8c 100644 --- a/src/Examples/DapperExample/TranslationToSql/Builders/DeleteResourceStatementBuilder.cs +++ b/src/Examples/DapperExample/TranslationToSql/Builders/DeleteResourceStatementBuilder.cs @@ -6,13 +6,8 @@ namespace DapperExample.TranslationToSql.Builders; -internal sealed class DeleteResourceStatementBuilder : StatementBuilder +internal sealed class DeleteResourceStatementBuilder(IDataModelService dataModelService) : StatementBuilder(dataModelService) { - public DeleteResourceStatementBuilder(IDataModelService dataModelService) - : base(dataModelService) - { - } - public DeleteNode Build(ResourceType resourceType, params object[] idValues) { ArgumentGuard.NotNull(resourceType); diff --git a/src/Examples/DapperExample/TranslationToSql/Builders/InsertStatementBuilder.cs b/src/Examples/DapperExample/TranslationToSql/Builders/InsertStatementBuilder.cs index 7e444b45a1..5f8374df65 100644 --- a/src/Examples/DapperExample/TranslationToSql/Builders/InsertStatementBuilder.cs +++ b/src/Examples/DapperExample/TranslationToSql/Builders/InsertStatementBuilder.cs @@ -6,13 +6,8 @@ namespace DapperExample.TranslationToSql.Builders; -internal sealed class InsertStatementBuilder : StatementBuilder +internal sealed class InsertStatementBuilder(IDataModelService dataModelService) : StatementBuilder(dataModelService) { - public InsertStatementBuilder(IDataModelService dataModelService) - : base(dataModelService) - { - } - public InsertNode Build(ResourceType resourceType, IReadOnlyDictionary columnsToSet) { ArgumentGuard.NotNull(resourceType); diff --git a/src/Examples/DapperExample/TranslationToSql/Builders/SqlQueryBuilder.cs b/src/Examples/DapperExample/TranslationToSql/Builders/SqlQueryBuilder.cs index 3e3d48eb10..a082d3ecad 100644 --- a/src/Examples/DapperExample/TranslationToSql/Builders/SqlQueryBuilder.cs +++ b/src/Examples/DapperExample/TranslationToSql/Builders/SqlQueryBuilder.cs @@ -8,7 +8,7 @@ namespace DapperExample.TranslationToSql.Builders; /// /// Converts s into SQL text. /// -internal sealed class SqlQueryBuilder : SqlTreeNodeVisitor +internal sealed class SqlQueryBuilder(DatabaseProvider databaseProvider) : SqlTreeNodeVisitor { private static readonly char[] SpecialCharactersInLikeDefault = [ @@ -26,7 +26,7 @@ internal sealed class SqlQueryBuilder : SqlTreeNodeVisitor _parametersByName = []; private int _indentDepth; @@ -35,11 +35,6 @@ internal sealed class SqlQueryBuilder : SqlTreeNodeVisitor Parameters => _parametersByName.Values.ToDictionary(parameter => parameter.Name, parameter => parameter.Value); - public SqlQueryBuilder(DatabaseProvider databaseProvider) - { - _databaseProvider = databaseProvider; - } - public string GetCommand(SqlTreeNode node) { ArgumentGuard.NotNull(node); @@ -488,14 +483,9 @@ private IDisposable Indent() return new RevertIndentOnDispose(this); } - private sealed class RevertIndentOnDispose : IDisposable + private sealed class RevertIndentOnDispose(SqlQueryBuilder owner) : IDisposable { - private readonly SqlQueryBuilder _owner; - - public RevertIndentOnDispose(SqlQueryBuilder owner) - { - _owner = owner; - } + private readonly SqlQueryBuilder _owner = owner; public void Dispose() { diff --git a/src/Examples/DapperExample/TranslationToSql/Builders/UpdateClearOneToOneStatementBuilder.cs b/src/Examples/DapperExample/TranslationToSql/Builders/UpdateClearOneToOneStatementBuilder.cs index 4ccc5fec9a..712561528d 100644 --- a/src/Examples/DapperExample/TranslationToSql/Builders/UpdateClearOneToOneStatementBuilder.cs +++ b/src/Examples/DapperExample/TranslationToSql/Builders/UpdateClearOneToOneStatementBuilder.cs @@ -6,13 +6,8 @@ namespace DapperExample.TranslationToSql.Builders; -internal sealed class UpdateClearOneToOneStatementBuilder : StatementBuilder +internal sealed class UpdateClearOneToOneStatementBuilder(IDataModelService dataModelService) : StatementBuilder(dataModelService) { - public UpdateClearOneToOneStatementBuilder(IDataModelService dataModelService) - : base(dataModelService) - { - } - public UpdateNode Build(ResourceType resourceType, string setColumnName, string whereColumnName, object? whereValue) { ArgumentGuard.NotNull(resourceType); diff --git a/src/Examples/DapperExample/TranslationToSql/Builders/UpdateResourceStatementBuilder.cs b/src/Examples/DapperExample/TranslationToSql/Builders/UpdateResourceStatementBuilder.cs index 62fc3b7e20..a279ad45e5 100644 --- a/src/Examples/DapperExample/TranslationToSql/Builders/UpdateResourceStatementBuilder.cs +++ b/src/Examples/DapperExample/TranslationToSql/Builders/UpdateResourceStatementBuilder.cs @@ -6,13 +6,8 @@ namespace DapperExample.TranslationToSql.Builders; -internal sealed class UpdateResourceStatementBuilder : StatementBuilder +internal sealed class UpdateResourceStatementBuilder(IDataModelService dataModelService) : StatementBuilder(dataModelService) { - public UpdateResourceStatementBuilder(IDataModelService dataModelService) - : base(dataModelService) - { - } - public UpdateNode Build(ResourceType resourceType, IReadOnlyDictionary columnsToUpdate, params object[] idValues) { ArgumentGuard.NotNull(resourceType); diff --git a/src/Examples/DapperExample/TranslationToSql/DataModel/FromEntitiesDataModelService.cs b/src/Examples/DapperExample/TranslationToSql/DataModel/FromEntitiesDataModelService.cs index 0f030debdb..b0ba38f3a5 100644 --- a/src/Examples/DapperExample/TranslationToSql/DataModel/FromEntitiesDataModelService.cs +++ b/src/Examples/DapperExample/TranslationToSql/DataModel/FromEntitiesDataModelService.cs @@ -12,7 +12,7 @@ namespace DapperExample.TranslationToSql.DataModel; /// /// Derives foreign keys and connection strings from an existing Entity Framework Core model. /// -public sealed class FromEntitiesDataModelService : BaseDataModelService +public sealed class FromEntitiesDataModelService(IResourceGraph resourceGraph) : BaseDataModelService(resourceGraph) { private readonly Dictionary _foreignKeysByRelationship = []; private readonly Dictionary _columnNullabilityPerAttribute = []; @@ -21,11 +21,6 @@ public sealed class FromEntitiesDataModelService : BaseDataModelService public override DatabaseProvider DatabaseProvider => AssertHasDatabaseProvider(); - public FromEntitiesDataModelService(IResourceGraph resourceGraph) - : base(resourceGraph) - { - } - public void Initialize(DbContext dbContext) { _connectionString = dbContext.Database.GetConnectionString(); diff --git a/src/Examples/DapperExample/TranslationToSql/Generators/ParameterGenerator.cs b/src/Examples/DapperExample/TranslationToSql/Generators/ParameterGenerator.cs index bd4df111fc..aaa328e68f 100644 --- a/src/Examples/DapperExample/TranslationToSql/Generators/ParameterGenerator.cs +++ b/src/Examples/DapperExample/TranslationToSql/Generators/ParameterGenerator.cs @@ -20,11 +20,5 @@ public void Reset() _nameGenerator.Reset(); } - private sealed class ParameterNameGenerator : UniqueNameGenerator - { - public ParameterNameGenerator() - : base("@p") - { - } - } + private sealed class ParameterNameGenerator() : UniqueNameGenerator("@p"); } diff --git a/src/Examples/DapperExample/TranslationToSql/Generators/TableAliasGenerator.cs b/src/Examples/DapperExample/TranslationToSql/Generators/TableAliasGenerator.cs index 39d5d9d702..a63bfdc01a 100644 --- a/src/Examples/DapperExample/TranslationToSql/Generators/TableAliasGenerator.cs +++ b/src/Examples/DapperExample/TranslationToSql/Generators/TableAliasGenerator.cs @@ -3,10 +3,4 @@ namespace DapperExample.TranslationToSql.Generators; /// /// Generates a SQL table alias with a unique name. /// -internal sealed class TableAliasGenerator : UniqueNameGenerator -{ - public TableAliasGenerator() - : base("t") - { - } -} +internal sealed class TableAliasGenerator() : UniqueNameGenerator("t"); diff --git a/src/Examples/DapperExample/TranslationToSql/Transformations/StaleColumnReferenceRewriter.cs b/src/Examples/DapperExample/TranslationToSql/Transformations/StaleColumnReferenceRewriter.cs index 03692baf2d..a632c43e51 100644 --- a/src/Examples/DapperExample/TranslationToSql/Transformations/StaleColumnReferenceRewriter.cs +++ b/src/Examples/DapperExample/TranslationToSql/Transformations/StaleColumnReferenceRewriter.cs @@ -290,14 +290,9 @@ private IReadOnlyList VisitList(IEnumerable nodes, ColumnVisitMode mode return nodes.Select(element => TypedVisit(element, mode)).ToList(); } - private sealed class PopStackOnDispose : IDisposable + private sealed class PopStackOnDispose(Stack stack) : IDisposable { - private readonly Stack _stack; - - public PopStackOnDispose(Stack stack) - { - _stack = stack; - } + private readonly Stack _stack = stack; public void Dispose() { diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInSelectNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInSelectNode.cs index e4b79fe7eb..2be0561011 100644 --- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInSelectNode.cs +++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInSelectNode.cs @@ -10,18 +10,13 @@ namespace DapperExample.TranslationToSql.TreeNodes; /// SELECT t2.Id AS Id0 FROM (SELECT t1.Id FROM Users AS t1) AS t2 /// ]]>. /// -internal sealed class ColumnInSelectNode : ColumnNode +internal sealed class ColumnInSelectNode(ColumnSelectorNode selector, string? tableAlias) : ColumnNode(GetColumnName(selector), selector.Column.Type, + tableAlias) { - public ColumnSelectorNode Selector { get; } + public ColumnSelectorNode Selector { get; } = selector; public bool IsVirtual => Selector.Alias != null || Selector.Column is ColumnInSelectNode { IsVirtual: true }; - public ColumnInSelectNode(ColumnSelectorNode selector, string? tableAlias) - : base(GetColumnName(selector), selector.Column.Type, tableAlias) - { - Selector = selector; - } - private static string GetColumnName(ColumnSelectorNode selector) { ArgumentGuard.NotNull(selector); diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInTableNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInTableNode.cs index 8e8aab29ce..cd605e72a4 100644 --- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInTableNode.cs +++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInTableNode.cs @@ -8,13 +8,8 @@ namespace DapperExample.TranslationToSql.TreeNodes; /// FROM Users AS t1 /// ]]>. /// -internal sealed class ColumnInTableNode : ColumnNode +internal sealed class ColumnInTableNode(string name, ColumnType type, string? tableAlias) : ColumnNode(name, type, tableAlias) { - public ColumnInTableNode(string name, ColumnType type, string? tableAlias) - : base(name, type, tableAlias) - { - } - public override TResult Accept(SqlTreeNodeVisitor visitor, TArgument argument) { return visitor.VisitColumnInTable(this, argument); diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/CountSelectorNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/CountSelectorNode.cs index 07ad67f144..d0b9e18ca2 100644 --- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/CountSelectorNode.cs +++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/CountSelectorNode.cs @@ -8,13 +8,8 @@ namespace DapperExample.TranslationToSql.TreeNodes; /// SELECT COUNT(*) FROM Users /// ]]>. /// -internal sealed class CountSelectorNode : SelectorNode +internal sealed class CountSelectorNode(string? alias) : SelectorNode(alias) { - public CountSelectorNode(string? alias) - : base(alias) - { - } - public override TResult Accept(SqlTreeNodeVisitor visitor, TArgument argument) { return visitor.VisitCountSelector(this, argument); diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/FromNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/FromNode.cs index 8ec4ab5c20..3d29636212 100644 --- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/FromNode.cs +++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/FromNode.cs @@ -5,13 +5,8 @@ namespace DapperExample.TranslationToSql.TreeNodes; /// FROM Customers AS t1 /// ]]>. /// -internal sealed class FromNode : TableAccessorNode +internal sealed class FromNode(TableSourceNode source) : TableAccessorNode(source) { - public FromNode(TableSourceNode source) - : base(source) - { - } - public override TResult Accept(SqlTreeNodeVisitor visitor, TArgument argument) { return visitor.VisitFrom(this, argument); diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/OneSelectorNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/OneSelectorNode.cs index c86aea6d63..a9c05301a7 100644 --- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/OneSelectorNode.cs +++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/OneSelectorNode.cs @@ -8,13 +8,8 @@ namespace DapperExample.TranslationToSql.TreeNodes; /// SELECT 1 FROM Users /// ]]>. /// -internal sealed class OneSelectorNode : SelectorNode +internal sealed class OneSelectorNode(string? alias) : SelectorNode(alias) { - public OneSelectorNode(string? alias) - : base(alias) - { - } - public override TResult Accept(SqlTreeNodeVisitor visitor, TArgument argument) { return visitor.VisitOneSelector(this, argument); diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/OrderByTermNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/OrderByTermNode.cs index 2c3fc80b3e..89fb4c219d 100644 --- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/OrderByTermNode.cs +++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/OrderByTermNode.cs @@ -3,12 +3,7 @@ namespace DapperExample.TranslationToSql.TreeNodes; /// /// Represents the base type for terms in an . /// -internal abstract class OrderByTermNode : SqlTreeNode +internal abstract class OrderByTermNode(bool isAscending) : SqlTreeNode { - public bool IsAscending { get; } - - protected OrderByTermNode(bool isAscending) - { - IsAscending = isAscending; - } + public bool IsAscending { get; } = isAscending; } diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/SelectorNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/SelectorNode.cs index 8a47a8af66..44ee14ea17 100644 --- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/SelectorNode.cs +++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/SelectorNode.cs @@ -3,12 +3,7 @@ namespace DapperExample.TranslationToSql.TreeNodes; /// /// Represents the base type for selectors in a . /// -internal abstract class SelectorNode : SqlTreeNode +internal abstract class SelectorNode(string? alias) : SqlTreeNode { - public string? Alias { get; } - - protected SelectorNode(string? alias) - { - Alias = alias; - } + public string? Alias { get; } = alias; } diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/TableSourceNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/TableSourceNode.cs index 6628ed11dc..62ff5ad3ef 100644 --- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/TableSourceNode.cs +++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/TableSourceNode.cs @@ -5,17 +5,12 @@ namespace DapperExample.TranslationToSql.TreeNodes; /// /// Represents the base type for tabular data sources, such as database tables and sub-queries. /// -internal abstract class TableSourceNode : SqlTreeNode +internal abstract class TableSourceNode(string? alias) : SqlTreeNode { public const string IdColumnName = nameof(Identifiable.Id); public abstract IReadOnlyList Columns { get; } - public string? Alias { get; } - - protected TableSourceNode(string? alias) - { - Alias = alias; - } + public string? Alias { get; } = alias; public ColumnNode GetIdColumn(string? innerTableAlias) { diff --git a/src/Examples/DatabasePerTenantExample/Data/AppDbContext.cs b/src/Examples/DatabasePerTenantExample/Data/AppDbContext.cs index 40bf7e3f53..7ff84c8e41 100644 --- a/src/Examples/DatabasePerTenantExample/Data/AppDbContext.cs +++ b/src/Examples/DatabasePerTenantExample/Data/AppDbContext.cs @@ -8,21 +8,15 @@ namespace DatabasePerTenantExample.Data; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class AppDbContext : DbContext +public sealed class AppDbContext(DbContextOptions options, IHttpContextAccessor httpContextAccessor, IConfiguration configuration) + : DbContext(options) { - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IConfiguration _configuration; + private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; + private readonly IConfiguration _configuration = configuration; private string? _forcedTenantName; public DbSet Employees => Set(); - public AppDbContext(DbContextOptions options, IHttpContextAccessor httpContextAccessor, IConfiguration configuration) - : base(options) - { - _httpContextAccessor = httpContextAccessor; - _configuration = configuration; - } - public void SetTenantName(string tenantName) { _forcedTenantName = tenantName; diff --git a/src/Examples/GettingStarted/Data/SampleDbContext.cs b/src/Examples/GettingStarted/Data/SampleDbContext.cs index 44662c388b..5e65f8466e 100644 --- a/src/Examples/GettingStarted/Data/SampleDbContext.cs +++ b/src/Examples/GettingStarted/Data/SampleDbContext.cs @@ -5,12 +5,7 @@ namespace GettingStarted.Data; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public class SampleDbContext : DbContext +public class SampleDbContext(DbContextOptions options) : DbContext(options) { public DbSet Books => Set(); - - public SampleDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs index 6dd2bcb9ba..e38b30d861 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs @@ -6,11 +6,6 @@ namespace JsonApiDotNetCoreExample.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) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields); diff --git a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs index dd30287500..e7864a42f6 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs @@ -8,15 +8,10 @@ namespace JsonApiDotNetCoreExample.Data; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class AppDbContext : DbContext +public sealed class AppDbContext(DbContextOptions options) : DbContext(options) { public DbSet TodoItems => Set(); - public AppDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { // When deleting a person, un-assign him/her from existing todo-items. diff --git a/src/Examples/JsonApiDotNetCoreExample/Data/RotatingList.cs b/src/Examples/JsonApiDotNetCoreExample/Data/RotatingList.cs index 778119c6be..c59ea96918 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Data/RotatingList.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Data/RotatingList.cs @@ -16,16 +16,11 @@ public static RotatingList Create(int count, Func createElement) } } -internal sealed class RotatingList +internal sealed class RotatingList(IList elements) { private int _index = -1; - public IList Elements { get; } - - public RotatingList(IList elements) - { - Elements = elements; - } + public IList Elements { get; } = elements; public T GetNext() { diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs index 31aee37585..d94fffa85b 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs @@ -12,22 +12,19 @@ namespace JsonApiDotNetCoreExample.Definitions; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class TodoItemDefinition : JsonApiResourceDefinition +public sealed class TodoItemDefinition( + IResourceGraph resourceGraph, +#if NET6_0 + ISystemClock systemClock +#else + TimeProvider timeProvider +#endif +) : JsonApiResourceDefinition(resourceGraph) { - private readonly Func _getUtcNow; - #if NET6_0 - public TodoItemDefinition(IResourceGraph resourceGraph, ISystemClock systemClock) - : base(resourceGraph) - { - _getUtcNow = () => systemClock.UtcNow; - } + private readonly Func _getUtcNow = () => systemClock.UtcNow; #else - public TodoItemDefinition(IResourceGraph resourceGraph, TimeProvider timeProvider) - : base(resourceGraph) - { - _getUtcNow = timeProvider.GetUtcNow; - } + private readonly Func _getUtcNow = timeProvider.GetUtcNow; #endif public override SortExpression OnApplySort(SortExpression? existingSort) diff --git a/src/Examples/MultiDbContextExample/Data/DbContextA.cs b/src/Examples/MultiDbContextExample/Data/DbContextA.cs index b21e69f2ff..4efd10ea7b 100644 --- a/src/Examples/MultiDbContextExample/Data/DbContextA.cs +++ b/src/Examples/MultiDbContextExample/Data/DbContextA.cs @@ -5,12 +5,7 @@ namespace MultiDbContextExample.Data; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class DbContextA : DbContext +public sealed class DbContextA(DbContextOptions options) : DbContext(options) { public DbSet ResourceAs => Set(); - - public DbContextA(DbContextOptions options) - : base(options) - { - } } diff --git a/src/Examples/MultiDbContextExample/Data/DbContextB.cs b/src/Examples/MultiDbContextExample/Data/DbContextB.cs index 9bc82c5257..faf50c0902 100644 --- a/src/Examples/MultiDbContextExample/Data/DbContextB.cs +++ b/src/Examples/MultiDbContextExample/Data/DbContextB.cs @@ -5,12 +5,7 @@ namespace MultiDbContextExample.Data; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class DbContextB : DbContext +public sealed class DbContextB(DbContextOptions options) : DbContext(options) { public DbSet ResourceBs => Set(); - - public DbContextB(DbContextOptions options) - : base(options) - { - } } diff --git a/src/Examples/MultiDbContextExample/Repositories/DbContextARepository.cs b/src/Examples/MultiDbContextExample/Repositories/DbContextARepository.cs index aadeb889cc..d90b572004 100644 --- a/src/Examples/MultiDbContextExample/Repositories/DbContextARepository.cs +++ b/src/Examples/MultiDbContextExample/Repositories/DbContextARepository.cs @@ -8,13 +8,9 @@ namespace MultiDbContextExample.Repositories; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class DbContextARepository : EntityFrameworkCoreRepository - where TResource : class, IIdentifiable -{ - public DbContextARepository(ITargetedFields targetedFields, DbContextResolver dbContextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, - IResourceDefinitionAccessor resourceDefinitionAccessor) - : base(targetedFields, dbContextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) - { - } -} +public sealed class DbContextARepository( + ITargetedFields targetedFields, DbContextResolver dbContextResolver, IResourceGraph resourceGraph, IResourceFactory resourceFactory, + IEnumerable constraintProviders, ILoggerFactory loggerFactory, IResourceDefinitionAccessor resourceDefinitionAccessor) + : EntityFrameworkCoreRepository(targetedFields, dbContextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, + resourceDefinitionAccessor) + where TResource : class, IIdentifiable; diff --git a/src/Examples/MultiDbContextExample/Repositories/DbContextBRepository.cs b/src/Examples/MultiDbContextExample/Repositories/DbContextBRepository.cs index ac4ce8789c..ed56237d56 100644 --- a/src/Examples/MultiDbContextExample/Repositories/DbContextBRepository.cs +++ b/src/Examples/MultiDbContextExample/Repositories/DbContextBRepository.cs @@ -8,13 +8,9 @@ namespace MultiDbContextExample.Repositories; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class DbContextBRepository : EntityFrameworkCoreRepository - where TResource : class, IIdentifiable -{ - public DbContextBRepository(ITargetedFields targetedFields, DbContextResolver dbContextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, - IResourceDefinitionAccessor resourceDefinitionAccessor) - : base(targetedFields, dbContextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) - { - } -} +public sealed class DbContextBRepository( + ITargetedFields targetedFields, DbContextResolver dbContextResolver, IResourceGraph resourceGraph, IResourceFactory resourceFactory, + IEnumerable constraintProviders, ILoggerFactory loggerFactory, IResourceDefinitionAccessor resourceDefinitionAccessor) + : EntityFrameworkCoreRepository(targetedFields, dbContextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, + resourceDefinitionAccessor) + where TResource : class, IIdentifiable; diff --git a/src/Examples/NoEntityFrameworkExample/InMemoryInverseNavigationResolver.cs b/src/Examples/NoEntityFrameworkExample/InMemoryInverseNavigationResolver.cs index 179eafd516..5e0d6e7bd7 100644 --- a/src/Examples/NoEntityFrameworkExample/InMemoryInverseNavigationResolver.cs +++ b/src/Examples/NoEntityFrameworkExample/InMemoryInverseNavigationResolver.cs @@ -4,14 +4,9 @@ namespace NoEntityFrameworkExample; -internal sealed class InMemoryInverseNavigationResolver : IInverseNavigationResolver +internal sealed class InMemoryInverseNavigationResolver(IResourceGraph resourceGraph) : IInverseNavigationResolver { - private readonly IResourceGraph _resourceGraph; - - public InMemoryInverseNavigationResolver(IResourceGraph resourceGraph) - { - _resourceGraph = resourceGraph; - } + private readonly IResourceGraph _resourceGraph = resourceGraph; /// public void Resolve() diff --git a/src/Examples/NoEntityFrameworkExample/QueryLayerToLinqConverter.cs b/src/Examples/NoEntityFrameworkExample/QueryLayerToLinqConverter.cs index f3eca749eb..29d5f999e9 100644 --- a/src/Examples/NoEntityFrameworkExample/QueryLayerToLinqConverter.cs +++ b/src/Examples/NoEntityFrameworkExample/QueryLayerToLinqConverter.cs @@ -7,16 +7,10 @@ namespace NoEntityFrameworkExample; -internal sealed class QueryLayerToLinqConverter +internal sealed class QueryLayerToLinqConverter(IModel model, IQueryableBuilder queryableBuilder) { - private readonly IModel _model; - private readonly IQueryableBuilder _queryableBuilder; - - public QueryLayerToLinqConverter(IModel model, IQueryableBuilder queryableBuilder) - { - _model = model; - _queryableBuilder = queryableBuilder; - } + private readonly IModel _model = model; + private readonly IQueryableBuilder _queryableBuilder = queryableBuilder; public IEnumerable ApplyQueryLayer(QueryLayer queryLayer, IEnumerable resources) where TResource : class, IIdentifiable diff --git a/src/Examples/NoEntityFrameworkExample/Repositories/PersonRepository.cs b/src/Examples/NoEntityFrameworkExample/Repositories/PersonRepository.cs index 4a2fc5e72a..897af592b7 100644 --- a/src/Examples/NoEntityFrameworkExample/Repositories/PersonRepository.cs +++ b/src/Examples/NoEntityFrameworkExample/Repositories/PersonRepository.cs @@ -7,13 +7,9 @@ namespace NoEntityFrameworkExample.Repositories; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class PersonRepository : InMemoryResourceRepository +public sealed class PersonRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder) + : InMemoryResourceRepository(resourceGraph, queryableBuilder) { - public PersonRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder) - : base(resourceGraph, queryableBuilder) - { - } - protected override IEnumerable GetDataSource() { return Database.People; diff --git a/src/Examples/NoEntityFrameworkExample/Repositories/TagRepository.cs b/src/Examples/NoEntityFrameworkExample/Repositories/TagRepository.cs index 30661d8bc1..30658fb68d 100644 --- a/src/Examples/NoEntityFrameworkExample/Repositories/TagRepository.cs +++ b/src/Examples/NoEntityFrameworkExample/Repositories/TagRepository.cs @@ -7,13 +7,9 @@ namespace NoEntityFrameworkExample.Repositories; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class TagRepository : InMemoryResourceRepository +public sealed class TagRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder) + : InMemoryResourceRepository(resourceGraph, queryableBuilder) { - public TagRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder) - : base(resourceGraph, queryableBuilder) - { - } - protected override IEnumerable GetDataSource() { return Database.Tags; diff --git a/src/Examples/NoEntityFrameworkExample/Repositories/TodoItemRepository.cs b/src/Examples/NoEntityFrameworkExample/Repositories/TodoItemRepository.cs index 8156bf2798..41774b0c8f 100644 --- a/src/Examples/NoEntityFrameworkExample/Repositories/TodoItemRepository.cs +++ b/src/Examples/NoEntityFrameworkExample/Repositories/TodoItemRepository.cs @@ -7,13 +7,9 @@ namespace NoEntityFrameworkExample.Repositories; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class TodoItemRepository : InMemoryResourceRepository +public sealed class TodoItemRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder) + : InMemoryResourceRepository(resourceGraph, queryableBuilder) { - public TodoItemRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder) - : base(resourceGraph, queryableBuilder) - { - } - protected override IEnumerable GetDataSource() { return Database.TodoItems; diff --git a/src/Examples/NoEntityFrameworkExample/Services/TodoItemService.cs b/src/Examples/NoEntityFrameworkExample/Services/TodoItemService.cs index 5f8f96e0c6..294d23978c 100644 --- a/src/Examples/NoEntityFrameworkExample/Services/TodoItemService.cs +++ b/src/Examples/NoEntityFrameworkExample/Services/TodoItemService.cs @@ -9,14 +9,12 @@ namespace NoEntityFrameworkExample.Services; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class TodoItemService : InMemoryResourceService +public sealed class TodoItemService( + IJsonApiOptions options, IResourceGraph resourceGraph, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext, + IEnumerable constraintProviders, IQueryableBuilder queryableBuilder, ILoggerFactory loggerFactory) + : InMemoryResourceService(options, resourceGraph, queryLayerComposer, paginationContext, constraintProviders, queryableBuilder, + loggerFactory) { - public TodoItemService(IJsonApiOptions options, IResourceGraph resourceGraph, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext, - IEnumerable constraintProviders, IQueryableBuilder queryableBuilder, ILoggerFactory loggerFactory) - : base(options, resourceGraph, queryLayerComposer, paginationContext, constraintProviders, queryableBuilder, loggerFactory) - { - } - protected override IEnumerable GetDataSource(ResourceType resourceType) { if (resourceType.ClrType == typeof(TodoItem)) diff --git a/src/Examples/ReportsExample/Services/ReportService.cs b/src/Examples/ReportsExample/Services/ReportService.cs index 61a97ecde5..c04e821347 100644 --- a/src/Examples/ReportsExample/Services/ReportService.cs +++ b/src/Examples/ReportsExample/Services/ReportService.cs @@ -5,14 +5,9 @@ namespace ReportsExample.Services; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public class ReportService : IGetAllService +public class ReportService(ILoggerFactory loggerFactory) : IGetAllService { - private readonly ILogger _logger; - - public ReportService(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } + private readonly ILogger _logger = loggerFactory.CreateLogger(); public Task> GetAsync(CancellationToken cancellationToken) { diff --git a/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs b/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs index 0ce666e342..1e68e5afab 100644 --- a/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs +++ b/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs @@ -6,7 +6,7 @@ namespace JsonApiDotNetCore.SourceGenerators; /// /// Writes the source code for an ASP.NET controller for a JSON:API resource. /// -internal sealed class SourceCodeWriter +internal sealed class SourceCodeWriter(GeneratorExecutionContext context, DiagnosticDescriptor missingIndentInTableErrorDescriptor) { private const int SpacesPerIndent = 4; @@ -41,18 +41,12 @@ internal sealed class SourceCodeWriter [JsonApiEndpointsCopy.DeleteRelationship] = ("IRemoveFromRelationshipService", "removeFromRelationship") }; - private readonly GeneratorExecutionContext _context; - private readonly DiagnosticDescriptor _missingIndentInTableErrorDescriptor; + private readonly GeneratorExecutionContext _context = context; + private readonly DiagnosticDescriptor _missingIndentInTableErrorDescriptor = missingIndentInTableErrorDescriptor; private readonly StringBuilder _sourceBuilder = new(); private int _depth; - public SourceCodeWriter(GeneratorExecutionContext context, DiagnosticDescriptor missingIndentInTableErrorDescriptor) - { - _context = context; - _missingIndentInTableErrorDescriptor = missingIndentInTableErrorDescriptor; - } - public string Write(INamedTypeSymbol resourceType, ITypeSymbol idType, JsonApiEndpointsCopy endpointsToGenerate, string? controllerNamespace, string controllerName, INamedTypeSymbol loggerFactoryInterface) { diff --git a/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs b/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs index 4fe3421dd2..09ebefaf93 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs @@ -90,14 +90,9 @@ private static void AssertSameResourceType(ResourceType currentType, ResourceTyp } } - private sealed class LocalIdState + private sealed class LocalIdState(ResourceType resourceType) { - public ResourceType ResourceType { get; } + public ResourceType ResourceType { get; } = resourceType; public string? ServerId { get; set; } - - public LocalIdState(ResourceType resourceType) - { - ResourceType = resourceType; - } } } diff --git a/src/JsonApiDotNetCore/Configuration/ResourceNameFormatter.cs b/src/JsonApiDotNetCore/Configuration/ResourceNameFormatter.cs index a6e12951a9..6a5a09dcaf 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceNameFormatter.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceNameFormatter.cs @@ -5,14 +5,9 @@ namespace JsonApiDotNetCore.Configuration; -internal sealed class ResourceNameFormatter +internal sealed class ResourceNameFormatter(JsonNamingPolicy? namingPolicy) { - private readonly JsonNamingPolicy? _namingPolicy; - - public ResourceNameFormatter(JsonNamingPolicy? namingPolicy) - { - _namingPolicy = namingPolicy; - } + private readonly JsonNamingPolicy? _namingPolicy = namingPolicy; /// /// Gets the publicly exposed resource name by applying the configured naming convention on the pluralized CLR type name. diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs index 70d65aa7b3..43f75896a0 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs @@ -11,14 +11,10 @@ namespace JsonApiDotNetCore.Controllers; /// The base class to derive atomic:operations controllers from. This class delegates all work to but adds /// attributes for routing templates. If you want to provide routing templates yourself, you should derive from BaseJsonApiOperationsController directly. /// -public abstract class JsonApiOperationsController : BaseJsonApiOperationsController +public abstract class JsonApiOperationsController( + IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, + ITargetedFields targetedFields) : BaseJsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields) { - protected JsonApiOperationsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, - IJsonApiRequest request, ITargetedFields targetedFields) - : base(options, resourceGraph, loggerFactory, processor, request, targetedFields) - { - } - /// [HttpPost] public override Task PostOperationsAsync([FromBody] IList operations, CancellationToken cancellationToken) diff --git a/src/JsonApiDotNetCore/Errors/CannotClearRequiredRelationshipException.cs b/src/JsonApiDotNetCore/Errors/CannotClearRequiredRelationshipException.cs index 97377b0d7b..3ec95be11a 100644 --- a/src/JsonApiDotNetCore/Errors/CannotClearRequiredRelationshipException.cs +++ b/src/JsonApiDotNetCore/Errors/CannotClearRequiredRelationshipException.cs @@ -8,14 +8,9 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when a required relationship is cleared. /// [PublicAPI] -public sealed class CannotClearRequiredRelationshipException : JsonApiException -{ - public CannotClearRequiredRelationshipException(string relationshipName, string resourceType) - : base(new ErrorObject(HttpStatusCode.BadRequest) - { - Title = "Failed to clear a required relationship.", - Detail = $"The relationship '{relationshipName}' on resource type '{resourceType}' cannot be cleared because it is a required relationship." - }) +public sealed class CannotClearRequiredRelationshipException(string relationshipName, string resourceType) : JsonApiException( + new ErrorObject(HttpStatusCode.BadRequest) { - } -} + Title = "Failed to clear a required relationship.", + Detail = $"The relationship '{relationshipName}' on resource type '{resourceType}' cannot be cleared because it is a required relationship." + }); diff --git a/src/JsonApiDotNetCore/Errors/DuplicateLocalIdValueException.cs b/src/JsonApiDotNetCore/Errors/DuplicateLocalIdValueException.cs index 7b714d2b9d..07ac377ae1 100644 --- a/src/JsonApiDotNetCore/Errors/DuplicateLocalIdValueException.cs +++ b/src/JsonApiDotNetCore/Errors/DuplicateLocalIdValueException.cs @@ -8,14 +8,8 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when assigning a local ID that was already assigned in an earlier operation. /// [PublicAPI] -public sealed class DuplicateLocalIdValueException : JsonApiException +public sealed class DuplicateLocalIdValueException(string localId) : JsonApiException(new ErrorObject(HttpStatusCode.BadRequest) { - public DuplicateLocalIdValueException(string localId) - : base(new ErrorObject(HttpStatusCode.BadRequest) - { - Title = "Another local ID with the same name is already defined at this point.", - Detail = $"Another local ID with name '{localId}' is already defined at this point." - }) - { - } -} + Title = "Another local ID with the same name is already defined at this point.", + Detail = $"Another local ID with name '{localId}' is already defined at this point." +}); diff --git a/src/JsonApiDotNetCore/Errors/FailedOperationException.cs b/src/JsonApiDotNetCore/Errors/FailedOperationException.cs index e94b5a7c1b..a6b970bcce 100644 --- a/src/JsonApiDotNetCore/Errors/FailedOperationException.cs +++ b/src/JsonApiDotNetCore/Errors/FailedOperationException.cs @@ -8,18 +8,13 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when an operation in an atomic:operations request failed to be processed for unknown reasons. /// [PublicAPI] -public sealed class FailedOperationException : JsonApiException -{ - public FailedOperationException(int operationIndex, Exception innerException) - : base(new ErrorObject(HttpStatusCode.InternalServerError) - { - Title = "An unhandled error occurred while processing an operation in this request.", - Detail = innerException.Message, - Source = new ErrorSource - { - Pointer = $"/atomic:operations[{operationIndex}]" - } - }, innerException) +public sealed class FailedOperationException(int operationIndex, Exception innerException) : JsonApiException( + new ErrorObject(HttpStatusCode.InternalServerError) { - } -} + Title = "An unhandled error occurred while processing an operation in this request.", + Detail = innerException.Message, + Source = new ErrorSource + { + Pointer = $"/atomic:operations[{operationIndex}]" + } + }, innerException); diff --git a/src/JsonApiDotNetCore/Errors/IncompatibleLocalIdTypeException.cs b/src/JsonApiDotNetCore/Errors/IncompatibleLocalIdTypeException.cs index f3f7b33bb5..e893f04f00 100644 --- a/src/JsonApiDotNetCore/Errors/IncompatibleLocalIdTypeException.cs +++ b/src/JsonApiDotNetCore/Errors/IncompatibleLocalIdTypeException.cs @@ -8,14 +8,9 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when referencing a local ID that was assigned to a different resource type. /// [PublicAPI] -public sealed class IncompatibleLocalIdTypeException : JsonApiException -{ - public IncompatibleLocalIdTypeException(string localId, string declaredType, string currentType) - : base(new ErrorObject(HttpStatusCode.BadRequest) - { - Title = "Incompatible type in Local ID usage.", - Detail = $"Local ID '{localId}' belongs to resource type '{declaredType}' instead of '{currentType}'." - }) +public sealed class IncompatibleLocalIdTypeException(string localId, string declaredType, string currentType) : JsonApiException( + new ErrorObject(HttpStatusCode.BadRequest) { - } -} + Title = "Incompatible type in Local ID usage.", + Detail = $"Local ID '{localId}' belongs to resource type '{declaredType}' instead of '{currentType}'." + }); diff --git a/src/JsonApiDotNetCore/Errors/InvalidConfigurationException.cs b/src/JsonApiDotNetCore/Errors/InvalidConfigurationException.cs index e25fa5244c..0099e57c79 100644 --- a/src/JsonApiDotNetCore/Errors/InvalidConfigurationException.cs +++ b/src/JsonApiDotNetCore/Errors/InvalidConfigurationException.cs @@ -6,10 +6,4 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when configured usage of this library is invalid. /// [PublicAPI] -public sealed class InvalidConfigurationException : Exception -{ - public InvalidConfigurationException(string message, Exception? innerException = null) - : base(message, innerException) - { - } -} +public sealed class InvalidConfigurationException(string message, Exception? innerException = null) : Exception(message, innerException); diff --git a/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs b/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs index ce8ab8a1b0..396694b50a 100644 --- a/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs +++ b/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs @@ -15,14 +15,11 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when ASP.NET ModelState validation fails. /// [PublicAPI] -public sealed class InvalidModelStateException : JsonApiException +public sealed class InvalidModelStateException( + IReadOnlyDictionary modelState, Type modelType, bool includeExceptionStackTraceInErrors, IResourceGraph resourceGraph, + Func? getCollectionElementTypeCallback = null) : JsonApiException(FromModelStateDictionary(modelState, modelType, resourceGraph, + includeExceptionStackTraceInErrors, getCollectionElementTypeCallback)) { - public InvalidModelStateException(IReadOnlyDictionary modelState, Type modelType, bool includeExceptionStackTraceInErrors, - IResourceGraph resourceGraph, Func? getCollectionElementTypeCallback = null) - : base(FromModelStateDictionary(modelState, modelType, resourceGraph, includeExceptionStackTraceInErrors, getCollectionElementTypeCallback)) - { - } - private static IEnumerable FromModelStateDictionary(IReadOnlyDictionary modelState, Type modelType, IResourceGraph resourceGraph, bool includeExceptionStackTraceInErrors, Func? getCollectionElementTypeCallback) { @@ -317,18 +314,14 @@ private static ModelStateKeySegment CreateSegment(Type modelType, string key, bo /// /// Represents an array indexer in a ModelState key, such as "1" in "Customer.Orders[1].Amount". /// - private sealed class ArrayIndexerSegment : ModelStateKeySegment + private sealed class ArrayIndexerSegment( + int arrayIndex, Type modelType, bool isInComplexType, string nextKey, string? sourcePointer, ModelStateKeySegment? parent, + Func? getCollectionElementTypeCallback) : ModelStateKeySegment(modelType, isInComplexType, nextKey, sourcePointer, parent, + getCollectionElementTypeCallback) { private static readonly CollectionConverter CollectionConverter = new(); - public int ArrayIndex { get; } - - public ArrayIndexerSegment(int arrayIndex, Type modelType, bool isInComplexType, string nextKey, string? sourcePointer, ModelStateKeySegment? parent, - Func? getCollectionElementTypeCallback) - : base(modelType, isInComplexType, nextKey, sourcePointer, parent, getCollectionElementTypeCallback) - { - ArrayIndex = arrayIndex; - } + public int ArrayIndex { get; } = arrayIndex; public Type GetCollectionElementType() { diff --git a/src/JsonApiDotNetCore/Errors/InvalidQueryException.cs b/src/JsonApiDotNetCore/Errors/InvalidQueryException.cs index f4332c1af1..76ebd91461 100644 --- a/src/JsonApiDotNetCore/Errors/InvalidQueryException.cs +++ b/src/JsonApiDotNetCore/Errors/InvalidQueryException.cs @@ -9,14 +9,8 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when translating a to Entity Framework Core fails. /// [PublicAPI] -public sealed class InvalidQueryException : JsonApiException +public sealed class InvalidQueryException(string reason, Exception? innerException) : JsonApiException(new ErrorObject(HttpStatusCode.BadRequest) { - public InvalidQueryException(string reason, Exception? innerException) - : base(new ErrorObject(HttpStatusCode.BadRequest) - { - Title = reason, - Detail = innerException?.Message - }, innerException) - { - } -} + Title = reason, + Detail = innerException?.Message +}, innerException); diff --git a/src/JsonApiDotNetCore/Errors/InvalidQueryStringParameterException.cs b/src/JsonApiDotNetCore/Errors/InvalidQueryStringParameterException.cs index 485cf3685b..d676e40111 100644 --- a/src/JsonApiDotNetCore/Errors/InvalidQueryStringParameterException.cs +++ b/src/JsonApiDotNetCore/Errors/InvalidQueryStringParameterException.cs @@ -8,21 +8,16 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when processing the request fails due to an error in the request query string. /// [PublicAPI] -public sealed class InvalidQueryStringParameterException : JsonApiException -{ - public string ParameterName { get; } - - public InvalidQueryStringParameterException(string parameterName, string genericMessage, string specificMessage, Exception? innerException = null) - : base(new ErrorObject(HttpStatusCode.BadRequest) - { - Title = genericMessage, - Detail = specificMessage, - Source = new ErrorSource - { - Parameter = parameterName - } - }, innerException) +public sealed class InvalidQueryStringParameterException(string parameterName, string genericMessage, string specificMessage, Exception? innerException = null) + : JsonApiException(new ErrorObject(HttpStatusCode.BadRequest) { - ParameterName = parameterName; - } + Title = genericMessage, + Detail = specificMessage, + Source = new ErrorSource + { + Parameter = parameterName + } + }, innerException) +{ + public string ParameterName { get; } = parameterName; } diff --git a/src/JsonApiDotNetCore/Errors/InvalidRequestBodyException.cs b/src/JsonApiDotNetCore/Errors/InvalidRequestBodyException.cs index d8d752ece4..25297a4056 100644 --- a/src/JsonApiDotNetCore/Errors/InvalidRequestBodyException.cs +++ b/src/JsonApiDotNetCore/Errors/InvalidRequestBodyException.cs @@ -8,27 +8,22 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when deserializing the request body fails. /// [PublicAPI] -public sealed class InvalidRequestBodyException : JsonApiException +public sealed class InvalidRequestBodyException( + string? requestBody, string? genericMessage, string? specificMessage, string? sourcePointer, HttpStatusCode? alternativeStatusCode = null, + Exception? innerException = null) : JsonApiException(new ErrorObject(alternativeStatusCode ?? HttpStatusCode.UnprocessableEntity) { - public InvalidRequestBodyException(string? requestBody, string? genericMessage, string? specificMessage, string? sourcePointer, - HttpStatusCode? alternativeStatusCode = null, Exception? innerException = null) - : base(new ErrorObject(alternativeStatusCode ?? HttpStatusCode.UnprocessableEntity) + Title = genericMessage != null ? $"Failed to deserialize request body: {genericMessage}" : "Failed to deserialize request body.", + Detail = specificMessage, + Source = sourcePointer == null + ? null + : new ErrorSource { - Title = genericMessage != null ? $"Failed to deserialize request body: {genericMessage}" : "Failed to deserialize request body.", - Detail = specificMessage, - Source = sourcePointer == null - ? null - : new ErrorSource - { - Pointer = sourcePointer - }, - Meta = string.IsNullOrEmpty(requestBody) - ? null - : new Dictionary - { - ["RequestBody"] = requestBody - } - }, innerException) - { - } -} + Pointer = sourcePointer + }, + Meta = string.IsNullOrEmpty(requestBody) + ? null + : new Dictionary + { + ["RequestBody"] = requestBody + } +}, innerException); diff --git a/src/JsonApiDotNetCore/Errors/LocalIdSingleOperationException.cs b/src/JsonApiDotNetCore/Errors/LocalIdSingleOperationException.cs index d99eb6dfbb..e96f78672f 100644 --- a/src/JsonApiDotNetCore/Errors/LocalIdSingleOperationException.cs +++ b/src/JsonApiDotNetCore/Errors/LocalIdSingleOperationException.cs @@ -8,14 +8,8 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when assigning and referencing a local ID within the same operation. /// [PublicAPI] -public sealed class LocalIdSingleOperationException : JsonApiException +public sealed class LocalIdSingleOperationException(string localId) : JsonApiException(new ErrorObject(HttpStatusCode.BadRequest) { - public LocalIdSingleOperationException(string localId) - : base(new ErrorObject(HttpStatusCode.BadRequest) - { - Title = "Local ID cannot be both defined and used within the same operation.", - Detail = $"Local ID '{localId}' cannot be both defined and used within the same operation." - }) - { - } -} + Title = "Local ID cannot be both defined and used within the same operation.", + Detail = $"Local ID '{localId}' cannot be both defined and used within the same operation." +}); diff --git a/src/JsonApiDotNetCore/Errors/MissingTransactionSupportException.cs b/src/JsonApiDotNetCore/Errors/MissingTransactionSupportException.cs index f67dd0d243..e682a5fac2 100644 --- a/src/JsonApiDotNetCore/Errors/MissingTransactionSupportException.cs +++ b/src/JsonApiDotNetCore/Errors/MissingTransactionSupportException.cs @@ -8,14 +8,8 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when accessing a repository that does not support transactions during an atomic:operations request. /// [PublicAPI] -public sealed class MissingTransactionSupportException : JsonApiException +public sealed class MissingTransactionSupportException(string resourceType) : JsonApiException(new ErrorObject(HttpStatusCode.UnprocessableEntity) { - public MissingTransactionSupportException(string resourceType) - : base(new ErrorObject(HttpStatusCode.UnprocessableEntity) - { - Title = "Unsupported resource type in atomic:operations request.", - Detail = $"Operations on resources of type '{resourceType}' cannot be used because transaction support is unavailable." - }) - { - } -} + Title = "Unsupported resource type in atomic:operations request.", + Detail = $"Operations on resources of type '{resourceType}' cannot be used because transaction support is unavailable." +}); diff --git a/src/JsonApiDotNetCore/Errors/NonParticipatingTransactionException.cs b/src/JsonApiDotNetCore/Errors/NonParticipatingTransactionException.cs index 09e116c4c1..b4855e27ff 100644 --- a/src/JsonApiDotNetCore/Errors/NonParticipatingTransactionException.cs +++ b/src/JsonApiDotNetCore/Errors/NonParticipatingTransactionException.cs @@ -8,14 +8,8 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when a repository does not participate in the overarching transaction during an atomic:operations request. /// [PublicAPI] -public sealed class NonParticipatingTransactionException : JsonApiException +public sealed class NonParticipatingTransactionException() : JsonApiException(new ErrorObject(HttpStatusCode.UnprocessableEntity) { - public NonParticipatingTransactionException() - : base(new ErrorObject(HttpStatusCode.UnprocessableEntity) - { - Title = "Unsupported combination of resource types in atomic:operations request.", - Detail = "All operations need to participate in a single shared transaction, which is not the case for this request." - }) - { - } -} + Title = "Unsupported combination of resource types in atomic:operations request.", + Detail = "All operations need to participate in a single shared transaction, which is not the case for this request." +}); diff --git a/src/JsonApiDotNetCore/Errors/RelationshipNotFoundException.cs b/src/JsonApiDotNetCore/Errors/RelationshipNotFoundException.cs index 5c4d59e479..11a72f803f 100644 --- a/src/JsonApiDotNetCore/Errors/RelationshipNotFoundException.cs +++ b/src/JsonApiDotNetCore/Errors/RelationshipNotFoundException.cs @@ -8,14 +8,8 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when a relationship does not exist. /// [PublicAPI] -public sealed class RelationshipNotFoundException : JsonApiException +public sealed class RelationshipNotFoundException(string relationshipName, string resourceType) : JsonApiException(new ErrorObject(HttpStatusCode.NotFound) { - public RelationshipNotFoundException(string relationshipName, string resourceType) - : base(new ErrorObject(HttpStatusCode.NotFound) - { - Title = "The requested relationship does not exist.", - Detail = $"Resource of type '{resourceType}' does not contain a relationship named '{relationshipName}'." - }) - { - } -} + Title = "The requested relationship does not exist.", + Detail = $"Resource of type '{resourceType}' does not contain a relationship named '{relationshipName}'." +}); diff --git a/src/JsonApiDotNetCore/Errors/ResourceAlreadyExistsException.cs b/src/JsonApiDotNetCore/Errors/ResourceAlreadyExistsException.cs index eb4a444d42..d2941422b9 100644 --- a/src/JsonApiDotNetCore/Errors/ResourceAlreadyExistsException.cs +++ b/src/JsonApiDotNetCore/Errors/ResourceAlreadyExistsException.cs @@ -8,14 +8,8 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when creating a resource with an ID that already exists. /// [PublicAPI] -public sealed class ResourceAlreadyExistsException : JsonApiException +public sealed class ResourceAlreadyExistsException(string resourceId, string resourceType) : JsonApiException(new ErrorObject(HttpStatusCode.Conflict) { - public ResourceAlreadyExistsException(string resourceId, string resourceType) - : base(new ErrorObject(HttpStatusCode.Conflict) - { - Title = "Another resource with the specified ID already exists.", - Detail = $"Another resource of type '{resourceType}' with ID '{resourceId}' already exists." - }) - { - } -} + Title = "Another resource with the specified ID already exists.", + Detail = $"Another resource of type '{resourceType}' with ID '{resourceId}' already exists." +}); diff --git a/src/JsonApiDotNetCore/Errors/ResourceNotFoundException.cs b/src/JsonApiDotNetCore/Errors/ResourceNotFoundException.cs index 28b60851c3..5b2b2a829f 100644 --- a/src/JsonApiDotNetCore/Errors/ResourceNotFoundException.cs +++ b/src/JsonApiDotNetCore/Errors/ResourceNotFoundException.cs @@ -8,14 +8,8 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when a resource does not exist. /// [PublicAPI] -public sealed class ResourceNotFoundException : JsonApiException +public sealed class ResourceNotFoundException(string resourceId, string resourceType) : JsonApiException(new ErrorObject(HttpStatusCode.NotFound) { - public ResourceNotFoundException(string resourceId, string resourceType) - : base(new ErrorObject(HttpStatusCode.NotFound) - { - Title = "The requested resource does not exist.", - Detail = $"Resource of type '{resourceType}' with ID '{resourceId}' does not exist." - }) - { - } -} + Title = "The requested resource does not exist.", + Detail = $"Resource of type '{resourceType}' with ID '{resourceId}' does not exist." +}); diff --git a/src/JsonApiDotNetCore/Errors/ResourcesInRelationshipsNotFoundException.cs b/src/JsonApiDotNetCore/Errors/ResourcesInRelationshipsNotFoundException.cs index f40f76188b..81d0a14dc8 100644 --- a/src/JsonApiDotNetCore/Errors/ResourcesInRelationshipsNotFoundException.cs +++ b/src/JsonApiDotNetCore/Errors/ResourcesInRelationshipsNotFoundException.cs @@ -8,13 +8,9 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when referencing one or more non-existing resources in one or more relationships. /// [PublicAPI] -public sealed class ResourcesInRelationshipsNotFoundException : JsonApiException +public sealed class ResourcesInRelationshipsNotFoundException(IEnumerable missingResources) + : JsonApiException(missingResources.Select(CreateError)) { - public ResourcesInRelationshipsNotFoundException(IEnumerable missingResources) - : base(missingResources.Select(CreateError)) - { - } - private static ErrorObject CreateError(MissingResourceInRelationship missingResourceInRelationship) { return new ErrorObject(HttpStatusCode.NotFound) diff --git a/src/JsonApiDotNetCore/Errors/RouteNotAvailableException.cs b/src/JsonApiDotNetCore/Errors/RouteNotAvailableException.cs index 80a368379b..0bd1a3debd 100644 --- a/src/JsonApiDotNetCore/Errors/RouteNotAvailableException.cs +++ b/src/JsonApiDotNetCore/Errors/RouteNotAvailableException.cs @@ -8,17 +8,11 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when a request is received for an HTTP route that is not exposed. /// [PublicAPI] -public sealed class RouteNotAvailableException : JsonApiException +public sealed class RouteNotAvailableException(HttpMethod method, string route) : JsonApiException(new ErrorObject(HttpStatusCode.Forbidden) { - public HttpMethod Method { get; } - - public RouteNotAvailableException(HttpMethod method, string route) - : base(new ErrorObject(HttpStatusCode.Forbidden) - { - Title = "The requested endpoint is not accessible.", - Detail = $"Endpoint '{route}' is not accessible for {method} requests." - }) - { - Method = method; - } + Title = "The requested endpoint is not accessible.", + Detail = $"Endpoint '{route}' is not accessible for {method} requests." +}) +{ + public HttpMethod Method { get; } = method; } diff --git a/src/JsonApiDotNetCore/Errors/UnknownLocalIdValueException.cs b/src/JsonApiDotNetCore/Errors/UnknownLocalIdValueException.cs index b5eeef052e..4cd5e3d0de 100644 --- a/src/JsonApiDotNetCore/Errors/UnknownLocalIdValueException.cs +++ b/src/JsonApiDotNetCore/Errors/UnknownLocalIdValueException.cs @@ -8,14 +8,8 @@ namespace JsonApiDotNetCore.Errors; /// The error that is thrown when referencing a local ID that hasn't been assigned. /// [PublicAPI] -public sealed class UnknownLocalIdValueException : JsonApiException +public sealed class UnknownLocalIdValueException(string localId) : JsonApiException(new ErrorObject(HttpStatusCode.BadRequest) { - public UnknownLocalIdValueException(string localId) - : base(new ErrorObject(HttpStatusCode.BadRequest) - { - Title = "Server-generated value for local ID is not available at this point.", - Detail = $"Server-generated value for local ID '{localId}' is not available at this point." - }) - { - } -} + Title = "Server-generated value for local ID is not available at this point.", + Detail = $"Server-generated value for local ID '{localId}' is not available at this point." +}); diff --git a/src/JsonApiDotNetCore/Middleware/TraceLogWriter.cs b/src/JsonApiDotNetCore/Middleware/TraceLogWriter.cs index 0b20b897d1..c02b8c02d3 100644 --- a/src/JsonApiDotNetCore/Middleware/TraceLogWriter.cs +++ b/src/JsonApiDotNetCore/Middleware/TraceLogWriter.cs @@ -118,17 +118,12 @@ public override void Write(Utf8JsonWriter writer, TWrapper value, JsonSerializer } } -internal sealed class TraceLogWriter : TraceLogWriter +internal sealed class TraceLogWriter(ILoggerFactory loggerFactory) : TraceLogWriter { - private readonly ILogger _logger; + private readonly ILogger _logger = loggerFactory.CreateLogger(typeof(T)); private bool IsEnabled => _logger.IsEnabled(LogLevel.Trace); - public TraceLogWriter(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(typeof(T)); - } - public void LogMethodStart(object? parameters = null, [CallerMemberName] string memberName = "") { if (IsEnabled) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/PaginationElementQueryStringValueExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/PaginationElementQueryStringValueExpression.cs index 184fd7a3c1..7e39ca5735 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/PaginationElementQueryStringValueExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/PaginationElementQueryStringValueExpression.cs @@ -10,29 +10,22 @@ namespace JsonApiDotNetCore.Queries.Expressions; /// . /// [PublicAPI] -public class PaginationElementQueryStringValueExpression : QueryExpression +public class PaginationElementQueryStringValueExpression(ResourceFieldChainExpression? scope, int value, int position) : QueryExpression { /// /// The relationship this pagination applies to. Chain format: zero or more relationships, followed by a to-many relationship. /// - public ResourceFieldChainExpression? Scope { get; } + public ResourceFieldChainExpression? Scope { get; } = scope; /// /// The numeric pagination value. /// - public int Value { get; } + public int Value { get; } = value; /// /// The zero-based position in the text of the query string parameter value. /// - public int Position { get; } - - public PaginationElementQueryStringValueExpression(ResourceFieldChainExpression? scope, int value, int position) - { - Scope = scope; - Value = value; - Position = position; - } + public int Position { get; } = position; public override TResult Accept(QueryExpressionVisitor visitor, TArgument argument) { diff --git a/src/JsonApiDotNetCore/Queries/IndentingStringWriter.cs b/src/JsonApiDotNetCore/Queries/IndentingStringWriter.cs index 2d5a366c28..e5f39b6c77 100644 --- a/src/JsonApiDotNetCore/Queries/IndentingStringWriter.cs +++ b/src/JsonApiDotNetCore/Queries/IndentingStringWriter.cs @@ -2,17 +2,12 @@ namespace JsonApiDotNetCore.Queries; -internal sealed class IndentingStringWriter : IDisposable +internal sealed class IndentingStringWriter(StringBuilder builder) : IDisposable { - private readonly StringBuilder _builder; + private readonly StringBuilder _builder = builder; private int _indentDepth; - public IndentingStringWriter(StringBuilder builder) - { - _builder = builder; - } - public void WriteLine(string? line) { if (_indentDepth > 0) diff --git a/src/JsonApiDotNetCore/Queries/Parsing/FilterParser.cs b/src/JsonApiDotNetCore/Queries/Parsing/FilterParser.cs index 19757bbd08..077d727368 100644 --- a/src/JsonApiDotNetCore/Queries/Parsing/FilterParser.cs +++ b/src/JsonApiDotNetCore/Queries/Parsing/FilterParser.cs @@ -585,14 +585,9 @@ private void AssertResourceTypeStackIsEmpty() } } - private sealed class PopResourceTypeOnDispose : IDisposable + private sealed class PopResourceTypeOnDispose(Stack resourceTypeStack) : IDisposable { - private readonly Stack _resourceTypeStack; - - public PopResourceTypeOnDispose(Stack resourceTypeStack) - { - _resourceTypeStack = resourceTypeStack; - } + private readonly Stack _resourceTypeStack = resourceTypeStack; public void Dispose() { diff --git a/src/JsonApiDotNetCore/Queries/Parsing/Token.cs b/src/JsonApiDotNetCore/Queries/Parsing/Token.cs index 4700127e1d..b4751288bf 100644 --- a/src/JsonApiDotNetCore/Queries/Parsing/Token.cs +++ b/src/JsonApiDotNetCore/Queries/Parsing/Token.cs @@ -3,17 +3,11 @@ namespace JsonApiDotNetCore.Queries.Parsing; [PublicAPI] -public class Token +public class Token(TokenKind kind, int position) { - public TokenKind Kind { get; } + public TokenKind Kind { get; } = kind; public string? Value { get; } - public int Position { get; } - - public Token(TokenKind kind, int position) - { - Kind = kind; - Position = position; - } + public int Position { get; } = position; public Token(TokenKind kind, string value, int position) : this(kind, position) diff --git a/src/JsonApiDotNetCore/QueryStrings/FieldChains/FieldChainFormatException.cs b/src/JsonApiDotNetCore/QueryStrings/FieldChains/FieldChainFormatException.cs index 8156e1474e..d4e0d35d55 100644 --- a/src/JsonApiDotNetCore/QueryStrings/FieldChains/FieldChainFormatException.cs +++ b/src/JsonApiDotNetCore/QueryStrings/FieldChains/FieldChainFormatException.cs @@ -3,16 +3,10 @@ namespace JsonApiDotNetCore.QueryStrings.FieldChains; /// /// The exception that is thrown when the format of a dot-separated resource field chain is invalid. /// -internal sealed class FieldChainFormatException : FormatException +internal sealed class FieldChainFormatException(int position, string message) : FormatException(message) { /// /// Gets the zero-based error position in the field chain, or at its end. /// - public int Position { get; } - - public FieldChainFormatException(int position, string message) - : base(message) - { - Position = position; - } + public int Position { get; } = position; } diff --git a/src/JsonApiDotNetCore/QueryStrings/FieldChains/PatternFormatException.cs b/src/JsonApiDotNetCore/QueryStrings/FieldChains/PatternFormatException.cs index 0a1fa13d2c..04a73f7f72 100644 --- a/src/JsonApiDotNetCore/QueryStrings/FieldChains/PatternFormatException.cs +++ b/src/JsonApiDotNetCore/QueryStrings/FieldChains/PatternFormatException.cs @@ -6,22 +6,15 @@ namespace JsonApiDotNetCore.QueryStrings.FieldChains; /// The exception that is thrown when the format of a is invalid. /// [PublicAPI] -public sealed class PatternFormatException : FormatException +public sealed class PatternFormatException(string pattern, int position, string message) : FormatException(message) { /// /// Gets the text of the invalid pattern. /// - public string Pattern { get; } + public string Pattern { get; } = pattern; /// /// Gets the zero-based error position in , or at its end. /// - public int Position { get; } - - public PatternFormatException(string pattern, int position, string message) - : base(message) - { - Pattern = pattern; - Position = position; - } + public int Position { get; } = position; } diff --git a/src/JsonApiDotNetCore/Repositories/DataStoreUpdateException.cs b/src/JsonApiDotNetCore/Repositories/DataStoreUpdateException.cs index e823b50077..4855c7fb6a 100644 --- a/src/JsonApiDotNetCore/Repositories/DataStoreUpdateException.cs +++ b/src/JsonApiDotNetCore/Repositories/DataStoreUpdateException.cs @@ -6,10 +6,4 @@ namespace JsonApiDotNetCore.Repositories; /// The error that is thrown when the underlying data store is unable to persist changes. /// [PublicAPI] -public sealed class DataStoreUpdateException : Exception -{ - public DataStoreUpdateException(Exception? innerException) - : base("Failed to persist changes in the underlying data store.", innerException) - { - } -} +public sealed class DataStoreUpdateException(Exception? innerException) : Exception("Failed to persist changes in the underlying data store.", innerException); diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ErrorObject.cs b/src/JsonApiDotNetCore/Serialization/Objects/ErrorObject.cs index 68a2a19d3a..63d95174cd 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ErrorObject.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ErrorObject.cs @@ -8,7 +8,7 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// See https://jsonapi.org/format/#error-objects. /// [PublicAPI] -public sealed class ErrorObject +public sealed class ErrorObject(HttpStatusCode statusCode) { [JsonPropertyName("id")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -19,7 +19,7 @@ public sealed class ErrorObject public ErrorLinks? Links { get; set; } [JsonIgnore] - public HttpStatusCode StatusCode { get; set; } + public HttpStatusCode StatusCode { get; set; } = statusCode; [JsonPropertyName("status")] [JsonIgnore(Condition = JsonIgnoreCondition.Never)] @@ -49,11 +49,6 @@ public string Status [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IDictionary? Meta { get; set; } - public ErrorObject(HttpStatusCode statusCode) - { - StatusCode = statusCode; - } - public static HttpStatusCode GetResponseStatusCode(IReadOnlyList errorObjects) { if (errorObjects.IsNullOrEmpty()) diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceAdapter.cs index f7a5ad82fa..8e682439aa 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceAdapter.cs @@ -8,13 +8,9 @@ namespace JsonApiDotNetCore.Serialization.Request.Adapters; /// [PublicAPI] -public sealed class AtomicReferenceAdapter : ResourceIdentityAdapter, IAtomicReferenceAdapter +public sealed class AtomicReferenceAdapter(IResourceGraph resourceGraph, IResourceFactory resourceFactory) + : ResourceIdentityAdapter(resourceGraph, resourceFactory), IAtomicReferenceAdapter { - public AtomicReferenceAdapter(IResourceGraph resourceGraph, IResourceFactory resourceFactory) - : base(resourceGraph, resourceFactory) - { - } - /// public AtomicReferenceResult Convert(AtomicReference atomicReference, ResourceIdentityRequirements requirements, RequestAdapterState state) { diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/RequestAdapterPosition.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/RequestAdapterPosition.cs index 644383e711..3b114f61f4 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/RequestAdapterPosition.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/RequestAdapterPosition.cs @@ -55,14 +55,9 @@ public override string ToString() return ToSourcePointer() ?? string.Empty; } - private sealed class PopStackOnDispose : IDisposable + private sealed class PopStackOnDispose(RequestAdapterPosition owner) : IDisposable { - private readonly RequestAdapterPosition _owner; - - public PopStackOnDispose(RequestAdapterPosition owner) - { - _owner = owner; - } + private readonly RequestAdapterPosition _owner = owner; public void Dispose() { diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceDataInOperationsRequestAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceDataInOperationsRequestAdapter.cs index afccb303b5..7c299b0b5e 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceDataInOperationsRequestAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceDataInOperationsRequestAdapter.cs @@ -5,13 +5,9 @@ namespace JsonApiDotNetCore.Serialization.Request.Adapters; /// -public sealed class ResourceDataInOperationsRequestAdapter : ResourceDataAdapter, IResourceDataInOperationsRequestAdapter +public sealed class ResourceDataInOperationsRequestAdapter(IResourceDefinitionAccessor resourceDefinitionAccessor, IResourceObjectAdapter resourceObjectAdapter) + : ResourceDataAdapter(resourceDefinitionAccessor, resourceObjectAdapter), IResourceDataInOperationsRequestAdapter { - public ResourceDataInOperationsRequestAdapter(IResourceDefinitionAccessor resourceDefinitionAccessor, IResourceObjectAdapter resourceObjectAdapter) - : base(resourceDefinitionAccessor, resourceObjectAdapter) - { - } - protected override (IIdentifiable resource, ResourceType resourceType) ConvertResourceObject(SingleOrManyData data, ResourceIdentityRequirements requirements, RequestAdapterState state) { diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentifierObjectAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentifierObjectAdapter.cs index 8032c6c60c..bc9d380388 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentifierObjectAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentifierObjectAdapter.cs @@ -5,13 +5,9 @@ namespace JsonApiDotNetCore.Serialization.Request.Adapters; /// -public sealed class ResourceIdentifierObjectAdapter : ResourceIdentityAdapter, IResourceIdentifierObjectAdapter +public sealed class ResourceIdentifierObjectAdapter(IResourceGraph resourceGraph, IResourceFactory resourceFactory) + : ResourceIdentityAdapter(resourceGraph, resourceFactory), IResourceIdentifierObjectAdapter { - public ResourceIdentifierObjectAdapter(IResourceGraph resourceGraph, IResourceFactory resourceFactory) - : base(resourceGraph, resourceFactory) - { - } - /// public IIdentifiable Convert(ResourceIdentifierObject resourceIdentifierObject, ResourceIdentityRequirements requirements, RequestAdapterState state) { diff --git a/test/DapperTests/IntegrationTests/SqlTextAdapter.cs b/test/DapperTests/IntegrationTests/SqlTextAdapter.cs index a88646c7a7..de860f5814 100644 --- a/test/DapperTests/IntegrationTests/SqlTextAdapter.cs +++ b/test/DapperTests/IntegrationTests/SqlTextAdapter.cs @@ -3,7 +3,7 @@ namespace DapperTests.IntegrationTests; -internal sealed class SqlTextAdapter +internal sealed class SqlTextAdapter(DatabaseProvider databaseProvider) { private static readonly Dictionary SqlServerReplacements = new() { @@ -11,12 +11,7 @@ [new Regex("\"([^\"]+)\"", RegexOptions.Compiled)] = "[$+]", [new Regex($@"(VALUES \([^)]*\)){Environment.NewLine}RETURNING \[Id\]", RegexOptions.Compiled)] = $"OUTPUT INSERTED.[Id]{Environment.NewLine}$1" }; - private readonly DatabaseProvider _databaseProvider; - - public SqlTextAdapter(DatabaseProvider databaseProvider) - { - _databaseProvider = databaseProvider; - } + private readonly DatabaseProvider _databaseProvider = databaseProvider; public string Adapt(string text, bool hasClientGeneratedId) { diff --git a/test/DiscoveryTests/PrivateResourceDefinition.cs b/test/DiscoveryTests/PrivateResourceDefinition.cs index 25ff719718..80883eaa6d 100644 --- a/test/DiscoveryTests/PrivateResourceDefinition.cs +++ b/test/DiscoveryTests/PrivateResourceDefinition.cs @@ -5,10 +5,4 @@ namespace DiscoveryTests; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class PrivateResourceDefinition : JsonApiResourceDefinition -{ - public PrivateResourceDefinition(IResourceGraph resourceGraph) - : base(resourceGraph) - { - } -} +public sealed class PrivateResourceDefinition(IResourceGraph resourceGraph) : JsonApiResourceDefinition(resourceGraph); diff --git a/test/DiscoveryTests/PrivateResourceRepository.cs b/test/DiscoveryTests/PrivateResourceRepository.cs index cb654ea724..eb33d18440 100644 --- a/test/DiscoveryTests/PrivateResourceRepository.cs +++ b/test/DiscoveryTests/PrivateResourceRepository.cs @@ -8,12 +8,8 @@ namespace DiscoveryTests; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class PrivateResourceRepository : EntityFrameworkCoreRepository -{ - public PrivateResourceRepository(ITargetedFields targetedFields, IDbContextResolver dbContextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, - IResourceDefinitionAccessor resourceDefinitionAccessor) - : base(targetedFields, dbContextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) - { - } -} +public sealed class PrivateResourceRepository( + ITargetedFields targetedFields, IDbContextResolver dbContextResolver, IResourceGraph resourceGraph, IResourceFactory resourceFactory, + IEnumerable constraintProviders, ILoggerFactory loggerFactory, IResourceDefinitionAccessor resourceDefinitionAccessor) + : EntityFrameworkCoreRepository(targetedFields, dbContextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, + resourceDefinitionAccessor); diff --git a/test/DiscoveryTests/PrivateResourceService.cs b/test/DiscoveryTests/PrivateResourceService.cs index 6d289eafb1..751d04baab 100644 --- a/test/DiscoveryTests/PrivateResourceService.cs +++ b/test/DiscoveryTests/PrivateResourceService.cs @@ -10,12 +10,8 @@ namespace DiscoveryTests; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class PrivateResourceService : JsonApiResourceService -{ - public PrivateResourceService(IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext, - IJsonApiOptions options, ILoggerFactory loggerFactory, IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker, - IResourceDefinitionAccessor resourceDefinitionAccessor) - : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, resourceDefinitionAccessor) - { - } -} +public sealed class PrivateResourceService( + IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext, IJsonApiOptions options, + ILoggerFactory loggerFactory, IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker, + IResourceDefinitionAccessor resourceDefinitionAccessor) : JsonApiResourceService(repositoryAccessor, queryLayerComposer, + paginationContext, options, loggerFactory, request, resourceChangeTracker, resourceDefinitionAccessor); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs index 19a0208b17..5ec385081b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs @@ -13,23 +13,16 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Archiving; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class TelevisionBroadcastDefinition : JsonApiResourceDefinition +public sealed class TelevisionBroadcastDefinition( + IResourceGraph resourceGraph, TelevisionDbContext dbContext, IJsonApiRequest request, IEnumerable constraintProviders) + : JsonApiResourceDefinition(resourceGraph) { - private readonly TelevisionDbContext _dbContext; - private readonly IJsonApiRequest _request; - private readonly IEnumerable _constraintProviders; + private readonly TelevisionDbContext _dbContext = dbContext; + private readonly IJsonApiRequest _request = request; + private readonly IEnumerable _constraintProviders = constraintProviders; private DateTimeOffset? _storedArchivedAt; - public TelevisionBroadcastDefinition(IResourceGraph resourceGraph, TelevisionDbContext dbContext, IJsonApiRequest request, - IEnumerable constraintProviders) - : base(resourceGraph) - { - _dbContext = dbContext; - _request = request; - _constraintProviders = constraintProviders; - } - public override FilterExpression? OnApplyFilter(FilterExpression? existingFilter) { if (_request.IsReadOnly) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Archiving/TelevisionDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Archiving/TelevisionDbContext.cs index f378dd5846..b05a82f382 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Archiving/TelevisionDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Archiving/TelevisionDbContext.cs @@ -5,15 +5,10 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Archiving; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class TelevisionDbContext : TestableDbContext +public sealed class TelevisionDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Networks => Set(); public DbSet Stations => Set(); public DbSet Broadcasts => Set(); public DbSet Comments => Set(); - - public TelevisionDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs index 5c9647f6f7..8285661ede 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs @@ -14,14 +14,10 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.Controllers; [DisableRoutingConvention] [Route("/operations/musicTracks/create")] -public sealed class CreateMusicTrackOperationsController : JsonApiOperationsController +public sealed class CreateMusicTrackOperationsController( + IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, + ITargetedFields targetedFields) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields) { - public CreateMusicTrackOperationsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IOperationsProcessor processor, IJsonApiRequest request, ITargetedFields targetedFields) - : base(options, resourceGraph, loggerFactory, processor, request, targetedFields) - { - } - public override Task PostOperationsAsync(IList operations, CancellationToken cancellationToken) { AssertOnlyCreatingMusicTracks(operations); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/ImplicitlyChangingTextLanguageDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/ImplicitlyChangingTextLanguageDefinition.cs index 4548e31c16..fedd490ddd 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/ImplicitlyChangingTextLanguageDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/ImplicitlyChangingTextLanguageDefinition.cs @@ -9,17 +9,12 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations; /// Used to simulate side effects that occur in the database while saving, typically caused by database triggers. /// [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public class ImplicitlyChangingTextLanguageDefinition : HitCountingResourceDefinition +public class ImplicitlyChangingTextLanguageDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter, OperationsDbContext dbContext) + : HitCountingResourceDefinition(resourceGraph, hitCounter) { internal const string Suffix = " (changed)"; - private readonly OperationsDbContext _dbContext; - - public ImplicitlyChangingTextLanguageDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter, OperationsDbContext dbContext) - : base(resourceGraph, hitCounter) - { - _dbContext = dbContext; - } + private readonly OperationsDbContext _dbContext = dbContext; public override async Task OnWriteSucceededAsync(TextLanguage resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs index be59668c1c..3c8dda12ab 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs @@ -4,15 +4,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.Meta; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class MusicTrackMetaDefinition : HitCountingResourceDefinition +public sealed class MusicTrackMetaDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter) + : HitCountingResourceDefinition(resourceGraph, hitCounter) { protected override ResourceDefinitionExtensibilityPoints ExtensibilityPointsToTrack => ResourceDefinitionExtensibilityPoints.GetMeta; - public MusicTrackMetaDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter) - : base(resourceGraph, hitCounter) - { - } - public override IDictionary GetMeta(MusicTrack resource) { base.GetMeta(resource); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs index f46b2940a6..679a3b76a9 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs @@ -4,17 +4,13 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.Meta; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class TextLanguageMetaDefinition : ImplicitlyChangingTextLanguageDefinition +public sealed class TextLanguageMetaDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter, OperationsDbContext dbContext) + : ImplicitlyChangingTextLanguageDefinition(resourceGraph, hitCounter, dbContext) { internal const string NoticeText = "See https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes for ISO 639-1 language codes."; protected override ResourceDefinitionExtensibilityPoints ExtensibilityPointsToTrack => ResourceDefinitionExtensibilityPoints.GetMeta; - public TextLanguageMetaDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter, OperationsDbContext dbContext) - : base(resourceGraph, hitCounter, dbContext) - { - } - public override IDictionary GetMeta(TextLanguage resource) { base.GetMeta(resource); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicLoggingTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicLoggingTests.cs index bae5abf988..58824a8bf9 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicLoggingTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicLoggingTests.cs @@ -135,17 +135,12 @@ public Task BeginTransactionAsync(CancellationToken canc return Task.FromResult(transaction); } - private sealed class ThrowingOperationsTransaction : IOperationsTransaction + private sealed class ThrowingOperationsTransaction(ThrowingOperationsTransactionFactory owner) : IOperationsTransaction { - private readonly ThrowingOperationsTransactionFactory _owner; + private readonly ThrowingOperationsTransactionFactory _owner = owner; public string TransactionId => "some"; - public ThrowingOperationsTransaction(ThrowingOperationsTransactionFactory owner) - { - _owner = owner; - } - public ValueTask DisposeAsync() { return ValueTask.CompletedTask; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsController.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsController.cs index 08ef7b169b..5380300ede 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsController.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsController.cs @@ -7,11 +7,6 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations; -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) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs index 6fd7817ba7..e4e22195a8 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class OperationsDbContext : TestableDbContext +public sealed class OperationsDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Playlists => Set(); public DbSet MusicTracks => Set(); @@ -16,11 +16,6 @@ public sealed class OperationsDbContext : TestableDbContext public DbSet Performers => Set(); public DbSet RecordCompanies => Set(); - public OperationsDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/ResourceDefinitions/Serialization/RecordCompanyDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/ResourceDefinitions/Serialization/RecordCompanyDefinition.cs index 4db0e6fe3c..4c89170efc 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/ResourceDefinitions/Serialization/RecordCompanyDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/ResourceDefinitions/Serialization/RecordCompanyDefinition.cs @@ -4,15 +4,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.ResourceDefinitions.Serialization; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class RecordCompanyDefinition : HitCountingResourceDefinition +public sealed class RecordCompanyDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter) + : HitCountingResourceDefinition(resourceGraph, hitCounter) { protected override ResourceDefinitionExtensibilityPoints ExtensibilityPointsToTrack => ResourceDefinitionExtensibilityPoints.Serialization; - public RecordCompanyDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter) - : base(resourceGraph, hitCounter) - { - } - public override void OnDeserialize(RecordCompany resource) { base.OnDeserialize(resource); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/ResourceDefinitions/SparseFieldSets/LyricTextDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/ResourceDefinitions/SparseFieldSets/LyricTextDefinition.cs index 3abc4a134c..8f38b1aa32 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/ResourceDefinitions/SparseFieldSets/LyricTextDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/ResourceDefinitions/SparseFieldSets/LyricTextDefinition.cs @@ -5,18 +5,13 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.ResourceDefinitions.SparseFieldSets; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class LyricTextDefinition : HitCountingResourceDefinition +public sealed class LyricTextDefinition(IResourceGraph resourceGraph, LyricPermissionProvider lyricPermissionProvider, ResourceDefinitionHitCounter hitCounter) + : HitCountingResourceDefinition(resourceGraph, hitCounter) { - private readonly LyricPermissionProvider _lyricPermissionProvider; + private readonly LyricPermissionProvider _lyricPermissionProvider = lyricPermissionProvider; protected override ResourceDefinitionExtensibilityPoints ExtensibilityPointsToTrack => ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet; - public LyricTextDefinition(IResourceGraph resourceGraph, LyricPermissionProvider lyricPermissionProvider, ResourceDefinitionHitCounter hitCounter) - : base(resourceGraph, hitCounter) - { - _lyricPermissionProvider = lyricPermissionProvider; - } - public override SparseFieldSetExpression? OnApplySparseFieldSet(SparseFieldSetExpression? existingSparseFieldSet) { base.OnApplySparseFieldSet(existingSparseFieldSet); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/ExtraDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/ExtraDbContext.cs index 8638855a46..efc9ebaeaa 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/ExtraDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/ExtraDbContext.cs @@ -5,10 +5,4 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.Transactions; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ExtraDbContext : TestableDbContext -{ - public ExtraDbContext(DbContextOptions options) - : base(options) - { - } -} +public sealed class ExtraDbContext(DbContextOptions options) : TestableDbContext(options); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs index 6512b3fb27..7766b67570 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs @@ -8,14 +8,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.Transactions; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class MusicTrackRepository : EntityFrameworkCoreRepository +public sealed class MusicTrackRepository( + ITargetedFields targetedFields, IDbContextResolver dbContextResolver, IResourceGraph resourceGraph, IResourceFactory resourceFactory, + IEnumerable constraintProviders, ILoggerFactory loggerFactory, IResourceDefinitionAccessor resourceDefinitionAccessor) + : EntityFrameworkCoreRepository(targetedFields, dbContextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, + resourceDefinitionAccessor) { public override string? TransactionId => null; - - public MusicTrackRepository(ITargetedFields targetedFields, IDbContextResolver dbContextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, - IResourceDefinitionAccessor resourceDefinitionAccessor) - : base(targetedFields, dbContextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/OperationsController.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/OperationsController.cs index bfdf4aaa94..357ff7ef5a 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/OperationsController.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/OperationsController.cs @@ -10,14 +10,10 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Authorization.Scopes; -public sealed class OperationsController : JsonApiOperationsController +public sealed class OperationsController( + IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, + ITargetedFields targetedFields) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields) { - public OperationsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, - IJsonApiRequest request, ITargetedFields targetedFields) - : base(options, resourceGraph, loggerFactory, processor, request, targetedFields) - { - } - public override async Task PostOperationsAsync(IList operations, CancellationToken cancellationToken) { AuthScopeSet requestedScopes = AuthScopeSet.GetRequestedScopes(HttpContext.Request.Headers); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/ScopesAuthorizationFilter.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/ScopesAuthorizationFilter.cs index a788b3e115..a4cba72878 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/ScopesAuthorizationFilter.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/ScopesAuthorizationFilter.cs @@ -60,14 +60,9 @@ private AuthScopeSet GetRequiredScopes(IJsonApiRequest request, ITargetedFields return requiredScopes; } - private sealed class QueryStringWalker : QueryExpressionRewriter + private sealed class QueryStringWalker(AuthScopeSet authScopeSet) : QueryExpressionRewriter { - private readonly AuthScopeSet _authScopeSet; - - public QueryStringWalker(AuthScopeSet authScopeSet) - { - _authScopeSet = authScopeSet; - } + private readonly AuthScopeSet _authScopeSet = authScopeSet; public void IncludeScopesFrom(IEnumerable constraintProviders) { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/ScopesDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/ScopesDbContext.cs index 26ce29ff8f..81af9423d2 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/ScopesDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/ScopesDbContext.cs @@ -5,14 +5,9 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Authorization.Scopes; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ScopesDbContext : TestableDbContext +public sealed class ScopesDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Movies => Set(); public DbSet Actors => Set(); public DbSet Genres => Set(); - - public ScopesDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobDbContext.cs index 88a891c319..64c8cd7569 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobDbContext.cs @@ -5,12 +5,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Blobs; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class BlobDbContext : TestableDbContext +public sealed class BlobDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet ImageContainers => Set(); - - public BlobDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CarCompositeKeyAwareRepository.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CarCompositeKeyAwareRepository.cs index 989967bc10..b818c0f094 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CarCompositeKeyAwareRepository.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CarCompositeKeyAwareRepository.cs @@ -9,18 +9,14 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.CompositeKeys; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public class CarCompositeKeyAwareRepository : EntityFrameworkCoreRepository +public class CarCompositeKeyAwareRepository( + ITargetedFields targetedFields, IDbContextResolver dbContextResolver, IResourceGraph resourceGraph, IResourceFactory resourceFactory, + IEnumerable constraintProviders, ILoggerFactory loggerFactory, IResourceDefinitionAccessor resourceDefinitionAccessor) + : EntityFrameworkCoreRepository(targetedFields, dbContextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, + resourceDefinitionAccessor) where TResource : class, IIdentifiable { - private readonly CarExpressionRewriter _writer; - - public CarCompositeKeyAwareRepository(ITargetedFields targetedFields, IDbContextResolver dbContextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, - IResourceDefinitionAccessor resourceDefinitionAccessor) - : base(targetedFields, dbContextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) - { - _writer = new CarExpressionRewriter(resourceGraph); - } + private readonly CarExpressionRewriter _writer = new(resourceGraph); protected override IQueryable ApplyQueryLayer(QueryLayer queryLayer) { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs index d4850ad428..3d10a70df6 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs @@ -7,17 +7,12 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.CompositeKeys; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class CompositeDbContext : TestableDbContext +public sealed class CompositeDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Cars => Set(); public DbSet Engines => Set(); public DbSet Dealerships => Set(); - public CompositeDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/OperationsController.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/OperationsController.cs index d24a0f29d1..dfe7282eac 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/OperationsController.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/OperationsController.cs @@ -7,11 +7,6 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ContentNegotiation; -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) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/PolicyDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/PolicyDbContext.cs index 2526faae2e..bf96700a06 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/PolicyDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/PolicyDbContext.cs @@ -5,12 +5,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ContentNegotiation; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class PolicyDbContext : TestableDbContext +public sealed class PolicyDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Policies => Set(); - - public PolicyDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ControllerActionResults/ActionResultDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ControllerActionResults/ActionResultDbContext.cs index d0a6050a0b..bbf27df0d3 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ControllerActionResults/ActionResultDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ControllerActionResults/ActionResultDbContext.cs @@ -5,12 +5,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ControllerActionResults; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ActionResultDbContext : TestableDbContext +public sealed class ActionResultDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Toothbrushes => Set(); - - public ActionResultDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteDbContext.cs index 9e2debb9a0..43c3bfa59f 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteDbContext.cs @@ -5,13 +5,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.CustomRoutes; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class CustomRouteDbContext : TestableDbContext +public sealed class CustomRouteDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Towns => Set(); public DbSet Civilians => Set(); - - public CustomRouteDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/BuildingRepository.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/BuildingRepository.cs index 888f060ca1..8f6d59c7b1 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/BuildingRepository.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/BuildingRepository.cs @@ -8,15 +8,12 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.EagerLoading; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class BuildingRepository : EntityFrameworkCoreRepository +public sealed class BuildingRepository( + ITargetedFields targetedFields, IDbContextResolver dbContextResolver, IResourceGraph resourceGraph, IResourceFactory resourceFactory, + IEnumerable constraintProviders, ILoggerFactory loggerFactory, IResourceDefinitionAccessor resourceDefinitionAccessor) + : EntityFrameworkCoreRepository(targetedFields, dbContextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, + resourceDefinitionAccessor) { - public BuildingRepository(ITargetedFields targetedFields, IDbContextResolver dbContextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, - IResourceDefinitionAccessor resourceDefinitionAccessor) - : base(targetedFields, dbContextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) - { - } - public override async Task GetForCreateAsync(Type resourceClrType, int id, CancellationToken cancellationToken) { Building building = await base.GetForCreateAsync(resourceClrType, id, cancellationToken); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/EagerLoadingDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/EagerLoadingDbContext.cs index a31deab9a8..9b0932064c 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/EagerLoadingDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/EagerLoadingDbContext.cs @@ -7,18 +7,13 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.EagerLoading; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class EagerLoadingDbContext : TestableDbContext +public sealed class EagerLoadingDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet States => Set(); public DbSet Streets => Set(); public DbSet Buildings => Set(); public DbSet Doors => Set(); - public EagerLoadingDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/AlternateExceptionHandler.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/AlternateExceptionHandler.cs index 4af459fb71..6e406d9e0c 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/AlternateExceptionHandler.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/AlternateExceptionHandler.cs @@ -5,13 +5,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ExceptionHandling; -public sealed class AlternateExceptionHandler : ExceptionHandler +public sealed class AlternateExceptionHandler(ILoggerFactory loggerFactory, IJsonApiOptions options) : ExceptionHandler(loggerFactory, options) { - public AlternateExceptionHandler(ILoggerFactory loggerFactory, IJsonApiOptions options) - : base(loggerFactory, options) - { - } - protected override LogLevel GetLogLevel(Exception exception) { if (exception is ConsumerArticleIsNoLongerAvailableException) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ConsumerArticleIsNoLongerAvailableException.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ConsumerArticleIsNoLongerAvailableException.cs index 6c913ac04b..a1f7f15fcf 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ConsumerArticleIsNoLongerAvailableException.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ConsumerArticleIsNoLongerAvailableException.cs @@ -4,17 +4,12 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ExceptionHandling; -internal sealed class ConsumerArticleIsNoLongerAvailableException : JsonApiException -{ - public string SupportEmailAddress { get; } - - public ConsumerArticleIsNoLongerAvailableException(string articleCode, string supportEmailAddress) - : base(new ErrorObject(HttpStatusCode.Gone) - { - Title = "The requested article is no longer available.", - Detail = $"Article with code '{articleCode}' is no longer available." - }) +internal sealed class ConsumerArticleIsNoLongerAvailableException(string articleCode, string supportEmailAddress) : JsonApiException( + new ErrorObject(HttpStatusCode.Gone) { - SupportEmailAddress = supportEmailAddress; - } + Title = "The requested article is no longer available.", + Detail = $"Article with code '{articleCode}' is no longer available." + }) +{ + public string SupportEmailAddress { get; } = supportEmailAddress; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ConsumerArticleService.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ConsumerArticleService.cs index 335bf7e9fb..fe110f7714 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ConsumerArticleService.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ConsumerArticleService.cs @@ -10,18 +10,15 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ExceptionHandling; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class ConsumerArticleService : JsonApiResourceService +public sealed class ConsumerArticleService( + IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext, IJsonApiOptions options, + ILoggerFactory loggerFactory, IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker, + IResourceDefinitionAccessor resourceDefinitionAccessor) : JsonApiResourceService(repositoryAccessor, queryLayerComposer, + paginationContext, options, loggerFactory, request, resourceChangeTracker, resourceDefinitionAccessor) { private const string SupportEmailAddress = "company@email.com"; internal const string UnavailableArticlePrefix = "X"; - public ConsumerArticleService(IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext, - IJsonApiOptions options, ILoggerFactory loggerFactory, IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker, - IResourceDefinitionAccessor resourceDefinitionAccessor) - : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, resourceDefinitionAccessor) - { - } - public override async Task GetAsync(int id, CancellationToken cancellationToken) { ConsumerArticle consumerArticle = await base.GetAsync(id, cancellationToken); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ErrorDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ErrorDbContext.cs index bf176e681e..fb90f661fa 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ErrorDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ErrorDbContext.cs @@ -5,13 +5,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ExceptionHandling; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ErrorDbContext : TestableDbContext +public sealed class ErrorDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet ConsumerArticles => Set(); public DbSet ThrowingArticles => Set(); - - public ErrorDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/HostingInIIS/HostingDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/HostingInIIS/HostingDbContext.cs index a99db32f47..a27f87c770 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/HostingInIIS/HostingDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/HostingInIIS/HostingDbContext.cs @@ -5,13 +5,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.HostingInIIS; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class HostingDbContext : TestableDbContext +public sealed class HostingDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet ArtGalleries => Set(); public DbSet Paintings => Set(); - - public HostingDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/BankAccountsController.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/BankAccountsController.cs index eedc36ff87..b34fe3a321 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/BankAccountsController.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/BankAccountsController.cs @@ -4,11 +4,6 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.IdObfuscation; -public sealed class BankAccountsController : ObfuscatedIdentifiableController -{ - public BankAccountsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, resourceGraph, loggerFactory, resourceService) - { - } -} +public sealed class BankAccountsController( + IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IResourceService resourceService) + : ObfuscatedIdentifiableController(options, resourceGraph, loggerFactory, resourceService); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/DebitCardsController.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/DebitCardsController.cs index e8c426572a..7fb26d5708 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/DebitCardsController.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/DebitCardsController.cs @@ -4,11 +4,6 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.IdObfuscation; -public sealed class DebitCardsController : ObfuscatedIdentifiableController -{ - public DebitCardsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, resourceGraph, loggerFactory, resourceService) - { - } -} +public sealed class DebitCardsController( + IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IResourceService resourceService) + : ObfuscatedIdentifiableController(options, resourceGraph, loggerFactory, resourceService); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs index cded455259..4f8c5af49b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs @@ -7,17 +7,13 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.IdObfuscation; -public abstract class ObfuscatedIdentifiableController : BaseJsonApiController +public abstract class ObfuscatedIdentifiableController( + IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IResourceService resourceService) + : BaseJsonApiController(options, resourceGraph, loggerFactory, resourceService) where TResource : class, IIdentifiable { private readonly HexadecimalCodec _codec = new(); - protected ObfuscatedIdentifiableController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, resourceGraph, loggerFactory, resourceService) - { - } - [HttpGet] public override Task GetAsync(CancellationToken cancellationToken) { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/ObfuscationDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/ObfuscationDbContext.cs index efc83bbdf8..820a6cf554 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/ObfuscationDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/ObfuscationDbContext.cs @@ -5,13 +5,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.IdObfuscation; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ObfuscationDbContext : TestableDbContext +public sealed class ObfuscationDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet BankAccounts => Set(); public DbSet DebitCards => Set(); - - public ObfuscationDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateDbContext.cs index 1bfdb1a28e..3d41c39035 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateDbContext.cs @@ -7,17 +7,12 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.InputValidation.ModelState; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ModelStateDbContext : TestableDbContext +public sealed class ModelStateDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Volumes => Set(); public DbSet Directories => Set(); public DbSet Files => Set(); - public ModelStateDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/RequestBody/WorkflowDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/RequestBody/WorkflowDbContext.cs index 438093f855..e49e5618a2 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/RequestBody/WorkflowDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/RequestBody/WorkflowDbContext.cs @@ -7,12 +7,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.InputValidation.RequestBody; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class WorkflowDbContext : TestableDbContext +public sealed class WorkflowDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Workflows => Set(); - - public WorkflowDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/RequestBody/WorkflowDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/RequestBody/WorkflowDefinition.cs index f63b793e5b..cdcd268558 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/RequestBody/WorkflowDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/RequestBody/WorkflowDefinition.cs @@ -9,7 +9,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.InputValidation.RequestBody; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class WorkflowDefinition : JsonApiResourceDefinition +public sealed class WorkflowDefinition(IResourceGraph resourceGraph) : JsonApiResourceDefinition(resourceGraph) { private static readonly Dictionary> StageTransitionTable = new() { @@ -33,11 +33,6 @@ public sealed class WorkflowDefinition : JsonApiResourceDefinition options) : TestableDbContext(options) { public DbSet PhotoAlbums => Set(); public DbSet Photos => Set(); public DbSet PhotoLocations => Set(); - public LinksDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Logging/LoggingDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Logging/LoggingDbContext.cs index 26d86c8c3f..262f8e1aee 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Logging/LoggingDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Logging/LoggingDbContext.cs @@ -5,16 +5,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Logging; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class LoggingDbContext : TestableDbContext +public sealed class LoggingDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet AuditEntries => Set(); public DbSet FruitBowls => Set(); public DbSet Fruits => Set(); public DbSet Bananas => Set(); public DbSet Peaches => Set(); - - public LoggingDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/MetaDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/MetaDbContext.cs index 84937ff3d5..a1ab87c420 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/MetaDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/MetaDbContext.cs @@ -5,13 +5,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Meta; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class MetaDbContext : TestableDbContext +public sealed class MetaDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet ProductFamilies => Set(); public DbSet SupportTickets => Set(); - - public MetaDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/SupportTicketDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/SupportTicketDefinition.cs index 6ca207d0aa..20e5078bfe 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/SupportTicketDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/SupportTicketDefinition.cs @@ -4,15 +4,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Meta; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class SupportTicketDefinition : HitCountingResourceDefinition +public sealed class SupportTicketDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter) + : HitCountingResourceDefinition(resourceGraph, hitCounter) { protected override ResourceDefinitionExtensibilityPoints ExtensibilityPointsToTrack => ResourceDefinitionExtensibilityPoints.GetMeta; - public SupportTicketDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter) - : base(resourceGraph, hitCounter) - { - } - public override IDictionary? GetMeta(SupportTicket resource) { base.GetMeta(resource); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetDbContext.cs index 83cddc52f0..c25715d282 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetDbContext.cs @@ -5,13 +5,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.FireAndForgetDelivery; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class FireForgetDbContext : TestableDbContext +public sealed class FireForgetDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Users => Set(); public DbSet Groups => Set(); - - public FireForgetDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetGroupDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetGroupDefinition.cs index a148a47b20..f23dd9670e 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetGroupDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetGroupDefinition.cs @@ -6,18 +6,13 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.FireAndForgetDelivery; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class FireForgetGroupDefinition : MessagingGroupDefinition +public sealed class FireForgetGroupDefinition( + IResourceGraph resourceGraph, FireForgetDbContext dbContext, MessageBroker messageBroker, ResourceDefinitionHitCounter hitCounter) + : MessagingGroupDefinition(resourceGraph, dbContext.Users, dbContext.Groups, hitCounter) { - private readonly MessageBroker _messageBroker; + private readonly MessageBroker _messageBroker = messageBroker; private DomainGroup? _groupToDelete; - public FireForgetGroupDefinition(IResourceGraph resourceGraph, FireForgetDbContext dbContext, MessageBroker messageBroker, - ResourceDefinitionHitCounter hitCounter) - : base(resourceGraph, dbContext.Users, dbContext.Groups, hitCounter) - { - _messageBroker = messageBroker; - } - public override async Task OnWritingAsync(DomainGroup group, WriteOperationKind writeOperation, CancellationToken cancellationToken) { await base.OnWritingAsync(group, writeOperation, cancellationToken); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetUserDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetUserDefinition.cs index c72cd667f0..86afa84846 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetUserDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetUserDefinition.cs @@ -6,18 +6,13 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.FireAndForgetDelivery; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class FireForgetUserDefinition : MessagingUserDefinition +public sealed class FireForgetUserDefinition( + IResourceGraph resourceGraph, FireForgetDbContext dbContext, MessageBroker messageBroker, ResourceDefinitionHitCounter hitCounter) + : MessagingUserDefinition(resourceGraph, dbContext.Users, hitCounter) { - private readonly MessageBroker _messageBroker; + private readonly MessageBroker _messageBroker = messageBroker; private DomainUser? _userToDelete; - public FireForgetUserDefinition(IResourceGraph resourceGraph, FireForgetDbContext dbContext, MessageBroker messageBroker, - ResourceDefinitionHitCounter hitCounter) - : base(resourceGraph, dbContext.Users, hitCounter) - { - _messageBroker = messageBroker; - } - public override async Task OnWritingAsync(DomainUser user, WriteOperationKind writeOperation, CancellationToken cancellationToken) { await base.OnWritingAsync(user, writeOperation, cancellationToken); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/GroupCreatedContent.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/GroupCreatedContent.cs index c7c400ae4f..75abfd7170 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/GroupCreatedContent.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/GroupCreatedContent.cs @@ -3,16 +3,10 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.Messages; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class GroupCreatedContent : IMessageContent +public sealed class GroupCreatedContent(Guid groupId, string groupName) : IMessageContent { public int FormatVersion => 1; - public Guid GroupId { get; } - public string GroupName { get; } - - public GroupCreatedContent(Guid groupId, string groupName) - { - GroupId = groupId; - GroupName = groupName; - } + public Guid GroupId { get; } = groupId; + public string GroupName { get; } = groupName; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/GroupDeletedContent.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/GroupDeletedContent.cs index 338b88a676..1d11b80d84 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/GroupDeletedContent.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/GroupDeletedContent.cs @@ -3,14 +3,9 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.Messages; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class GroupDeletedContent : IMessageContent +public sealed class GroupDeletedContent(Guid groupId) : IMessageContent { public int FormatVersion => 1; - public Guid GroupId { get; } - - public GroupDeletedContent(Guid groupId) - { - GroupId = groupId; - } + public Guid GroupId { get; } = groupId; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/GroupRenamedContent.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/GroupRenamedContent.cs index 2412e9c8c1..d2745f5694 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/GroupRenamedContent.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/GroupRenamedContent.cs @@ -3,18 +3,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.Messages; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class GroupRenamedContent : IMessageContent +public sealed class GroupRenamedContent(Guid groupId, string beforeGroupName, string afterGroupName) : IMessageContent { public int FormatVersion => 1; - public Guid GroupId { get; } - public string BeforeGroupName { get; } - public string AfterGroupName { get; } - - public GroupRenamedContent(Guid groupId, string beforeGroupName, string afterGroupName) - { - GroupId = groupId; - BeforeGroupName = beforeGroupName; - AfterGroupName = afterGroupName; - } + public Guid GroupId { get; } = groupId; + public string BeforeGroupName { get; } = beforeGroupName; + public string AfterGroupName { get; } = afterGroupName; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserAddedToGroupContent.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserAddedToGroupContent.cs index fd8400b9a4..4d61510b02 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserAddedToGroupContent.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserAddedToGroupContent.cs @@ -3,16 +3,10 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.Messages; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class UserAddedToGroupContent : IMessageContent +public sealed class UserAddedToGroupContent(Guid userId, Guid groupId) : IMessageContent { public int FormatVersion => 1; - public Guid UserId { get; } - public Guid GroupId { get; } - - public UserAddedToGroupContent(Guid userId, Guid groupId) - { - UserId = userId; - GroupId = groupId; - } + public Guid UserId { get; } = userId; + public Guid GroupId { get; } = groupId; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserCreatedContent.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserCreatedContent.cs index 8c9f2c08f7..774f9d3a70 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserCreatedContent.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserCreatedContent.cs @@ -3,18 +3,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.Messages; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class UserCreatedContent : IMessageContent +public sealed class UserCreatedContent(Guid userId, string userLoginName, string? userDisplayName) : IMessageContent { public int FormatVersion => 1; - public Guid UserId { get; } - public string UserLoginName { get; } - public string? UserDisplayName { get; } - - public UserCreatedContent(Guid userId, string userLoginName, string? userDisplayName) - { - UserId = userId; - UserLoginName = userLoginName; - UserDisplayName = userDisplayName; - } + public Guid UserId { get; } = userId; + public string UserLoginName { get; } = userLoginName; + public string? UserDisplayName { get; } = userDisplayName; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserDeletedContent.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserDeletedContent.cs index f0a5a30102..877609eb5b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserDeletedContent.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserDeletedContent.cs @@ -3,14 +3,9 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.Messages; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class UserDeletedContent : IMessageContent +public sealed class UserDeletedContent(Guid userId) : IMessageContent { public int FormatVersion => 1; - public Guid UserId { get; } - - public UserDeletedContent(Guid userId) - { - UserId = userId; - } + public Guid UserId { get; } = userId; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserDisplayNameChangedContent.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserDisplayNameChangedContent.cs index aff93ee7db..05d84a8fa0 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserDisplayNameChangedContent.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserDisplayNameChangedContent.cs @@ -3,18 +3,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.Messages; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class UserDisplayNameChangedContent : IMessageContent +public sealed class UserDisplayNameChangedContent(Guid userId, string? beforeUserDisplayName, string? afterUserDisplayName) : IMessageContent { public int FormatVersion => 1; - public Guid UserId { get; } - public string? BeforeUserDisplayName { get; } - public string? AfterUserDisplayName { get; } - - public UserDisplayNameChangedContent(Guid userId, string? beforeUserDisplayName, string? afterUserDisplayName) - { - UserId = userId; - BeforeUserDisplayName = beforeUserDisplayName; - AfterUserDisplayName = afterUserDisplayName; - } + public Guid UserId { get; } = userId; + public string? BeforeUserDisplayName { get; } = beforeUserDisplayName; + public string? AfterUserDisplayName { get; } = afterUserDisplayName; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserLoginNameChangedContent.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserLoginNameChangedContent.cs index d835220aa0..bb46640e67 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserLoginNameChangedContent.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserLoginNameChangedContent.cs @@ -3,18 +3,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.Messages; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class UserLoginNameChangedContent : IMessageContent +public sealed class UserLoginNameChangedContent(Guid userId, string beforeUserLoginName, string afterUserLoginName) : IMessageContent { public int FormatVersion => 1; - public Guid UserId { get; } - public string BeforeUserLoginName { get; } - public string AfterUserLoginName { get; } - - public UserLoginNameChangedContent(Guid userId, string beforeUserLoginName, string afterUserLoginName) - { - UserId = userId; - BeforeUserLoginName = beforeUserLoginName; - AfterUserLoginName = afterUserLoginName; - } + public Guid UserId { get; } = userId; + public string BeforeUserLoginName { get; } = beforeUserLoginName; + public string AfterUserLoginName { get; } = afterUserLoginName; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserMovedToGroupContent.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserMovedToGroupContent.cs index 766422e595..aad81f0123 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserMovedToGroupContent.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserMovedToGroupContent.cs @@ -3,18 +3,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.Messages; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class UserMovedToGroupContent : IMessageContent +public sealed class UserMovedToGroupContent(Guid userId, Guid beforeGroupId, Guid afterGroupId) : IMessageContent { public int FormatVersion => 1; - public Guid UserId { get; } - public Guid BeforeGroupId { get; } - public Guid AfterGroupId { get; } - - public UserMovedToGroupContent(Guid userId, Guid beforeGroupId, Guid afterGroupId) - { - UserId = userId; - BeforeGroupId = beforeGroupId; - AfterGroupId = afterGroupId; - } + public Guid UserId { get; } = userId; + public Guid BeforeGroupId { get; } = beforeGroupId; + public Guid AfterGroupId { get; } = afterGroupId; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserRemovedFromGroupContent.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserRemovedFromGroupContent.cs index 8623d101b9..1969b7c4d7 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserRemovedFromGroupContent.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/Messages/UserRemovedFromGroupContent.cs @@ -3,16 +3,10 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.Messages; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class UserRemovedFromGroupContent : IMessageContent +public sealed class UserRemovedFromGroupContent(Guid userId, Guid groupId) : IMessageContent { public int FormatVersion => 1; - public Guid UserId { get; } - public Guid GroupId { get; } - - public UserRemovedFromGroupContent(Guid userId, Guid groupId) - { - UserId = userId; - GroupId = groupId; - } + public Guid UserId { get; } = userId; + public Guid GroupId { get; } = groupId; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/MessagingGroupDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/MessagingGroupDefinition.cs index b30c8ef88f..e6c1d53314 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/MessagingGroupDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/MessagingGroupDefinition.cs @@ -7,24 +7,18 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices; -public abstract class MessagingGroupDefinition : HitCountingResourceDefinition +public abstract class MessagingGroupDefinition( + IResourceGraph resourceGraph, DbSet userSet, DbSet groupSet, ResourceDefinitionHitCounter hitCounter) + : HitCountingResourceDefinition(resourceGraph, hitCounter) { - private readonly DbSet _userSet; - private readonly DbSet _groupSet; + private readonly DbSet _userSet = userSet; + private readonly DbSet _groupSet = groupSet; private readonly List _pendingMessages = []; private string? _beforeGroupName; protected override ResourceDefinitionExtensibilityPoints ExtensibilityPointsToTrack => ResourceDefinitionExtensibilityPoints.Writing; - protected MessagingGroupDefinition(IResourceGraph resourceGraph, DbSet userSet, DbSet groupSet, - ResourceDefinitionHitCounter hitCounter) - : base(resourceGraph, hitCounter) - { - _userSet = userSet; - _groupSet = groupSet; - } - public override async Task OnPrepareWriteAsync(DomainGroup group, WriteOperationKind writeOperation, CancellationToken cancellationToken) { await base.OnPrepareWriteAsync(group, writeOperation, cancellationToken); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/MessagingUserDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/MessagingUserDefinition.cs index 74b5190cd1..72c38677dd 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/MessagingUserDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/MessagingUserDefinition.cs @@ -7,9 +7,10 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices; -public abstract class MessagingUserDefinition : HitCountingResourceDefinition +public abstract class MessagingUserDefinition(IResourceGraph resourceGraph, DbSet userSet, ResourceDefinitionHitCounter hitCounter) + : HitCountingResourceDefinition(resourceGraph, hitCounter) { - private readonly DbSet _userSet; + private readonly DbSet _userSet = userSet; private readonly List _pendingMessages = []; private string? _beforeLoginName; @@ -17,12 +18,6 @@ public abstract class MessagingUserDefinition : HitCountingResourceDefinition ResourceDefinitionExtensibilityPoints.Writing; - protected MessagingUserDefinition(IResourceGraph resourceGraph, DbSet userSet, ResourceDefinitionHitCounter hitCounter) - : base(resourceGraph, hitCounter) - { - _userSet = userSet; - } - public override async Task OnPrepareWriteAsync(DomainUser user, WriteOperationKind writeOperation, CancellationToken cancellationToken) { await base.OnPrepareWriteAsync(user, writeOperation, cancellationToken); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxDbContext.cs index c02b8fcc34..a09b8ba389 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxDbContext.cs @@ -6,14 +6,9 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.TransactionalOutboxPattern; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class OutboxDbContext : TestableDbContext +public sealed class OutboxDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Users => Set(); public DbSet Groups => Set(); public DbSet OutboxMessages => Set(); - - public OutboxDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxGroupDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxGroupDefinition.cs index 8aaef59e35..9e950fa653 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxGroupDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxGroupDefinition.cs @@ -7,15 +7,10 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.TransactionalOutboxPattern; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class OutboxGroupDefinition : MessagingGroupDefinition +public sealed class OutboxGroupDefinition(IResourceGraph resourceGraph, OutboxDbContext dbContext, ResourceDefinitionHitCounter hitCounter) + : MessagingGroupDefinition(resourceGraph, dbContext.Users, dbContext.Groups, hitCounter) { - private readonly DbSet _outboxMessageSet; - - public OutboxGroupDefinition(IResourceGraph resourceGraph, OutboxDbContext dbContext, ResourceDefinitionHitCounter hitCounter) - : base(resourceGraph, dbContext.Users, dbContext.Groups, hitCounter) - { - _outboxMessageSet = dbContext.OutboxMessages; - } + private readonly DbSet _outboxMessageSet = dbContext.OutboxMessages; public override async Task OnWritingAsync(DomainGroup group, WriteOperationKind writeOperation, CancellationToken cancellationToken) { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxUserDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxUserDefinition.cs index 8076c944ec..cbf08b9aef 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxUserDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxUserDefinition.cs @@ -7,15 +7,10 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.TransactionalOutboxPattern; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class OutboxUserDefinition : MessagingUserDefinition +public sealed class OutboxUserDefinition(IResourceGraph resourceGraph, OutboxDbContext dbContext, ResourceDefinitionHitCounter hitCounter) + : MessagingUserDefinition(resourceGraph, dbContext.Users, hitCounter) { - private readonly DbSet _outboxMessageSet; - - public OutboxUserDefinition(IResourceGraph resourceGraph, OutboxDbContext dbContext, ResourceDefinitionHitCounter hitCounter) - : base(resourceGraph, dbContext.Users, hitCounter) - { - _outboxMessageSet = dbContext.OutboxMessages; - } + private readonly DbSet _outboxMessageSet = dbContext.OutboxMessages; public override async Task OnWritingAsync(DomainUser user, WriteOperationKind writeOperation, CancellationToken cancellationToken) { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenancyDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenancyDbContext.cs index 69a6459303..2ace5fb3fd 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenancyDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenancyDbContext.cs @@ -7,19 +7,13 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.MultiTenancy; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class MultiTenancyDbContext : TestableDbContext +public sealed class MultiTenancyDbContext(DbContextOptions options, ITenantProvider tenantProvider) : TestableDbContext(options) { - private readonly ITenantProvider _tenantProvider; + private readonly ITenantProvider _tenantProvider = tenantProvider; public DbSet WebShops => Set(); public DbSet WebProducts => Set(); - public MultiTenancyDbContext(DbContextOptions options, ITenantProvider tenantProvider) - : base(options) - { - _tenantProvider = tenantProvider; - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs index b46a8133d0..9918cbaff3 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs @@ -10,21 +10,18 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.MultiTenancy; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public class MultiTenantResourceService : JsonApiResourceService +public class MultiTenantResourceService( + ITenantProvider tenantProvider, IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, + IPaginationContext paginationContext, IJsonApiOptions options, ILoggerFactory loggerFactory, IJsonApiRequest request, + IResourceChangeTracker resourceChangeTracker, IResourceDefinitionAccessor resourceDefinitionAccessor) + : JsonApiResourceService(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, + resourceDefinitionAccessor) where TResource : class, IIdentifiable { - private readonly ITenantProvider _tenantProvider; + private readonly ITenantProvider _tenantProvider = tenantProvider; private static bool ResourceHasTenant => typeof(IHasTenant).IsAssignableFrom(typeof(TResource)); - public MultiTenantResourceService(ITenantProvider tenantProvider, IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, - IPaginationContext paginationContext, IJsonApiOptions options, ILoggerFactory loggerFactory, IJsonApiRequest request, - IResourceChangeTracker resourceChangeTracker, IResourceDefinitionAccessor resourceDefinitionAccessor) - : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, resourceDefinitionAccessor) - { - _tenantProvider = tenantProvider; - } - protected override async Task InitializeResourceAsync(TResource resourceForDatabase, CancellationToken cancellationToken) { await base.InitializeResourceAsync(resourceForDatabase, cancellationToken); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/RouteTenantProvider.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/RouteTenantProvider.cs index f51c214f0e..253b48b58a 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/RouteTenantProvider.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/RouteTenantProvider.cs @@ -2,7 +2,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.MultiTenancy; -internal sealed class RouteTenantProvider : ITenantProvider +internal sealed class RouteTenantProvider(IHttpContextAccessor httpContextAccessor) : ITenantProvider { // In reality, this would be looked up in a database. We'll keep it hardcoded for simplicity. public static readonly IDictionary TenantRegistry = new Dictionary @@ -11,7 +11,7 @@ internal sealed class RouteTenantProvider : ITenantProvider ["ita"] = Guid.NewGuid() }; - private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; public Guid TenantId { @@ -26,9 +26,4 @@ public Guid TenantId return countryCode != null && TenantRegistry.TryGetValue(countryCode, out Guid tenantId) ? tenantId : Guid.Empty; } } - - public RouteTenantProvider(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/DivingBoardsController.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/DivingBoardsController.cs index 55a3cd97da..3c02f9d8e2 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/DivingBoardsController.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/DivingBoardsController.cs @@ -5,11 +5,6 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.NamingConventions; -public sealed class DivingBoardsController : JsonApiController -{ - public DivingBoardsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, resourceGraph, loggerFactory, resourceService) - { - } -} +public sealed class DivingBoardsController( + IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IResourceService resourceService) + : JsonApiController(options, resourceGraph, loggerFactory, resourceService); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/NamingDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/NamingDbContext.cs index 059abf5d51..a13c4a44ae 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/NamingDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/NamingDbContext.cs @@ -5,14 +5,9 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.NamingConventions; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class NamingDbContext : TestableDbContext +public sealed class NamingDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet SwimmingPools => Set(); public DbSet WaterSlides => Set(); public DbSet DivingBoards => Set(); - - public NamingDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/SwimmingPoolsController.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/SwimmingPoolsController.cs index b87206097b..95c368fb0e 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/SwimmingPoolsController.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/SwimmingPoolsController.cs @@ -5,11 +5,6 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.NamingConventions; -public sealed class SwimmingPoolsController : JsonApiController -{ - public SwimmingPoolsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, resourceGraph, loggerFactory, resourceService) - { - } -} +public sealed class SwimmingPoolsController( + IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IResourceService resourceService) + : JsonApiController(options, resourceGraph, loggerFactory, resourceService); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/DuplicateKnownResourcesController.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/DuplicateKnownResourcesController.cs index b127a8cc81..34a9d1d674 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/DuplicateKnownResourcesController.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/DuplicateKnownResourcesController.cs @@ -5,11 +5,6 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.NonJsonApiControllers; -public sealed class DuplicateKnownResourcesController : JsonApiController -{ - public DuplicateKnownResourcesController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, resourceGraph, loggerFactory, resourceService) - { - } -} +public sealed class DuplicateKnownResourcesController( + IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IResourceService resourceService) + : JsonApiController(options, resourceGraph, loggerFactory, resourceService); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/EmptyDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/EmptyDbContext.cs index 0ac00ea2ae..33fa56a0bb 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/EmptyDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/EmptyDbContext.cs @@ -5,10 +5,4 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.NonJsonApiControllers; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class EmptyDbContext : TestableDbContext -{ - public EmptyDbContext(DbContextOptions options) - : base(options) - { - } -} +public sealed class EmptyDbContext(DbContextOptions options) : TestableDbContext(options); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/KnownDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/KnownDbContext.cs index b18d71938c..23aae10cd3 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/KnownDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/KnownDbContext.cs @@ -5,12 +5,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.NonJsonApiControllers; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class KnownDbContext : TestableDbContext +public sealed class KnownDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet KnownResources => Set(); - - public KnownDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/IsUpperCase/IsUpperCaseFilterParser.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/IsUpperCase/IsUpperCaseFilterParser.cs index 5debe54835..27b32d7189 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/IsUpperCase/IsUpperCaseFilterParser.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/IsUpperCase/IsUpperCaseFilterParser.cs @@ -6,13 +6,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.QueryStrings.CustomFunctions.IsUpperCase; -internal sealed class IsUpperCaseFilterParser : FilterParser +internal sealed class IsUpperCaseFilterParser(IResourceFactory resourceFactory) : FilterParser(resourceFactory) { - public IsUpperCaseFilterParser(IResourceFactory resourceFactory) - : base(resourceFactory) - { - } - protected override FilterExpression ParseFilter() { if (TokenStack.TryPeek(out Token? nextToken) && nextToken is { Kind: TokenKind.Text, Value: IsUpperCaseExpression.Keyword }) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/StringLength/LengthFilterParser.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/StringLength/LengthFilterParser.cs index 028d40c0ed..f429224290 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/StringLength/LengthFilterParser.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/StringLength/LengthFilterParser.cs @@ -6,13 +6,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.QueryStrings.CustomFunctions.StringLength; -internal sealed class LengthFilterParser : FilterParser +internal sealed class LengthFilterParser(IResourceFactory resourceFactory) : FilterParser(resourceFactory) { - public LengthFilterParser(IResourceFactory resourceFactory) - : base(resourceFactory) - { - } - protected override bool IsFunction(string name) { if (name == LengthExpression.Keyword) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/Sum/SumFilterParser.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/Sum/SumFilterParser.cs index 76087dc31f..1db57b4fc0 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/Sum/SumFilterParser.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/Sum/SumFilterParser.cs @@ -6,7 +6,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.QueryStrings.CustomFunctions.Sum; -internal sealed class SumFilterParser : FilterParser +internal sealed class SumFilterParser(IResourceFactory resourceFactory) : FilterParser(resourceFactory) { private static readonly FieldChainPattern SingleToManyRelationshipChain = FieldChainPattern.Parse("M"); @@ -25,11 +25,6 @@ internal sealed class SumFilterParser : FilterParser typeof(decimal) ]; - public SumFilterParser(IResourceFactory resourceFactory) - : base(resourceFactory) - { - } - protected override bool IsFunction(string name) { if (name == SumExpression.Keyword) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/TimeOffset/FilterRewritingResourceDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/TimeOffset/FilterRewritingResourceDefinition.cs index c3e5941a19..1a704d3883 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/TimeOffset/FilterRewritingResourceDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/TimeOffset/FilterRewritingResourceDefinition.cs @@ -7,16 +7,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.QueryStrings.CustomFunctions.TimeOffset; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public class FilterRewritingResourceDefinition : JsonApiResourceDefinition +public class FilterRewritingResourceDefinition(IResourceGraph resourceGraph, ISystemClock systemClock) + : JsonApiResourceDefinition(resourceGraph) where TResource : class, IIdentifiable { - private readonly FilterTimeOffsetRewriter _rewriter; - - public FilterRewritingResourceDefinition(IResourceGraph resourceGraph, ISystemClock systemClock) - : base(resourceGraph) - { - _rewriter = new FilterTimeOffsetRewriter(systemClock); - } + private readonly FilterTimeOffsetRewriter _rewriter = new(systemClock); public override FilterExpression? OnApplyFilter(FilterExpression? existingFilter) { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/TimeOffset/FilterTimeOffsetRewriter.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/TimeOffset/FilterTimeOffsetRewriter.cs index 2d2ee4da89..9f9c97c73a 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/TimeOffset/FilterTimeOffsetRewriter.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/TimeOffset/FilterTimeOffsetRewriter.cs @@ -3,7 +3,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.QueryStrings.CustomFunctions.TimeOffset; -internal sealed class FilterTimeOffsetRewriter : QueryExpressionRewriter +internal sealed class FilterTimeOffsetRewriter(ISystemClock systemClock) : QueryExpressionRewriter { private static readonly Dictionary InverseComparisonOperatorTable = new() { @@ -14,12 +14,7 @@ internal sealed class FilterTimeOffsetRewriter : QueryExpressionRewriter options) : TestableDbContext(options) { public DbSet FilterableResources => Set(); - public FilterDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs index 3aa40b2725..28a8935d26 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.QueryStrings; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class QueryStringDbContext : TestableDbContext +public sealed class QueryStringDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Blogs => Set(); public DbSet Posts => Set(); @@ -23,11 +23,6 @@ public sealed class QueryStringDbContext : TestableDbContext public DbSet Appointments => Set(); public DbSet Reminders => Set(); - public QueryStringDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/SparseFieldSets/ResultCapturingRepository.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/SparseFieldSets/ResultCapturingRepository.cs index d900e5809d..97eed95472 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/SparseFieldSets/ResultCapturingRepository.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/SparseFieldSets/ResultCapturingRepository.cs @@ -11,18 +11,14 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.QueryStrings.SparseFieldSets; /// Enables sparse fieldset tests to verify which fields were (not) retrieved from the database. /// [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class ResultCapturingRepository : EntityFrameworkCoreRepository +public sealed class ResultCapturingRepository( + ITargetedFields targetedFields, IDbContextResolver dbContextResolver, IResourceGraph resourceGraph, IResourceFactory resourceFactory, + IEnumerable constraintProviders, ILoggerFactory loggerFactory, IResourceDefinitionAccessor resourceDefinitionAccessor, + ResourceCaptureStore captureStore) : EntityFrameworkCoreRepository(targetedFields, dbContextResolver, resourceGraph, resourceFactory, + constraintProviders, loggerFactory, resourceDefinitionAccessor) where TResource : class, IIdentifiable { - private readonly ResourceCaptureStore _captureStore; - - public ResultCapturingRepository(ITargetedFields targetedFields, IDbContextResolver dbContextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, - IResourceDefinitionAccessor resourceDefinitionAccessor, ResourceCaptureStore captureStore) - : base(targetedFields, dbContextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) - { - _captureStore = captureStore; - } + private readonly ResourceCaptureStore _captureStore = captureStore; public override async Task> GetAsync(QueryLayer queryLayer, CancellationToken cancellationToken) { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ImplicitlyChangingWorkItemDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ImplicitlyChangingWorkItemDefinition.cs index fdf1e2e512..996e917693 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ImplicitlyChangingWorkItemDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ImplicitlyChangingWorkItemDefinition.cs @@ -10,17 +10,12 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ReadWrite; /// Used to simulate side effects that occur in the database while saving, typically caused by database triggers. /// [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class ImplicitlyChangingWorkItemDefinition : JsonApiResourceDefinition +public sealed class ImplicitlyChangingWorkItemDefinition(IResourceGraph resourceGraph, ReadWriteDbContext dbContext) + : JsonApiResourceDefinition(resourceGraph) { internal const string Suffix = " (changed)"; - private readonly ReadWriteDbContext _dbContext; - - public ImplicitlyChangingWorkItemDefinition(IResourceGraph resourceGraph, ReadWriteDbContext dbContext) - : base(resourceGraph) - { - _dbContext = dbContext; - } + private readonly ReadWriteDbContext _dbContext = dbContext; public override async Task OnWriteSucceededAsync(WorkItem resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ImplicitlyChangingWorkItemGroupDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ImplicitlyChangingWorkItemGroupDefinition.cs index 1fbec1c9df..c0f06094aa 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ImplicitlyChangingWorkItemGroupDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ImplicitlyChangingWorkItemGroupDefinition.cs @@ -10,17 +10,12 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ReadWrite; /// Used to simulate side effects that occur in the database while saving, typically caused by database triggers. /// [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class ImplicitlyChangingWorkItemGroupDefinition : JsonApiResourceDefinition +public sealed class ImplicitlyChangingWorkItemGroupDefinition(IResourceGraph resourceGraph, ReadWriteDbContext dbContext) + : JsonApiResourceDefinition(resourceGraph) { internal const string Suffix = " (changed)"; - private readonly ReadWriteDbContext _dbContext; - - public ImplicitlyChangingWorkItemGroupDefinition(IResourceGraph resourceGraph, ReadWriteDbContext dbContext) - : base(resourceGraph) - { - _dbContext = dbContext; - } + private readonly ReadWriteDbContext _dbContext = dbContext; public override async Task OnWriteSucceededAsync(WorkItemGroup resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs index 4f3d1080cf..e8350555d5 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs @@ -8,7 +8,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ReadWrite; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ReadWriteDbContext : TestableDbContext +public sealed class ReadWriteDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet WorkItems => Set(); public DbSet WorkTags => Set(); @@ -16,11 +16,6 @@ public sealed class ReadWriteDbContext : TestableDbContext public DbSet RgbColors => Set(); public DbSet UserAccounts => Set(); - public ReadWriteDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs index 567559b5d4..4f3e8d3ab3 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs @@ -1096,7 +1096,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - private sealed class RemoveExtraFromWorkItemDefinition : JsonApiResourceDefinition + private sealed class RemoveExtraFromWorkItemDefinition(IResourceGraph resourceGraph) : JsonApiResourceDefinition(resourceGraph) { // Enables to verify that not the full relationship was loaded upfront. public ISet PreloadedSubscribers { get; } = new HashSet(IdentifiableComparer.Instance); @@ -1106,11 +1106,6 @@ private sealed class RemoveExtraFromWorkItemDefinition : JsonApiResourceDefiniti public ISet ExtraSubscribersIdsToRemove { get; } = new HashSet(); public ISet ExtraTagIdsToRemove { get; } = new HashSet(); - public RemoveExtraFromWorkItemDefinition(IResourceGraph resourceGraph) - : base(resourceGraph) - { - } - public void Reset() { PreloadedSubscribers.Clear(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs index 5b0b839c63..d0bea86f38 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs @@ -7,17 +7,12 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.RequiredRelationships; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class DefaultBehaviorDbContext : TestableDbContext +public sealed class DefaultBehaviorDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Customers => Set(); public DbSet Orders => Set(); public DbSet Shipments => Set(); - public DefaultBehaviorDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/GiftCertificate.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/GiftCertificate.cs index 6da63a30cb..6a04e8a1ab 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/GiftCertificate.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/GiftCertificate.cs @@ -8,9 +8,9 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceConstructorInjection; [UsedImplicitly(ImplicitUseTargetFlags.Members)] [Resource(ControllerNamespace = "JsonApiDotNetCoreTests.IntegrationTests.ResourceConstructorInjection")] -public sealed class GiftCertificate : Identifiable +public sealed class GiftCertificate(InjectionDbContext injectionDbContext) : Identifiable { - private readonly ISystemClock _systemClock; + private readonly ISystemClock _systemClock = injectionDbContext.SystemClock; [Attr] public DateTimeOffset IssueDate { get; set; } @@ -21,9 +21,4 @@ public sealed class GiftCertificate : Identifiable [HasOne] public PostOffice? Issuer { get; set; } - - public GiftCertificate(InjectionDbContext injectionDbContext) - { - _systemClock = injectionDbContext.SystemClock; - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/PostOffice.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/PostOffice.cs index f7bd504cd8..86658bb9d7 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/PostOffice.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/PostOffice.cs @@ -8,9 +8,9 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceConstructorInjection; [UsedImplicitly(ImplicitUseTargetFlags.Members)] [Resource(ControllerNamespace = "JsonApiDotNetCoreTests.IntegrationTests.ResourceConstructorInjection")] -public sealed class PostOffice : Identifiable +public sealed class PostOffice(InjectionDbContext injectionDbContext) : Identifiable { - private readonly ISystemClock _systemClock; + private readonly ISystemClock _systemClock = injectionDbContext.SystemClock; [Attr] public string Address { get; set; } = null!; @@ -22,11 +22,6 @@ public sealed class PostOffice : Identifiable [HasMany] public IList GiftCertificates { get; set; } = new List(); - public PostOffice(InjectionDbContext injectionDbContext) - { - _systemClock = injectionDbContext.SystemClock; - } - private bool IsWithinOperatingHours() { DateTimeOffset currentTime = _systemClock.UtcNow; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs index 2b904434ad..ada3bce0f7 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs @@ -9,21 +9,14 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class MoonDefinition : HitCountingResourceDefinition +// The constructor parameters will be resolved from the container, which means you can take on any dependency that is also defined in the container. +public sealed class MoonDefinition(IResourceGraph resourceGraph, IClientSettingsProvider clientSettingsProvider, ResourceDefinitionHitCounter hitCounter) + : HitCountingResourceDefinition(resourceGraph, hitCounter) { - private readonly IClientSettingsProvider _clientSettingsProvider; + private readonly IClientSettingsProvider _clientSettingsProvider = clientSettingsProvider; protected override ResourceDefinitionExtensibilityPoints ExtensibilityPointsToTrack => ResourceDefinitionExtensibilityPoints.Reading; - public MoonDefinition(IResourceGraph resourceGraph, IClientSettingsProvider clientSettingsProvider, ResourceDefinitionHitCounter hitCounter) - : base(resourceGraph, hitCounter) - { - // This constructor will be resolved from the container, which means - // you can take on any dependency that is also defined in the container. - - _clientSettingsProvider = clientSettingsProvider; - } - public override IImmutableSet OnApplyIncludes(IImmutableSet existingIncludes) { base.OnApplyIncludes(existingIncludes); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/PlanetDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/PlanetDefinition.cs index 70f77ecb24..d4a0c23af8 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/PlanetDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/PlanetDefinition.cs @@ -10,21 +10,14 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class PlanetDefinition : HitCountingResourceDefinition +// The constructor parameters will be resolved from the container, which means you can take on any dependency that is also defined in the container. +public sealed class PlanetDefinition(IResourceGraph resourceGraph, IClientSettingsProvider clientSettingsProvider, ResourceDefinitionHitCounter hitCounter) + : HitCountingResourceDefinition(resourceGraph, hitCounter) { - private readonly IClientSettingsProvider _clientSettingsProvider; + private readonly IClientSettingsProvider _clientSettingsProvider = clientSettingsProvider; protected override ResourceDefinitionExtensibilityPoints ExtensibilityPointsToTrack => ResourceDefinitionExtensibilityPoints.Reading; - public PlanetDefinition(IResourceGraph resourceGraph, IClientSettingsProvider clientSettingsProvider, ResourceDefinitionHitCounter hitCounter) - : base(resourceGraph, hitCounter) - { - // This constructor will be resolved from the container, which means - // you can take on any dependency that is also defined in the container. - - _clientSettingsProvider = clientSettingsProvider; - } - public override IImmutableSet OnApplyIncludes(IImmutableSet existingIncludes) { base.OnApplyIncludes(existingIncludes); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/StarDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/StarDefinition.cs index 6146e04367..944cb4ca0e 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/StarDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/StarDefinition.cs @@ -6,17 +6,12 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class StarDefinition : HitCountingResourceDefinition +// The constructor parameters will be resolved from the container, which means you can take on any dependency that is also defined in the container. +public sealed class StarDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter) + : HitCountingResourceDefinition(resourceGraph, hitCounter) { protected override ResourceDefinitionExtensibilityPoints ExtensibilityPointsToTrack => ResourceDefinitionExtensibilityPoints.Reading; - public StarDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter) - : base(resourceGraph, hitCounter) - { - // This constructor will be resolved from the container, which means - // you can take on any dependency that is also defined in the container. - } - public override SortExpression OnApplySort(SortExpression? existingSort) { base.OnApplySort(existingSort); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs index 3dc619dcba..6bfd710d0d 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs @@ -5,14 +5,9 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class UniverseDbContext : TestableDbContext +public sealed class UniverseDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Stars => Set(); public DbSet Planets => Set(); public DbSet Moons => Set(); - - public UniverseDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/SerializationDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/SerializationDbContext.cs index 4bfb7aa709..1772f6118c 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/SerializationDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/SerializationDbContext.cs @@ -7,16 +7,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Serialization; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class SerializationDbContext : TestableDbContext +public sealed class SerializationDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Students => Set(); public DbSet Scholarships => Set(); - public SerializationDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/StudentDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/StudentDefinition.cs index 8c857b5bc9..26e6eee785 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/StudentDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/StudentDefinition.cs @@ -4,21 +4,14 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Serialization; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class StudentDefinition : HitCountingResourceDefinition +// The constructor parameters will be resolved from the container, which means you can take on any dependency that is also defined in the container. +public sealed class StudentDefinition(IResourceGraph resourceGraph, IEncryptionService encryptionService, ResourceDefinitionHitCounter hitCounter) + : HitCountingResourceDefinition(resourceGraph, hitCounter) { - private readonly IEncryptionService _encryptionService; + private readonly IEncryptionService _encryptionService = encryptionService; protected override ResourceDefinitionExtensibilityPoints ExtensibilityPointsToTrack => ResourceDefinitionExtensibilityPoints.Serialization; - public StudentDefinition(IResourceGraph resourceGraph, IEncryptionService encryptionService, ResourceDefinitionHitCounter hitCounter) - : base(resourceGraph, hitCounter) - { - // This constructor will be resolved from the container, which means - // you can take on any dependency that is also defined in the container. - - _encryptionService = encryptionService; - } - public override void OnDeserialize(Student resource) { base.OnDeserialize(resource); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ChangeTracking/ChangeTrackingDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ChangeTracking/ChangeTrackingDbContext.cs index a335f493c4..e9002e7be9 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ChangeTracking/ChangeTrackingDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ChangeTracking/ChangeTrackingDbContext.cs @@ -5,12 +5,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceInheritance.ChangeTracking; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ChangeTrackingDbContext : ResourceInheritanceDbContext +public sealed class ChangeTrackingDbContext(DbContextOptions options) : ResourceInheritanceDbContext(options) { public DbSet AlwaysMovingTandems => Set(); - - public ChangeTrackingDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs index f5e0fc1bbe..acac25ee8a 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs @@ -6,7 +6,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceInheritance; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public abstract class ResourceInheritanceDbContext : TestableDbContext +public abstract class ResourceInheritanceDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Vehicles => Set(); public DbSet Bikes => Set(); @@ -32,9 +32,4 @@ public abstract class ResourceInheritanceDbContext : TestableDbContext public DbSet BicycleLights => Set(); public DbSet NavigationSystems => Set(); public DbSet GenericFeatures => Set(); - - protected ResourceInheritanceDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerConcreteType/TablePerConcreteTypeDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerConcreteType/TablePerConcreteTypeDbContext.cs index 2b5977c8b4..1e7e644367 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerConcreteType/TablePerConcreteTypeDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerConcreteType/TablePerConcreteTypeDbContext.cs @@ -7,13 +7,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceInheritance.TablePerConcreteType; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class TablePerConcreteTypeDbContext : ResourceInheritanceDbContext +public sealed class TablePerConcreteTypeDbContext(DbContextOptions options) : ResourceInheritanceDbContext(options) { - public TablePerConcreteTypeDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerConcreteType/TablePerConcreteTypeReadTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerConcreteType/TablePerConcreteTypeReadTests.cs index bcffe2ed5e..217918c468 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerConcreteType/TablePerConcreteTypeReadTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerConcreteType/TablePerConcreteTypeReadTests.cs @@ -4,10 +4,6 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceInheritance.TablePerConcreteType; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class TablePerConcreteTypeReadTests : ResourceInheritanceReadTests -{ - public TablePerConcreteTypeReadTests(IntegrationTestContext, TablePerConcreteTypeDbContext> testContext) - : base(testContext) - { - } -} +public sealed class TablePerConcreteTypeReadTests( + IntegrationTestContext, TablePerConcreteTypeDbContext> testContext) + : ResourceInheritanceReadTests(testContext); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerConcreteType/TablePerConcreteTypeWriteTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerConcreteType/TablePerConcreteTypeWriteTests.cs index 9f2e3c3cae..8827feaa61 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerConcreteType/TablePerConcreteTypeWriteTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerConcreteType/TablePerConcreteTypeWriteTests.cs @@ -4,10 +4,6 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceInheritance.TablePerConcreteType; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class TablePerConcreteTypeWriteTests : ResourceInheritanceWriteTests -{ - public TablePerConcreteTypeWriteTests(IntegrationTestContext, TablePerConcreteTypeDbContext> testContext) - : base(testContext) - { - } -} +public sealed class TablePerConcreteTypeWriteTests( + IntegrationTestContext, TablePerConcreteTypeDbContext> testContext) + : ResourceInheritanceWriteTests(testContext); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerHierarchy/TablePerHierarchyDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerHierarchy/TablePerHierarchyDbContext.cs index 582b995d89..f9d63e7dde 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerHierarchy/TablePerHierarchyDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerHierarchy/TablePerHierarchyDbContext.cs @@ -4,10 +4,4 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceInheritance.TablePerHierarchy; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class TablePerHierarchyDbContext : ResourceInheritanceDbContext -{ - public TablePerHierarchyDbContext(DbContextOptions options) - : base(options) - { - } -} +public sealed class TablePerHierarchyDbContext(DbContextOptions options) : ResourceInheritanceDbContext(options); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerHierarchy/TablePerHierarchyReadTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerHierarchy/TablePerHierarchyReadTests.cs index e9d3df4ac0..7a5b4113e7 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerHierarchy/TablePerHierarchyReadTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerHierarchy/TablePerHierarchyReadTests.cs @@ -4,10 +4,5 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceInheritance.TablePerHierarchy; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class TablePerHierarchyReadTests : ResourceInheritanceReadTests -{ - public TablePerHierarchyReadTests(IntegrationTestContext, TablePerHierarchyDbContext> testContext) - : base(testContext) - { - } -} +public sealed class TablePerHierarchyReadTests(IntegrationTestContext, TablePerHierarchyDbContext> testContext) + : ResourceInheritanceReadTests(testContext); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerHierarchy/TablePerHierarchyWriteTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerHierarchy/TablePerHierarchyWriteTests.cs index edd81659e8..fb26f609aa 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerHierarchy/TablePerHierarchyWriteTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerHierarchy/TablePerHierarchyWriteTests.cs @@ -4,10 +4,5 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceInheritance.TablePerHierarchy; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class TablePerHierarchyWriteTests : ResourceInheritanceWriteTests -{ - public TablePerHierarchyWriteTests(IntegrationTestContext, TablePerHierarchyDbContext> testContext) - : base(testContext) - { - } -} +public sealed class TablePerHierarchyWriteTests(IntegrationTestContext, TablePerHierarchyDbContext> testContext) + : ResourceInheritanceWriteTests(testContext); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeDbContext.cs index 86a723bfd3..382a54cc3e 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeDbContext.cs @@ -7,13 +7,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceInheritance.TablePerType; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class TablePerTypeDbContext : ResourceInheritanceDbContext +public sealed class TablePerTypeDbContext(DbContextOptions options) : ResourceInheritanceDbContext(options) { - public TablePerTypeDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeReadTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeReadTests.cs index 7782f4f315..c3c64bb1a6 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeReadTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeReadTests.cs @@ -4,10 +4,5 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceInheritance.TablePerType; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class TablePerTypeReadTests : ResourceInheritanceReadTests -{ - public TablePerTypeReadTests(IntegrationTestContext, TablePerTypeDbContext> testContext) - : base(testContext) - { - } -} +public sealed class TablePerTypeReadTests(IntegrationTestContext, TablePerTypeDbContext> testContext) + : ResourceInheritanceReadTests(testContext); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeWriteTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeWriteTests.cs index ed98334583..d6516586fb 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeWriteTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeWriteTests.cs @@ -4,10 +4,5 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceInheritance.TablePerType; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class TablePerTypeWriteTests : ResourceInheritanceWriteTests -{ - public TablePerTypeWriteTests(IntegrationTestContext, TablePerTypeDbContext> testContext) - : base(testContext) - { - } -} +public sealed class TablePerTypeWriteTests(IntegrationTestContext, TablePerTypeDbContext> testContext) + : ResourceInheritanceWriteTests(testContext); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/RestrictedControllers/RestrictionDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/RestrictedControllers/RestrictionDbContext.cs index 433d50ecd3..8a5f5484de 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/RestrictedControllers/RestrictionDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/RestrictedControllers/RestrictionDbContext.cs @@ -5,15 +5,10 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.RestrictedControllers; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class RestrictionDbContext : TestableDbContext +public sealed class RestrictionDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Tables => Set
(); public DbSet Chairs => Set(); public DbSet Sofas => Set(); public DbSet Beds => Set(); - - public RestrictionDbContext(DbContextOptions options) - : base(options) - { - } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Serialization/SerializationDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Serialization/SerializationDbContext.cs index 32e093214c..c90e22f736 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Serialization/SerializationDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Serialization/SerializationDbContext.cs @@ -7,16 +7,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Serialization; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class SerializationDbContext : TestableDbContext +public sealed class SerializationDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Meetings => Set(); public DbSet Attendees => Set(); - public SerializationDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs index 6a865eaf85..c7449397a9 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs @@ -11,24 +11,18 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.SoftDeletion; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public class SoftDeletionAwareResourceService : JsonApiResourceService +public class SoftDeletionAwareResourceService( + ISystemClock systemClock, ITargetedFields targetedFields, IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, + IPaginationContext paginationContext, IJsonApiOptions options, ILoggerFactory loggerFactory, IJsonApiRequest request, + IResourceChangeTracker resourceChangeTracker, IResourceDefinitionAccessor resourceDefinitionAccessor) + : JsonApiResourceService(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, + resourceDefinitionAccessor) where TResource : class, IIdentifiable { - private readonly ISystemClock _systemClock; - private readonly ITargetedFields _targetedFields; - private readonly IResourceRepositoryAccessor _repositoryAccessor; - private readonly IJsonApiRequest _request; - - public SoftDeletionAwareResourceService(ISystemClock systemClock, ITargetedFields targetedFields, IResourceRepositoryAccessor repositoryAccessor, - IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext, IJsonApiOptions options, ILoggerFactory loggerFactory, - IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker, IResourceDefinitionAccessor resourceDefinitionAccessor) - : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, resourceDefinitionAccessor) - { - _systemClock = systemClock; - _targetedFields = targetedFields; - _repositoryAccessor = repositoryAccessor; - _request = request; - } + private readonly ISystemClock _systemClock = systemClock; + private readonly ITargetedFields _targetedFields = targetedFields; + private readonly IResourceRepositoryAccessor _repositoryAccessor = repositoryAccessor; + private readonly IJsonApiRequest _request = request; // To optimize performance, the default resource service does not always fetch all resources on write operations. // We do that here, to assure a 404 error is thrown for soft-deleted resources. diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionDbContext.cs index 3e98950be0..98da66fc3d 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionDbContext.cs @@ -7,16 +7,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.SoftDeletion; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class SoftDeletionDbContext : TestableDbContext +public sealed class SoftDeletionDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Companies => Set(); public DbSet Departments => Set(); - public SoftDeletionDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs index 3e93768683..8502500c20 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs @@ -7,17 +7,12 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ZeroKeys; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ZeroKeyDbContext : TestableDbContext +public sealed class ZeroKeyDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet Games => Set(); public DbSet Players => Set(); public DbSet Maps => Set(); - public ZeroKeyDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs index 0289585dfb..1249cd5dfa 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs @@ -176,14 +176,10 @@ public CircularServiceB(CircularServiceA serviceA) } [UsedImplicitly(ImplicitUseTargetFlags.Members)] - private sealed class DependencyContainerRegistrationDbContext : TestableDbContext + private sealed class DependencyContainerRegistrationDbContext(DbContextOptions options) + : TestableDbContext(options) { public DbSet Resources => Set(); - - public DependencyContainerRegistrationDbContext(DbContextOptions options) - : base(options) - { - } } [UsedImplicitly(ImplicitUseTargetFlags.Members)] diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Configuration/ServiceCollectionExtensionsTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Configuration/ServiceCollectionExtensionsTests.cs index 0f7e8b0b09..66bb8b6f04 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Configuration/ServiceCollectionExtensionsTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Configuration/ServiceCollectionExtensionsTests.cs @@ -567,16 +567,11 @@ public void OnSerialize(ResourceOfGuid resource) } [UsedImplicitly(ImplicitUseTargetFlags.Members)] - private sealed class TestDbContext : TestableDbContext + private sealed class TestDbContext(DbContextOptions options) : TestableDbContext(options) { public DbSet ResourcesOfInt32 => Set(); public DbSet ResourcesOfGuid => Set(); public DbSet SetOfPersons => Set(); - - public TestDbContext(DbContextOptions options) - : base(options) - { - } } [UsedImplicitly(ImplicitUseKindFlags.Access)] diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Middleware/JsonApiMiddlewareTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Middleware/JsonApiMiddlewareTests.cs index d11eb53a19..196ac6493e 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Middleware/JsonApiMiddlewareTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Middleware/JsonApiMiddlewareTests.cs @@ -191,16 +191,10 @@ private sealed class TodoItem : Identifiable public ISet Tags { get; set; } = new HashSet(); } - private sealed class FakeJsonApiRoutingConvention : IJsonApiRoutingConvention + private sealed class FakeJsonApiRoutingConvention(IResourceGraph resourceGraph, string? resourceTypePublicName) : IJsonApiRoutingConvention { - private readonly IResourceGraph _resourceGraph; - private readonly string? _resourceTypePublicName; - - public FakeJsonApiRoutingConvention(IResourceGraph resourceGraph, string? resourceTypePublicName) - { - _resourceGraph = resourceGraph; - _resourceTypePublicName = resourceTypePublicName; - } + private readonly IResourceGraph _resourceGraph = resourceGraph; + private readonly string? _resourceTypePublicName = resourceTypePublicName; public void Apply(ApplicationModel application) { diff --git a/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/FilterParseTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/FilterParseTests.cs index 1b3026e67b..24056cb9a7 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/FilterParseTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/FilterParseTests.cs @@ -246,13 +246,8 @@ public void Throws_When_No_ResourceType_In_Scope() action.Should().ThrowExactly().WithMessage("No resource type is currently in scope. Call Parse() first."); } - private sealed class NotDisposingFilterParser : FilterParser + private sealed class NotDisposingFilterParser(IResourceFactory resourceFactory) : FilterParser(resourceFactory) { - public NotDisposingFilterParser(IResourceFactory resourceFactory) - : base(resourceFactory) - { - } - protected override FilterExpression ParseFilter() { // Forgot to dispose the return value. @@ -262,13 +257,8 @@ protected override FilterExpression ParseFilter() } } - private sealed class ResourceTypeAccessingFilterParser : FilterParser + private sealed class ResourceTypeAccessingFilterParser(IResourceFactory resourceFactory) : FilterParser(resourceFactory) { - public ResourceTypeAccessingFilterParser(IResourceFactory resourceFactory) - : base(resourceFactory) - { - } - protected override void Tokenize(string source) { // There is no resource type in scope yet. diff --git a/test/JsonApiDotNetCoreTests/UnitTests/ResourceDefinitions/CreateSortExpressionFromLambdaTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/ResourceDefinitions/CreateSortExpressionFromLambdaTests.cs index 337940b75c..30e8a899dc 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/ResourceDefinitions/CreateSortExpressionFromLambdaTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/ResourceDefinitions/CreateSortExpressionFromLambdaTests.cs @@ -345,14 +345,9 @@ private static IResourceGraph GetResourceGraph() // @formatter:wrap_before_first_method_call restore } - private sealed class WrapperResourceDefinition : JsonApiResourceDefinition + private sealed class WrapperResourceDefinition(IResourceGraph resourceGraph) : JsonApiResourceDefinition(resourceGraph) where TResource : class, IIdentifiable { - public WrapperResourceDefinition(IResourceGraph resourceGraph) - : base(resourceGraph) - { - } - public SortExpression GetSortExpressionFromLambda(PropertySortOrder sortOrder) { return CreateSortExpressionFromLambda(sortOrder); diff --git a/test/MultiDbContextTests/ResourceTests.cs b/test/MultiDbContextTests/ResourceTests.cs index ed25d0f274..3b2c259c57 100644 --- a/test/MultiDbContextTests/ResourceTests.cs +++ b/test/MultiDbContextTests/ResourceTests.cs @@ -10,9 +10,9 @@ namespace MultiDbContextTests; -public sealed class ResourceTests : IntegrationTest, IClassFixture> +public sealed class ResourceTests(NoLoggingWebApplicationFactory factory) : IntegrationTest, IClassFixture> { - private readonly NoLoggingWebApplicationFactory _factory; + private readonly NoLoggingWebApplicationFactory _factory = factory; protected override JsonSerializerOptions SerializerOptions { @@ -23,11 +23,6 @@ protected override JsonSerializerOptions SerializerOptions } } - public ResourceTests(NoLoggingWebApplicationFactory factory) - { - _factory = factory; - } - [Fact] public async Task Can_get_ResourceAs() { diff --git a/test/NoEntityFrameworkTests/PersonTests.cs b/test/NoEntityFrameworkTests/PersonTests.cs index 965cac9a3e..01cdefa3e5 100644 --- a/test/NoEntityFrameworkTests/PersonTests.cs +++ b/test/NoEntityFrameworkTests/PersonTests.cs @@ -10,9 +10,9 @@ namespace NoEntityFrameworkTests; -public sealed class PersonTests : IntegrationTest, IClassFixture> +public sealed class PersonTests(NoLoggingWebApplicationFactory factory) : IntegrationTest, IClassFixture> { - private readonly NoLoggingWebApplicationFactory _factory; + private readonly NoLoggingWebApplicationFactory _factory = factory; protected override JsonSerializerOptions SerializerOptions { @@ -23,11 +23,6 @@ protected override JsonSerializerOptions SerializerOptions } } - public PersonTests(NoLoggingWebApplicationFactory factory) - { - _factory = factory; - } - [Fact] public async Task Can_get_primary_resources() { diff --git a/test/NoEntityFrameworkTests/TagTests.cs b/test/NoEntityFrameworkTests/TagTests.cs index 83842b7725..49b5887adb 100644 --- a/test/NoEntityFrameworkTests/TagTests.cs +++ b/test/NoEntityFrameworkTests/TagTests.cs @@ -10,9 +10,9 @@ namespace NoEntityFrameworkTests; -public sealed class TagTests : IntegrationTest, IClassFixture> +public sealed class TagTests(NoLoggingWebApplicationFactory factory) : IntegrationTest, IClassFixture> { - private readonly NoLoggingWebApplicationFactory _factory; + private readonly NoLoggingWebApplicationFactory _factory = factory; protected override JsonSerializerOptions SerializerOptions { @@ -23,11 +23,6 @@ protected override JsonSerializerOptions SerializerOptions } } - public TagTests(NoLoggingWebApplicationFactory factory) - { - _factory = factory; - } - [Fact] public async Task Can_get_primary_resources() { diff --git a/test/NoEntityFrameworkTests/TodoItemTests.cs b/test/NoEntityFrameworkTests/TodoItemTests.cs index 3fdd5683c5..90691fd770 100644 --- a/test/NoEntityFrameworkTests/TodoItemTests.cs +++ b/test/NoEntityFrameworkTests/TodoItemTests.cs @@ -10,9 +10,9 @@ namespace NoEntityFrameworkTests; -public sealed class TodoItemTests : IntegrationTest, IClassFixture> +public sealed class TodoItemTests(NoLoggingWebApplicationFactory factory) : IntegrationTest, IClassFixture> { - private readonly NoLoggingWebApplicationFactory _factory; + private readonly NoLoggingWebApplicationFactory _factory = factory; protected override JsonSerializerOptions SerializerOptions { @@ -23,11 +23,6 @@ protected override JsonSerializerOptions SerializerOptions } } - public TodoItemTests(NoLoggingWebApplicationFactory factory) - { - _factory = factory; - } - [Fact] public async Task Can_get_primary_resources() { diff --git a/test/SourceGeneratorTests/GeneratorDriverRunResultExtensions.cs b/test/SourceGeneratorTests/GeneratorDriverRunResultExtensions.cs index bb8e1bfd04..f54f9767d7 100644 --- a/test/SourceGeneratorTests/GeneratorDriverRunResultExtensions.cs +++ b/test/SourceGeneratorTests/GeneratorDriverRunResultExtensions.cs @@ -12,15 +12,11 @@ public static GeneratorDriverRunResultAssertions Should(this GeneratorDriverRunR return new GeneratorDriverRunResultAssertions(instance); } - internal sealed class GeneratorDriverRunResultAssertions : ReferenceTypeAssertions + internal sealed class GeneratorDriverRunResultAssertions(GeneratorDriverRunResult subject) + : ReferenceTypeAssertions(subject) { protected override string Identifier => nameof(GeneratorDriverRunResult); - public GeneratorDriverRunResultAssertions(GeneratorDriverRunResult subject) - : base(subject) - { - } - public void NotHaveDiagnostics() { Subject.Diagnostics.Should().BeEmpty(); diff --git a/test/TestBuildingBlocks/FakeLogMessage.cs b/test/TestBuildingBlocks/FakeLogMessage.cs index 8df3eebde6..cd6cc2bdaf 100644 --- a/test/TestBuildingBlocks/FakeLogMessage.cs +++ b/test/TestBuildingBlocks/FakeLogMessage.cs @@ -4,16 +4,10 @@ namespace TestBuildingBlocks; [PublicAPI] -public sealed class FakeLogMessage +public sealed class FakeLogMessage(LogLevel logLevel, string text) { - public LogLevel LogLevel { get; } - public string Text { get; } - - public FakeLogMessage(LogLevel logLevel, string text) - { - LogLevel = logLevel; - Text = text; - } + public LogLevel LogLevel { get; } = logLevel; + public string Text { get; } = text; public override string ToString() { diff --git a/test/TestBuildingBlocks/FakeLoggerFactory.cs b/test/TestBuildingBlocks/FakeLoggerFactory.cs index 9d8e74c5b3..cb726bb4d7 100644 --- a/test/TestBuildingBlocks/FakeLoggerFactory.cs +++ b/test/TestBuildingBlocks/FakeLoggerFactory.cs @@ -4,14 +4,9 @@ namespace TestBuildingBlocks; [PublicAPI] -public sealed class FakeLoggerFactory : ILoggerFactory, ILoggerProvider +public sealed class FakeLoggerFactory(LogLevel minimumLevel) : ILoggerFactory, ILoggerProvider { - public FakeLogger Logger { get; } - - public FakeLoggerFactory(LogLevel minimumLevel) - { - Logger = new FakeLogger(minimumLevel); - } + public FakeLogger Logger { get; } = new(minimumLevel); public ILogger CreateLogger(string categoryName) { @@ -26,18 +21,13 @@ public void Dispose() { } - public sealed class FakeLogger : ILogger + public sealed class FakeLogger(LogLevel minimumLevel) : ILogger { - private readonly LogLevel _minimumLevel; + private readonly LogLevel _minimumLevel = minimumLevel; private readonly object _lockObject = new(); private readonly List _messages = []; - public FakeLogger(LogLevel minimumLevel) - { - _minimumLevel = minimumLevel; - } - public bool IsEnabled(LogLevel logLevel) { return _minimumLevel != LogLevel.None && logLevel >= _minimumLevel; diff --git a/test/TestBuildingBlocks/TestableDbContext.cs b/test/TestBuildingBlocks/TestableDbContext.cs index 65cb8872be..a91a8530ad 100644 --- a/test/TestBuildingBlocks/TestableDbContext.cs +++ b/test/TestBuildingBlocks/TestableDbContext.cs @@ -5,13 +5,8 @@ namespace TestBuildingBlocks; -public abstract class TestableDbContext : DbContext +public abstract class TestableDbContext(DbContextOptions options) : DbContext(options) { - protected TestableDbContext(DbContextOptions options) - : base(options) - { - } - protected override void OnConfiguring(DbContextOptionsBuilder builder) { WriteSqlStatementsToOutputWindow(builder); diff --git a/test/TestBuildingBlocks/XUnitLoggerProvider.cs b/test/TestBuildingBlocks/XUnitLoggerProvider.cs index e19f8cbbc6..6b5e7f93ec 100644 --- a/test/TestBuildingBlocks/XUnitLoggerProvider.cs +++ b/test/TestBuildingBlocks/XUnitLoggerProvider.cs @@ -38,18 +38,11 @@ public void Dispose() { } - private sealed class XUnitLogger : ILogger + private sealed class XUnitLogger(ITestOutputHelper testOutputHelper, LogOutputFields outputFields, string categoryName) : ILogger { - private readonly ITestOutputHelper _testOutputHelper; - private readonly LogOutputFields _outputFields; - private readonly string _categoryName; - - public XUnitLogger(ITestOutputHelper testOutputHelper, LogOutputFields outputFields, string categoryName) - { - _testOutputHelper = testOutputHelper; - _outputFields = outputFields; - _categoryName = categoryName; - } + private readonly ITestOutputHelper _testOutputHelper = testOutputHelper; + private readonly LogOutputFields _outputFields = outputFields; + private readonly string _categoryName = categoryName; public bool IsEnabled(LogLevel logLevel) { From c2fb4920961f2fa5039df44f0940f0b236f4fa27 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 21 Dec 2023 14:16:19 +0100 Subject: [PATCH 16/91] Resharper: Turn off EF Core warnings about unlimited string length --- JsonApiDotNetCore.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings index ce137df506..6e4e064588 100644 --- a/JsonApiDotNetCore.sln.DotSettings +++ b/JsonApiDotNetCore.sln.DotSettings @@ -54,6 +54,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$); WARNING WARNING WARNING + DO_NOT_SHOW WARNING SUGGESTION HINT From 61388ddf54e0c0f03e3f74463c6b228eada98b04 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 21 Dec 2023 14:17:04 +0100 Subject: [PATCH 17/91] Remove redundant compilation conditionals --- .../Serialization/Response/FingerprintGenerator.cs | 4 ---- .../NullSafeExpressionRewriterTests.cs | 8 +++----- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/JsonApiDotNetCore/Serialization/Response/FingerprintGenerator.cs b/src/JsonApiDotNetCore/Serialization/Response/FingerprintGenerator.cs index 3ecc0b2c5a..5baef6c086 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/FingerprintGenerator.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/FingerprintGenerator.cs @@ -6,11 +6,7 @@ namespace JsonApiDotNetCore.Serialization.Response; /// internal sealed class FingerprintGenerator : IFingerprintGenerator { -#if NET6_0 - private static readonly byte[] Separator = Encoding.UTF8.GetBytes("|"); -#else private static readonly byte[] Separator = "|"u8.ToArray(); -#endif private static readonly uint[] LookupTable = Enumerable.Range(0, 256).Select(ToLookupEntry).ToArray(); private static uint ToLookupEntry(int index) diff --git a/test/NoEntityFrameworkTests/NullSafeExpressionRewriterTests.cs b/test/NoEntityFrameworkTests/NullSafeExpressionRewriterTests.cs index e8fe1585ca..0a61821987 100644 --- a/test/NoEntityFrameworkTests/NullSafeExpressionRewriterTests.cs +++ b/test/NoEntityFrameworkTests/NullSafeExpressionRewriterTests.cs @@ -10,6 +10,8 @@ namespace NoEntityFrameworkTests; public sealed class NullSafeExpressionRewriterTests { + private const nint OnePointer = 1; + [Fact] public void Can_rewrite_where_clause_with_constant_comparison() { @@ -498,11 +500,7 @@ public void Can_rewrite_order_by_clause_with_IntPtr() Parent = new TestResource { Id = generator.GetNext(), -#if NET6_0 - Pointer = (IntPtr)1 -#else - Pointer = 1 -#endif + Pointer = OnePointer } } }; From aac053f9504d559063fc29e5d2aafc9a511cf3f1 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 21 Dec 2023 15:26:48 +0100 Subject: [PATCH 18/91] Fix broken cleanupcode.ps1 --- cleanupcode.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cleanupcode.ps1 b/cleanupcode.ps1 index 237f854be3..b35d1cb215 100644 --- a/cleanupcode.ps1 +++ b/cleanupcode.ps1 @@ -28,12 +28,12 @@ if ($revision) { if ($baseCommitHash -eq $headCommitHash) { Write-Output "Running code cleanup on staged/unstaged files." - dotnet regitlint -s JsonApiDotNetCore.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 + dotnet regitlint -s JsonApiDotNetCore.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.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 + dotnet regitlint -s JsonApiDotNetCore.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 } } From d60e3546d724303e761b580379838044539044d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 21:18:40 +0100 Subject: [PATCH 19/91] Bump actions/download-artifact from 3 to 4 (#1426) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7efd2cef0e..73b5812fc1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -286,7 +286,7 @@ jobs: - name: Tune GitHub-hosted runner network uses: smorimoto/tune-github-hosted-runner-network@v1 - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 - name: Publish to GitHub Packages if: github.event_name == 'push' || github.event_name == 'release' env: From aa3a0bcd5615e61c2d2df05d25831fe92fcda5ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 21:18:56 +0100 Subject: [PATCH 20/91] Bump actions/upload-artifact from 3 to 4 (#1424) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- .github/workflows/qodana.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 73b5812fc1..06609b8ccf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -145,7 +145,7 @@ jobs: 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@v3 + uses: actions/upload-artifact@v4 with: name: packages path: artifacts/packages @@ -168,7 +168,7 @@ jobs: Copy-Item -Recurse home/assets/* _site/styles/ - name: Upload documentation to artifacts if: matrix.os == 'ubuntu-latest' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: documentation path: docs/_site diff --git a/.github/workflows/qodana.yml b/.github/workflows/qodana.yml index 9225d9f816..a53c3d246c 100644 --- a/.github/workflows/qodana.yml +++ b/.github/workflows/qodana.yml @@ -27,7 +27,7 @@ jobs: QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} - name: Upload results to artifacts on failure if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: qodana_results path: ${{ runner.temp }}/qodana/results From c5195dbdff8acb251705865c445dcdcf35e0be75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 21:19:15 +0100 Subject: [PATCH 21/91] Bump github/codeql-action from 2 to 3 (#1425) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9a58eeb4a7..eb0375769e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -32,12 +32,12 @@ jobs: - name: Git checkout uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" From 2a30f19791f3e8f3d20ee74259e63ac8c7b508d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 21:19:40 +0100 Subject: [PATCH 22/91] Bump JetBrains/qodana-action from 2023.2 to 2023.3 (#1427) Bumps [JetBrains/qodana-action](https://github.com/jetbrains/qodana-action) from 2023.2 to 2023.3. - [Release notes](https://github.com/jetbrains/qodana-action/releases) - [Commits](https://github.com/jetbrains/qodana-action/compare/v2023.2...v2023.3) --- updated-dependencies: - dependency-name: JetBrains/qodana-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/qodana.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/qodana.yml b/.github/workflows/qodana.yml index a53c3d246c..f1a64da824 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.2 + uses: JetBrains/qodana-action@v2023.3 env: QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} - name: Upload results to artifacts on failure From 2637d0db8d5e459e614ec2c9e1c18b8daed4c316 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Fri, 22 Dec 2023 02:11:47 +0100 Subject: [PATCH 23/91] Removes building an intermediate service provider at startup, which is considered an anti-pattern, leading to occasional compatibility issues. (#1431) This unblocks use in [Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/) (more specifically, its usage of `AddNpgsqlDataSource`, which throws an `ObjectDisposedException`) and fixes #1082. --- .../InjectablesAssemblyScanner.cs | 129 ++++++++++++++++ .../JsonApiApplicationBuilder.cs | 76 ++++----- .../Configuration/ResourcesAssemblyScanner.cs | 29 ++++ .../ServiceCollectionExtensions.cs | 8 +- .../Configuration/ServiceDiscoveryFacade.cs | 146 +----------------- 5 files changed, 209 insertions(+), 179 deletions(-) create mode 100644 src/JsonApiDotNetCore/Configuration/InjectablesAssemblyScanner.cs create mode 100644 src/JsonApiDotNetCore/Configuration/ResourcesAssemblyScanner.cs diff --git a/src/JsonApiDotNetCore/Configuration/InjectablesAssemblyScanner.cs b/src/JsonApiDotNetCore/Configuration/InjectablesAssemblyScanner.cs new file mode 100644 index 0000000000..9985533776 --- /dev/null +++ b/src/JsonApiDotNetCore/Configuration/InjectablesAssemblyScanner.cs @@ -0,0 +1,129 @@ +using System.Reflection; +using JsonApiDotNetCore.Repositories; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace JsonApiDotNetCore.Configuration; + +/// +/// Scans assemblies for injectables (types that implement , +/// or ) and registers them in the IoC container. +/// +internal sealed class InjectablesAssemblyScanner +{ + internal static readonly HashSet ServiceUnboundInterfaces = + [ + typeof(IResourceService<,>), + typeof(IResourceCommandService<,>), + typeof(IResourceQueryService<,>), + typeof(IGetAllService<,>), + typeof(IGetByIdService<,>), + typeof(IGetSecondaryService<,>), + typeof(IGetRelationshipService<,>), + typeof(ICreateService<,>), + typeof(IAddToRelationshipService<,>), + typeof(IUpdateService<,>), + typeof(ISetRelationshipService<,>), + typeof(IDeleteService<,>), + typeof(IRemoveFromRelationshipService<,>) + ]; + + internal static readonly HashSet RepositoryUnboundInterfaces = + [ + typeof(IResourceRepository<,>), + typeof(IResourceWriteRepository<,>), + typeof(IResourceReadRepository<,>) + ]; + + internal static readonly HashSet ResourceDefinitionUnboundInterfaces = [typeof(IResourceDefinition<,>)]; + + private readonly ResourceDescriptorAssemblyCache _assemblyCache; + private readonly IServiceCollection _services; + private readonly TypeLocator _typeLocator = new(); + + public InjectablesAssemblyScanner(ResourceDescriptorAssemblyCache assemblyCache, IServiceCollection services) + { + ArgumentGuard.NotNull(assemblyCache); + ArgumentGuard.NotNull(services); + + _assemblyCache = assemblyCache; + _services = services; + } + + public void DiscoverInjectables() + { + IReadOnlyCollection descriptors = _assemblyCache.GetResourceDescriptors(); + IReadOnlyCollection assemblies = _assemblyCache.GetAssemblies(); + + foreach (Assembly assembly in assemblies) + { + AddDbContextResolvers(assembly); + AddInjectables(descriptors, assembly); + } + } + + private void AddDbContextResolvers(Assembly assembly) + { + IEnumerable dbContextTypes = _typeLocator.GetDerivedTypes(assembly, typeof(DbContext)); + + foreach (Type dbContextType in dbContextTypes) + { + Type dbContextResolverClosedType = typeof(DbContextResolver<>).MakeGenericType(dbContextType); + _services.TryAddScoped(typeof(IDbContextResolver), dbContextResolverClosedType); + } + } + + private void AddInjectables(IEnumerable resourceDescriptors, Assembly assembly) + { + foreach (ResourceDescriptor resourceDescriptor in resourceDescriptors) + { + AddServices(assembly, resourceDescriptor); + AddRepositories(assembly, resourceDescriptor); + AddResourceDefinitions(assembly, resourceDescriptor); + } + } + + private void AddServices(Assembly assembly, ResourceDescriptor resourceDescriptor) + { + foreach (Type serviceUnboundInterface in ServiceUnboundInterfaces) + { + RegisterImplementations(assembly, serviceUnboundInterface, resourceDescriptor); + } + } + + private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescriptor) + { + foreach (Type repositoryUnboundInterface in RepositoryUnboundInterfaces) + { + RegisterImplementations(assembly, repositoryUnboundInterface, resourceDescriptor); + } + } + + private void AddResourceDefinitions(Assembly assembly, ResourceDescriptor resourceDescriptor) + { + foreach (Type resourceDefinitionUnboundInterface in ResourceDefinitionUnboundInterfaces) + { + RegisterImplementations(assembly, resourceDefinitionUnboundInterface, resourceDescriptor); + } + } + + private void RegisterImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor) + { + Type[] typeArguments = + [ + resourceDescriptor.ResourceClrType, + resourceDescriptor.IdClrType + ]; + + (Type implementationType, Type serviceInterface)? result = _typeLocator.GetContainerRegistrationFromAssembly(assembly, interfaceType, typeArguments); + + if (result != null) + { + (Type implementationType, Type serviceInterface) = result.Value; + _services.TryAddScoped(serviceInterface, implementationType); + } + } +} diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs index 17ca6677c3..2004178ccd 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs @@ -23,17 +23,15 @@ namespace JsonApiDotNetCore.Configuration; /// -/// A utility class that builds a JsonApi application. It registers all required services and allows the user to override parts of the startup +/// A utility class that builds a JSON:API application. It registers all required services and allows the user to override parts of the startup /// configuration. /// -internal sealed class JsonApiApplicationBuilder : IJsonApiApplicationBuilder, IDisposable +internal sealed class JsonApiApplicationBuilder : IJsonApiApplicationBuilder { - private readonly JsonApiOptions _options = new(); private readonly IServiceCollection _services; private readonly IMvcCoreBuilder _mvcBuilder; - private readonly ResourceGraphBuilder _resourceGraphBuilder; - private readonly ServiceDiscoveryFacade _serviceDiscoveryFacade; - private readonly ServiceProvider _intermediateProvider; + private readonly JsonApiOptions _options = new(); + private readonly ResourceDescriptorAssemblyCache _assemblyCache = new(); public Action? ConfigureMvcOptions { get; set; } @@ -44,12 +42,6 @@ public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mv _services = services; _mvcBuilder = mvcBuilder; - _intermediateProvider = services.BuildServiceProvider(); - - var loggerFactory = _intermediateProvider.GetRequiredService(); - - _resourceGraphBuilder = new ResourceGraphBuilder(_options, loggerFactory); - _serviceDiscoveryFacade = new ServiceDiscoveryFacade(_services, _resourceGraphBuilder, loggerFactory); } /// @@ -61,35 +53,51 @@ public void ConfigureJsonApiOptions(Action? configureOptions) } /// - /// Executes the action provided by the user to configure . + /// Executes the action provided by the user to configure auto-discovery. /// public void ConfigureAutoDiscovery(Action? configureAutoDiscovery) { - configureAutoDiscovery?.Invoke(_serviceDiscoveryFacade); + if (configureAutoDiscovery != null) + { + var facade = new ServiceDiscoveryFacade(_assemblyCache); + configureAutoDiscovery.Invoke(facade); + } } /// - /// Configures and builds the resource graph with resources from the provided sources and adds it to the DI container. + /// Configures and builds the resource graph with resources from the provided sources and adds them to the IoC container. /// public void ConfigureResourceGraph(ICollection dbContextTypes, Action? configureResourceGraph) { ArgumentGuard.NotNull(dbContextTypes); - _serviceDiscoveryFacade.DiscoverResources(); - - foreach (Type dbContextType in dbContextTypes) + _services.TryAddSingleton(serviceProvider => { - var dbContext = (DbContext)_intermediateProvider.GetRequiredService(dbContextType); - _resourceGraphBuilder.Add(dbContext); - } + var loggerFactory = serviceProvider.GetRequiredService(); + var resourceGraphBuilder = new ResourceGraphBuilder(_options, loggerFactory); - configureResourceGraph?.Invoke(_resourceGraphBuilder); + var scanner = new ResourcesAssemblyScanner(_assemblyCache, resourceGraphBuilder); + scanner.DiscoverResources(); - IResourceGraph resourceGraph = _resourceGraphBuilder.Build(); + if (dbContextTypes.Count > 0) + { + using IServiceScope scope = serviceProvider.CreateScope(); - _options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph)); + foreach (Type dbContextType in dbContextTypes) + { + var dbContext = (DbContext)scope.ServiceProvider.GetRequiredService(dbContextType); + resourceGraphBuilder.Add(dbContext); + } + } + + configureResourceGraph?.Invoke(resourceGraphBuilder); + + IResourceGraph resourceGraph = resourceGraphBuilder.Build(); + + _options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph)); - _services.TryAddSingleton(resourceGraph); + return resourceGraph; + }); } /// @@ -114,15 +122,16 @@ public void ConfigureMvc() } /// - /// Discovers DI registrable services in the assemblies marked for discovery. + /// Registers injectables in the IoC container found in assemblies marked for auto-discovery. /// public void DiscoverInjectables() { - _serviceDiscoveryFacade.DiscoverInjectables(); + var scanner = new InjectablesAssemblyScanner(_assemblyCache, _services); + scanner.DiscoverInjectables(); } /// - /// Registers the remaining internals. + /// Registers the remaining internals in the IoC container. /// public void ConfigureServiceContainer(ICollection dbContextTypes) { @@ -182,7 +191,7 @@ private void AddMiddlewareLayer() private void AddResourceLayer() { - RegisterImplementationForInterfaces(ServiceDiscoveryFacade.ResourceDefinitionUnboundInterfaces, typeof(JsonApiResourceDefinition<,>)); + RegisterImplementationForInterfaces(InjectablesAssemblyScanner.ResourceDefinitionUnboundInterfaces, typeof(JsonApiResourceDefinition<,>)); _services.TryAddScoped(); _services.TryAddScoped(); @@ -190,7 +199,7 @@ private void AddResourceLayer() private void AddRepositoryLayer() { - RegisterImplementationForInterfaces(ServiceDiscoveryFacade.RepositoryUnboundInterfaces, typeof(EntityFrameworkCoreRepository<,>)); + RegisterImplementationForInterfaces(InjectablesAssemblyScanner.RepositoryUnboundInterfaces, typeof(EntityFrameworkCoreRepository<,>)); _services.TryAddScoped(); @@ -204,7 +213,7 @@ private void AddRepositoryLayer() private void AddServiceLayer() { - RegisterImplementationForInterfaces(ServiceDiscoveryFacade.ServiceUnboundInterfaces, typeof(JsonApiResourceService<,>)); + RegisterImplementationForInterfaces(InjectablesAssemblyScanner.ServiceUnboundInterfaces, typeof(JsonApiResourceService<,>)); } private void RegisterImplementationForInterfaces(HashSet unboundInterfaces, Type unboundImplementationType) @@ -291,9 +300,4 @@ private void AddOperationsLayer() _services.TryAddScoped(); _services.TryAddScoped(); } - - public void Dispose() - { - _intermediateProvider.Dispose(); - } } diff --git a/src/JsonApiDotNetCore/Configuration/ResourcesAssemblyScanner.cs b/src/JsonApiDotNetCore/Configuration/ResourcesAssemblyScanner.cs new file mode 100644 index 0000000000..cdfdc4446c --- /dev/null +++ b/src/JsonApiDotNetCore/Configuration/ResourcesAssemblyScanner.cs @@ -0,0 +1,29 @@ +using JsonApiDotNetCore.Resources; + +namespace JsonApiDotNetCore.Configuration; + +/// +/// Scans assemblies for types that implement and adds them to the resource graph. +/// +internal sealed class ResourcesAssemblyScanner +{ + private readonly ResourceDescriptorAssemblyCache _assemblyCache; + private readonly ResourceGraphBuilder _resourceGraphBuilder; + + public ResourcesAssemblyScanner(ResourceDescriptorAssemblyCache assemblyCache, ResourceGraphBuilder resourceGraphBuilder) + { + ArgumentGuard.NotNull(assemblyCache); + ArgumentGuard.NotNull(resourceGraphBuilder); + + _assemblyCache = assemblyCache; + _resourceGraphBuilder = resourceGraphBuilder; + } + + public void DiscoverResources() + { + foreach (ResourceDescriptor resourceDescriptor in _assemblyCache.GetResourceDescriptors()) + { + _resourceGraphBuilder.Add(resourceDescriptor.ResourceClrType, resourceDescriptor.IdClrType); + } + } +} diff --git a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs index b25c208086..5ad4474d95 100644 --- a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs @@ -43,7 +43,7 @@ private static void SetupApplicationBuilder(IServiceCollection services, Action< Action? configureAutoDiscovery, Action? configureResources, IMvcCoreBuilder? mvcBuilder, ICollection dbContextTypes) { - using var applicationBuilder = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore()); + var applicationBuilder = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore()); applicationBuilder.ConfigureJsonApiOptions(configureOptions); applicationBuilder.ConfigureAutoDiscovery(configureAutoDiscovery); @@ -61,7 +61,7 @@ public static IServiceCollection AddResourceService(this IServiceColle { ArgumentGuard.NotNull(services); - RegisterTypeForUnboundInterfaces(services, typeof(TService), ServiceDiscoveryFacade.ServiceUnboundInterfaces); + RegisterTypeForUnboundInterfaces(services, typeof(TService), InjectablesAssemblyScanner.ServiceUnboundInterfaces); return services; } @@ -74,7 +74,7 @@ public static IServiceCollection AddResourceRepository(this IServic { ArgumentGuard.NotNull(services); - RegisterTypeForUnboundInterfaces(services, typeof(TRepository), ServiceDiscoveryFacade.RepositoryUnboundInterfaces); + RegisterTypeForUnboundInterfaces(services, typeof(TRepository), InjectablesAssemblyScanner.RepositoryUnboundInterfaces); return services; } @@ -87,7 +87,7 @@ public static IServiceCollection AddResourceDefinition(this { ArgumentGuard.NotNull(services); - RegisterTypeForUnboundInterfaces(services, typeof(TResourceDefinition), ServiceDiscoveryFacade.ResourceDefinitionUnboundInterfaces); + RegisterTypeForUnboundInterfaces(services, typeof(TResourceDefinition), InjectablesAssemblyScanner.ResourceDefinitionUnboundInterfaces); return services; } diff --git a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs index 01f9ecc7cb..3139930852 100644 --- a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs +++ b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs @@ -1,66 +1,23 @@ using System.Reflection; -using JetBrains.Annotations; -using JsonApiDotNetCore.Repositories; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Services; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Configuration; /// -/// Scans for types like resources, services, repositories and resource definitions in an assembly and registers them to the IoC container. +/// Provides auto-discovery by scanning assemblies for resources and related injectables. /// -[PublicAPI] public sealed class ServiceDiscoveryFacade { - internal static readonly HashSet ServiceUnboundInterfaces = - [ - typeof(IResourceService<,>), - typeof(IResourceCommandService<,>), - typeof(IResourceQueryService<,>), - typeof(IGetAllService<,>), - typeof(IGetByIdService<,>), - typeof(IGetSecondaryService<,>), - typeof(IGetRelationshipService<,>), - typeof(ICreateService<,>), - typeof(IAddToRelationshipService<,>), - typeof(IUpdateService<,>), - typeof(ISetRelationshipService<,>), - typeof(IDeleteService<,>), - typeof(IRemoveFromRelationshipService<,>) - ]; + private readonly ResourceDescriptorAssemblyCache _assemblyCache; - internal static readonly HashSet RepositoryUnboundInterfaces = - [ - typeof(IResourceRepository<,>), - typeof(IResourceWriteRepository<,>), - typeof(IResourceReadRepository<,>) - ]; - - internal static readonly HashSet ResourceDefinitionUnboundInterfaces = [typeof(IResourceDefinition<,>)]; - - private readonly ILogger _logger; - private readonly IServiceCollection _services; - private readonly ResourceGraphBuilder _resourceGraphBuilder; - private readonly ResourceDescriptorAssemblyCache _assemblyCache = new(); - private readonly TypeLocator _typeLocator = new(); - - public ServiceDiscoveryFacade(IServiceCollection services, ResourceGraphBuilder resourceGraphBuilder, ILoggerFactory loggerFactory) + internal ServiceDiscoveryFacade(ResourceDescriptorAssemblyCache assemblyCache) { - ArgumentGuard.NotNull(services); - ArgumentGuard.NotNull(resourceGraphBuilder); - ArgumentGuard.NotNull(loggerFactory); + ArgumentGuard.NotNull(assemblyCache); - _logger = loggerFactory.CreateLogger(); - _services = services; - _resourceGraphBuilder = resourceGraphBuilder; + _assemblyCache = assemblyCache; } /// - /// Mark the calling assembly for scanning of resources and injectables. + /// Includes the calling assembly for auto-discovery of resources and related injectables. /// public ServiceDiscoveryFacade AddCurrentAssembly() { @@ -68,102 +25,13 @@ public ServiceDiscoveryFacade AddCurrentAssembly() } /// - /// Mark the specified assembly for scanning of resources and injectables. + /// Includes the specified assembly for auto-discovery of resources and related injectables. /// public ServiceDiscoveryFacade AddAssembly(Assembly assembly) { ArgumentGuard.NotNull(assembly); _assemblyCache.RegisterAssembly(assembly); - _logger.LogDebug($"Registering assembly '{assembly.FullName}' for discovery of resources and injectables."); - return this; } - - internal void DiscoverResources() - { - foreach (ResourceDescriptor resourceDescriptor in _assemblyCache.GetResourceDescriptors()) - { - AddResource(resourceDescriptor); - } - } - - internal void DiscoverInjectables() - { - IReadOnlyCollection descriptors = _assemblyCache.GetResourceDescriptors(); - IReadOnlyCollection assemblies = _assemblyCache.GetAssemblies(); - - foreach (Assembly assembly in assemblies) - { - AddDbContextResolvers(assembly); - AddInjectables(descriptors, assembly); - } - } - - private void AddInjectables(IReadOnlyCollection resourceDescriptors, Assembly assembly) - { - foreach (ResourceDescriptor resourceDescriptor in resourceDescriptors) - { - AddServices(assembly, resourceDescriptor); - AddRepositories(assembly, resourceDescriptor); - AddResourceDefinitions(assembly, resourceDescriptor); - } - } - - private void AddDbContextResolvers(Assembly assembly) - { - IEnumerable dbContextTypes = _typeLocator.GetDerivedTypes(assembly, typeof(DbContext)); - - foreach (Type dbContextType in dbContextTypes) - { - Type dbContextResolverClosedType = typeof(DbContextResolver<>).MakeGenericType(dbContextType); - _services.TryAddScoped(typeof(IDbContextResolver), dbContextResolverClosedType); - } - } - - private void AddResource(ResourceDescriptor resourceDescriptor) - { - _resourceGraphBuilder.Add(resourceDescriptor.ResourceClrType, resourceDescriptor.IdClrType); - } - - private void AddServices(Assembly assembly, ResourceDescriptor resourceDescriptor) - { - foreach (Type serviceUnboundInterface in ServiceUnboundInterfaces) - { - RegisterImplementations(assembly, serviceUnboundInterface, resourceDescriptor); - } - } - - private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescriptor) - { - foreach (Type repositoryUnboundInterface in RepositoryUnboundInterfaces) - { - RegisterImplementations(assembly, repositoryUnboundInterface, resourceDescriptor); - } - } - - private void AddResourceDefinitions(Assembly assembly, ResourceDescriptor resourceDescriptor) - { - foreach (Type resourceDefinitionUnboundInterface in ResourceDefinitionUnboundInterfaces) - { - RegisterImplementations(assembly, resourceDefinitionUnboundInterface, resourceDescriptor); - } - } - - private void RegisterImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor) - { - Type[] typeArguments = - [ - resourceDescriptor.ResourceClrType, - resourceDescriptor.IdClrType - ]; - - (Type implementationType, Type serviceInterface)? result = _typeLocator.GetContainerRegistrationFromAssembly(assembly, interfaceType, typeArguments); - - if (result != null) - { - (Type implementationType, Type serviceInterface) = result.Value; - _services.TryAddScoped(serviceInterface, implementationType); - } - } } From b134b4b1572205eb081c41c4c1799c992059bb6d Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 27 Dec 2023 11:51:37 +0100 Subject: [PATCH 24/91] Restore PublicAPI to suppress Resharper warning --- src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs index 3139930852..615ef244fc 100644 --- a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs +++ b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs @@ -1,10 +1,12 @@ using System.Reflection; +using JetBrains.Annotations; namespace JsonApiDotNetCore.Configuration; /// /// Provides auto-discovery by scanning assemblies for resources and related injectables. /// +[PublicAPI] public sealed class ServiceDiscoveryFacade { private readonly ResourceDescriptorAssemblyCache _assemblyCache; From 89f767edaa79ef97a2bec7d8288a418ca233ee75 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 27 Dec 2023 12:01:39 +0100 Subject: [PATCH 25/91] Package updates --- package-versions.props | 7 ++++--- .../IsUpperCase/IsUpperCaseFilterParseTests.cs | 2 +- .../CustomFunctions/StringLength/LengthFilterParseTests.cs | 2 +- .../CustomFunctions/StringLength/LengthSortParseTests.cs | 2 +- .../CustomFunctions/Sum/SumFilterParseTests.cs | 2 +- .../UnitTests/QueryStringParameters/FilterParseTests.cs | 2 +- .../UnitTests/QueryStringParameters/SortParseTests.cs | 2 +- .../UnitTests/TypeConversion/RuntimeTypeConverterTests.cs | 2 +- test/TestBuildingBlocks/TestBuildingBlocks.csproj | 2 +- 9 files changed, 12 insertions(+), 11 deletions(-) diff --git a/package-versions.props b/package-versions.props index 37c2377bf4..07bbfed960 100644 --- a/package-versions.props +++ b/package-versions.props @@ -7,16 +7,17 @@ 0.13.* - 34.0.* + 35.2.* 4.8.* 6.0.* 2.1.* 6.12.* 2.3.* - 1.4.* + 2.0.* 8.0.* 17.8.* - 2.5.* + 2.6.* + 2.5.* diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/IsUpperCase/IsUpperCaseFilterParseTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/IsUpperCase/IsUpperCaseFilterParseTests.cs index aca362aed3..ff0f8f6e09 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/IsUpperCase/IsUpperCaseFilterParseTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/IsUpperCase/IsUpperCaseFilterParseTests.cs @@ -66,7 +66,7 @@ public void Reader_Read_Fails(string parameterName, string parameterValue, strin [InlineData("filter", "has(posts,isUpperCase(author.userName))", null)] [InlineData("filter", "or(isUpperCase(title),isUpperCase(platformName))", null)] [InlineData("filter[posts]", "isUpperCase(author.userName)", "posts")] - public void Reader_Read_Succeeds(string parameterName, string parameterValue, string scopeExpected) + public void Reader_Read_Succeeds(string parameterName, string parameterValue, string? scopeExpected) { // Act _reader.Read(parameterName, parameterValue); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/StringLength/LengthFilterParseTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/StringLength/LengthFilterParseTests.cs index 78352a6ab7..e9f07521ac 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/StringLength/LengthFilterParseTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/StringLength/LengthFilterParseTests.cs @@ -66,7 +66,7 @@ public void Reader_Read_Fails(string parameterName, string parameterValue, strin [InlineData("filter", "has(posts,lessThan(length(author.userName),'1'))", null)] [InlineData("filter", "or(equals(length(title),'1'),equals(length(platformName),'1'))", null)] [InlineData("filter[posts]", "equals(length(author.userName),'1')", "posts")] - public void Reader_Read_Succeeds(string parameterName, string parameterValue, string scopeExpected) + public void Reader_Read_Succeeds(string parameterName, string parameterValue, string? scopeExpected) { // Act _reader.Read(parameterName, parameterValue); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/StringLength/LengthSortParseTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/StringLength/LengthSortParseTests.cs index c28bec8c8a..e7e16196e4 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/StringLength/LengthSortParseTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/StringLength/LengthSortParseTests.cs @@ -62,7 +62,7 @@ public void Reader_Read_Fails(string parameterName, string parameterValue, strin [InlineData("sort", "length(title),-length(platformName)", null)] [InlineData("sort", "length(owner.userName)", null)] [InlineData("sort[posts]", "length(author.userName)", "posts")] - public void Reader_Read_Succeeds(string parameterName, string parameterValue, string scopeExpected) + public void Reader_Read_Succeeds(string parameterName, string parameterValue, string? scopeExpected) { // Act _reader.Read(parameterName, parameterValue); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/Sum/SumFilterParseTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/Sum/SumFilterParseTests.cs index c2cc62e279..cc4c3fc7e9 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/Sum/SumFilterParseTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/CustomFunctions/Sum/SumFilterParseTests.cs @@ -71,7 +71,7 @@ public void Reader_Read_Fails(string parameterName, string parameterValue, strin [InlineData("filter", "has(posts,greaterThan(sum(comments,numStars),'5'))", null)] [InlineData("filter[posts]", "equals(sum(comments,numStars),'11')", "posts")] [InlineData("filter[posts]", "equals(sum(labels,count(posts)),'8')", "posts")] - public void Reader_Read_Succeeds(string parameterName, string parameterValue, string scopeExpected) + public void Reader_Read_Succeeds(string parameterName, string parameterValue, string? scopeExpected) { // Act _reader.Read(parameterName, parameterValue); diff --git a/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/FilterParseTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/FilterParseTests.cs index 24056cb9a7..9799d7d6ae 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/FilterParseTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/FilterParseTests.cs @@ -202,7 +202,7 @@ public void Reader_Read_ParameterValue_Fails(string parameterName, string parame [InlineData("filter", "isType(owner.person,men,equals(hasBeard,'true'))", null)] [InlineData("filter[posts.contributors]", "isType(,women)", "posts.contributors")] [InlineData("filter[posts.contributors]", "isType(,women,equals(maidenName,'Austen'))", "posts.contributors")] - public void Reader_Read_Succeeds(string parameterName, string parameterValue, string scopeExpected) + public void Reader_Read_Succeeds(string parameterName, string parameterValue, string? scopeExpected) { // Act _reader.Read(parameterName, parameterValue); diff --git a/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/SortParseTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/SortParseTests.cs index a2a013d535..77c52b3828 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/SortParseTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/SortParseTests.cs @@ -152,7 +152,7 @@ public void Reader_Read_ParameterValue_Fails(string parameterName, string parame [InlineData("sort[posts.contributors]", "count(wife.husband.drinkingBuddies)", "posts.contributors")] [InlineData("sort[posts.contributors]", "wife.age", "posts.contributors")] [InlineData("sort[posts.contributors]", "count(father.friends)", "posts.contributors")] - public void Reader_Read_Succeeds(string parameterName, string parameterValue, string scopeExpected) + public void Reader_Read_Succeeds(string parameterName, string parameterValue, string? scopeExpected) { // Act _reader.Read(parameterName, parameterValue); diff --git a/test/JsonApiDotNetCoreTests/UnitTests/TypeConversion/RuntimeTypeConverterTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/TypeConversion/RuntimeTypeConverterTests.cs index 4fbe4f3d22..1263912faf 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/TypeConversion/RuntimeTypeConverterTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/TypeConversion/RuntimeTypeConverterTests.cs @@ -141,7 +141,7 @@ public void Returns_same_instance_for_interface() [InlineData(typeof(IFace), null)] [InlineData(typeof(BaseType), null)] [InlineData(typeof(DerivedType), null)] - public void Returns_default_value_for_empty_string(Type type, object expectedValue) + public void Returns_default_value_for_empty_string(Type type, object? expectedValue) { // Act object? result = RuntimeTypeConverter.ConvertType(string.Empty, type); diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index ae6cf32ff6..40e10eb297 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -19,6 +19,6 @@ - + From b01e4144258fc271e04ebc604b7979ea507a73a1 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 13 Jan 2024 10:14:08 +0100 Subject: [PATCH 26/91] Remove unused PGPASSWORD environment variable --- .github/workflows/build.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 06609b8ccf..1a1b74f44f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,10 +23,6 @@ concurrency: env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: true - # The Windows runner image has PostgreSQL pre-installed and sets the PGPASSWORD environment variable to "root". - # This conflicts with the default password "postgres", which is used by ikalnytskyi/action-setup-postgres. - # Because action-setup-postgres forgets to update the environment variable accordingly, we do so here. - PGPASSWORD: "postgres" jobs: build-and-test: From a89d3f14424277bd2cd382079fcc8f5e8eb01c51 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 13 Jan 2024 10:21:03 +0100 Subject: [PATCH 27/91] Bump action-setup-postgres version --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1a1b74f44f..26028cba5a 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@v4 + uses: ikalnytskyi/action-setup-postgres@v5 with: username: postgres password: postgres From fc5a0b7842253488d62834bd3da1f8094ebd35e6 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 13 Jan 2024 13:40:17 +0100 Subject: [PATCH 28/91] Reduce logging output when running tests in ci-build (#1430) --- .github/workflows/build.yml | 7 +++++++ test/DapperTests/IntegrationTests/DapperTestContext.cs | 4 ++++ test/TestBuildingBlocks/IntegrationTestContext.cs | 8 ++++---- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 26028cba5a..eb3aab63b2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -127,6 +127,13 @@ jobs: run: | dotnet build --no-restore --configuration Release /p:VersionSuffix=$env:PACKAGE_VERSION_SUFFIX - name: Test + env: + # Override log levels, to reduce logging output when running tests in ci-build. + Logging__LogLevel__Microsoft.Hosting.Lifetime: 'None' + Logging__LogLevel__Microsoft.AspNetCore.Hosting.Diagnostics: 'None' + Logging__LogLevel__Microsoft.Extensions.Hosting.Internal.Host: 'None' + Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command: 'None' + Logging__LogLevel__JsonApiDotNetCore: 'None' run: | dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" --logger "GitHubActions;summary.includeSkippedTests=true" - name: Upload coverage to codecov.io diff --git a/test/DapperTests/IntegrationTests/DapperTestContext.cs b/test/DapperTests/IntegrationTests/DapperTestContext.cs index 6bbcca6777..233162e2e8 100644 --- a/test/DapperTests/IntegrationTests/DapperTestContext.cs +++ b/test/DapperTests/IntegrationTests/DapperTestContext.cs @@ -69,6 +69,10 @@ private WebApplicationFactory CreateFactory() { if (_testOutputHelper != null) { +#if !DEBUG + // Reduce logging output when running tests in ci-build. + loggingBuilder.ClearProviders(); +#endif loggingBuilder.Services.AddSingleton(_ => new XUnitLoggerProvider(_testOutputHelper, "DapperExample.")); } }); diff --git a/test/TestBuildingBlocks/IntegrationTestContext.cs b/test/TestBuildingBlocks/IntegrationTestContext.cs index dc186b474b..83f00667b2 100644 --- a/test/TestBuildingBlocks/IntegrationTestContext.cs +++ b/test/TestBuildingBlocks/IntegrationTestContext.cs @@ -83,8 +83,8 @@ private WebApplicationFactory CreateFactory() }); }); - // We have placed an appsettings.json in the TestBuildingBlock project folder and set the content root to there. Note that controllers - // are not discovered in the content root but are registered manually using IntegrationTestContext.UseController. + // We have placed an appsettings.json in the TestBuildingBlocks project directory and set the content root to there. Note that + // controllers are not discovered in the content root, but are registered manually using IntegrationTestContext.UseController. WebApplicationFactory factoryWithConfiguredContentRoot = factory.WithWebHostBuilder(builder => builder.UseSolutionRelativeContentRoot($"test/{nameof(TestBuildingBlocks)}")); @@ -161,8 +161,8 @@ protected override IHostBuilder CreateHostBuilder() .CreateDefaultBuilder(null) .ConfigureAppConfiguration(builder => { - // For tests asserting on log output, we discard the logging settings from appsettings.json. - // But using appsettings.json for all other tests makes it easy to quickly toggle when debugging. + // For tests asserting on log output, we discard the log levels from appsettings.json and environment variables. + // But using appsettings.json for all other tests makes it easy to quickly toggle when debugging tests. if (_loggingConfiguration != null) { builder.Sources.Clear(); From 78c98b171259385f99d2bc9ffe9099c3836b341d Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 13 Jan 2024 14:17:18 +0100 Subject: [PATCH 29/91] Increment version to 5.5.2 (used for pre-release builds from ci) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 0c2b51e13b..534f9bb7af 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -27,6 +27,6 @@ false $(MSBuildThisFileDirectory)CodingGuidelines.ruleset $(MSBuildThisFileDirectory)tests.runsettings - 5.5.1 + 5.5.2 From 167197ae4b9e86b877353de159c05fc19fd59942 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:52:49 +0000 Subject: [PATCH 30/91] Bump actions/dependency-review-action from 3 to 4 (#1442) --- .github/workflows/deps-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 459e27bd0cd3b153390ee89b8835494fcfca38ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 00:19:23 +0000 Subject: [PATCH 31/91] Bump jetbrains.resharper.globaltools from 2023.3.2 to 2023.3.3 (#1443) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 575923d62c..556e9c5598 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": "2023.3.3", "commands": [ "jb" ] From 8d37ce6488c552b5352f3774557945b869b96dfe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 23:52:10 +0000 Subject: [PATCH 32/91] Bump dotnet-reportgenerator-globaltool from 5.2.0 to 5.2.1 (#1453) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 556e9c5598..9658fff117 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "dotnet-reportgenerator-globaltool": { - "version": "5.2.0", + "version": "5.2.1", "commands": [ "reportgenerator" ] From 412af65b275a372c8de935f2db7cf37b0e0f448f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 00:26:58 +0000 Subject: [PATCH 33/91] Bump codecov/codecov-action from 3 to 4 (#1452) --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eb3aab63b2..0e41da591a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -138,7 +138,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 From d9d74f4bb2eda64a6196c3a6424185092e11ed0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire?= Date: Tue, 6 Feb 2024 21:42:12 -0500 Subject: [PATCH 34/91] doc: fix path of path example --- docs/usage/writing/updating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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": { From 06195b84be389223508de5077a5127098cdef0c1 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 7 Feb 2024 10:40:52 +0100 Subject: [PATCH 35/91] Update ROADMAP.md --- ROADMAP.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 270ae294e6..3ffba718e7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,12 +2,14 @@ This document provides an overview of the direction this project is heading and lists what we intend to work on in the near future. -> Disclaimer: This is an open source project. The available time of our contributors varies and therefore we do not plan release dates. This document expresses our current intent, which may change over time. +> Disclaimer: This is an open-source project. The available time of our contributors varies and therefore we do not plan release dates. This document expresses our current intent, which may change over time. -We have interest in the following topics. It's too soon yet to decide whether they'll make it into v5.x or in a later major version. +We have an interest in the following topics. It's too soon yet to decide whether they'll make it into v5.x or in a later major version. +- OpenAPI (Swagger): Generate documentation and typed clients [#1046](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1046) +- Query strings on JSON-mapped columns [#1439](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1439) +- Improved SQL Server support [#1118](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1118) - Optimistic concurrency [#1119](https://github.com/json-api-dotnet/JsonApiDotNetCore/pull/1119) -- OpenAPI (Swagger) [#1046](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1046) - Fluent API [#776](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/776) - Idempotency [#1132](https://github.com/json-api-dotnet/JsonApiDotNetCore/pull/1132) From 587d972e208f40650da7a5723b067e1c839cc40b Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 7 Feb 2024 10:43:41 +0100 Subject: [PATCH 36/91] Add sponsor link --- ROADMAP.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ROADMAP.md b/ROADMAP.md index 3ffba718e7..7ba6a2a5a8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -21,3 +21,5 @@ Please give us feedback that will give us insight on the following points: * Existing features that are missing some capability or otherwise don't work well enough. * Missing features that should be added to the product. * Design choices for a feature that is currently in-progress. + +Please consider to [sponsor the project](https://github.com/sponsors/json-api-dotnet). From 3f62b77c5e7ebf757d80d01ce0a3d455b51ad9e3 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 18 Feb 2024 02:45:56 +0100 Subject: [PATCH 37/91] Replaces CreateTupleAccessExpressionForConstant with CloseOver, which is simpler and requires less reflection. --- .../QueryableBuilding/SelectClauseBuilder.cs | 4 +-- .../SkipTakeClauseBuilder.cs | 2 +- .../QueryableBuilding/WhereClauseBuilder.cs | 3 +- .../Queries/SystemExpressionBuilder.cs | 34 +++++++++++++++++++ .../Queries/SystemExpressionExtensions.cs | 33 ------------------ .../Resources/ResourceFactory.cs | 2 +- 6 files changed, 39 insertions(+), 39 deletions(-) create mode 100644 src/JsonApiDotNetCore/Queries/SystemExpressionBuilder.cs delete mode 100644 src/JsonApiDotNetCore/Queries/SystemExpressionExtensions.cs diff --git a/src/JsonApiDotNetCore/Queries/QueryableBuilding/SelectClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/QueryableBuilding/SelectClauseBuilder.cs index 858532d7c2..d1113946ad 100644 --- a/src/JsonApiDotNetCore/Queries/QueryableBuilding/SelectClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/QueryableBuilding/SelectClauseBuilder.cs @@ -93,9 +93,9 @@ private Expression CreateLambdaBodyInitializerForTypeHierarchy(FieldSelection se private static BinaryExpression CreateRuntimeTypeCheck(LambdaScope lambdaScope, Type concreteClrType) { // Emitting "resource.GetType() == typeof(Article)" instead of "resource is Article" so we don't need to check for most-derived - // types first. This way, we can fallback to "anything else" at the end without worrying about order. + // types first. This way, we can fall back to "anything else" at the end without worrying about order. - Expression concreteTypeConstant = concreteClrType.CreateTupleAccessExpressionForConstant(typeof(Type)); + Expression concreteTypeConstant = SystemExpressionBuilder.CloseOver(concreteClrType); MethodCallExpression getTypeCall = Expression.Call(lambdaScope.Accessor, TypeGetTypeMethod); return Expression.MakeBinary(ExpressionType.Equal, getTypeCall, concreteTypeConstant, false, TypeOpEqualityMethod); diff --git a/src/JsonApiDotNetCore/Queries/QueryableBuilding/SkipTakeClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/QueryableBuilding/SkipTakeClauseBuilder.cs index b48fa696db..4c54586cb6 100644 --- a/src/JsonApiDotNetCore/Queries/QueryableBuilding/SkipTakeClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/QueryableBuilding/SkipTakeClauseBuilder.cs @@ -36,7 +36,7 @@ public override Expression VisitPagination(PaginationExpression expression, Quer private static Expression ExtensionMethodCall(Expression source, string operationName, int value, QueryClauseBuilderContext context) { - Expression constant = value.CreateTupleAccessExpressionForConstant(typeof(int)); + Expression constant = SystemExpressionBuilder.CloseOver(value); return Expression.Call(context.ExtensionType, operationName, [context.LambdaScope.Parameter.Type], source, constant); } diff --git a/src/JsonApiDotNetCore/Queries/QueryableBuilding/WhereClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/QueryableBuilding/WhereClauseBuilder.cs index f14d795f7b..b5fafdd21b 100644 --- a/src/JsonApiDotNetCore/Queries/QueryableBuilding/WhereClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/QueryableBuilding/WhereClauseBuilder.cs @@ -245,7 +245,6 @@ public override Expression VisitNullConstant(NullConstantExpression expression, public override Expression VisitLiteralConstant(LiteralConstantExpression expression, QueryClauseBuilderContext context) { - Type type = expression.TypedValue.GetType(); - return expression.TypedValue.CreateTupleAccessExpressionForConstant(type); + return SystemExpressionBuilder.CloseOver(expression.TypedValue); } } diff --git a/src/JsonApiDotNetCore/Queries/SystemExpressionBuilder.cs b/src/JsonApiDotNetCore/Queries/SystemExpressionBuilder.cs new file mode 100644 index 0000000000..f625ee9b4f --- /dev/null +++ b/src/JsonApiDotNetCore/Queries/SystemExpressionBuilder.cs @@ -0,0 +1,34 @@ +using System.Linq.Expressions; +using System.Reflection; + +#pragma warning disable AV1008 + +namespace JsonApiDotNetCore.Queries; + +internal static class SystemExpressionBuilder +{ + private static readonly MethodInfo CloseOverOpenMethod = + typeof(SystemExpressionBuilder).GetMethods().Single(method => method is { Name: nameof(CloseOver), IsGenericMethod: true }); + + // To enable efficient query plan caching, inline constants (that vary per request) should be converted into query parameters. + // https://stackoverflow.com/questions/54075758/building-a-parameterized-entityframework-core-expression + // + // CloseOver can be used to change a query like: + // SELECT ... FROM ... WHERE x."Age" = 3 + // into: + // SELECT ... FROM ... WHERE x."Age" = @p0 + + public static Expression CloseOver(object value) + { + ArgumentGuard.NotNull(value); + + MethodInfo closeOverClosedMethod = CloseOverOpenMethod.MakeGenericMethod(value.GetType()); + return (Expression)closeOverClosedMethod.Invoke(null, [value])!; + } + + public static Expression CloseOver(T value) + { + // From https://github.com/dotnet/efcore/issues/28151#issuecomment-1374480257. + return ((Expression>)(() => value)).Body; + } +} diff --git a/src/JsonApiDotNetCore/Queries/SystemExpressionExtensions.cs b/src/JsonApiDotNetCore/Queries/SystemExpressionExtensions.cs deleted file mode 100644 index ef81aece33..0000000000 --- a/src/JsonApiDotNetCore/Queries/SystemExpressionExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Linq.Expressions; -using System.Reflection; - -namespace JsonApiDotNetCore.Queries; - -internal static class SystemExpressionExtensions -{ - public static Expression CreateTupleAccessExpressionForConstant(this object? value, Type type) - { - // To enable efficient query plan caching, inline constants (that vary per request) should be converted into query parameters. - // https://stackoverflow.com/questions/54075758/building-a-parameterized-entityframework-core-expression - - // This method can be used to change a query like: - // SELECT ... FROM ... WHERE x."Age" = 3 - // into: - // SELECT ... FROM ... WHERE x."Age" = @p0 - - // The code below builds the next expression for a type T that is unknown at compile time: - // Expression.Property(Expression.Constant(Tuple.Create(value)), "Item1") - // Which represents the next C# code: - // Tuple.Create(value).Item1; - - MethodInfo tupleCreateUnboundMethod = typeof(Tuple).GetMethods() - .Single(method => method is { Name: "Create", IsGenericMethod: true } && method.GetGenericArguments().Length == 1); - - MethodInfo tupleCreateClosedMethod = tupleCreateUnboundMethod.MakeGenericMethod(type); - - ConstantExpression constantExpression = Expression.Constant(value, type); - - MethodCallExpression tupleCreateCall = Expression.Call(tupleCreateClosedMethod, constantExpression); - return Expression.Property(tupleCreateCall, "Item1"); - } -} diff --git a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs index 9d282b4938..f8af9dfa2f 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs @@ -102,7 +102,7 @@ public NewExpression CreateNewExpression(Type resourceClrType) { object constructorArgument = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, constructorParameter.ParameterType); - Expression argumentExpression = constructorArgument.CreateTupleAccessExpressionForConstant(constructorArgument.GetType()); + Expression argumentExpression = SystemExpressionBuilder.CloseOver(constructorArgument); constructorArguments.Add(argumentExpression); } #pragma warning disable AV1210 // Catch a specific exception instead of Exception, SystemException or ApplicationException From d1707ef55642f258249a4dd9e2054f23e5acf6c9 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 21 Feb 2024 07:10:13 +0100 Subject: [PATCH 38/91] Resharper: "Local function can be made static" is at warning level in cibuild, but was turned off in IDE (#1473) --- JsonApiDotNetCore.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings index 6e4e064588..4f68cc519a 100644 --- a/JsonApiDotNetCore.sln.DotSettings +++ b/JsonApiDotNetCore.sln.DotSettings @@ -59,6 +59,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$); SUGGESTION HINT WARNING + SUGGESTION DO_NOT_SHOW HINT SUGGESTION From 4362c5400d2d60161fc7cd476a5be11c634896c0 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 22 Feb 2024 01:11:01 +0100 Subject: [PATCH 39/91] Fix spelling error in comment --- test/TestBuildingBlocks/FakerContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/TestBuildingBlocks/FakerContainer.cs b/test/TestBuildingBlocks/FakerContainer.cs index 72f9a05567..c29700c968 100644 --- a/test/TestBuildingBlocks/FakerContainer.cs +++ b/test/TestBuildingBlocks/FakerContainer.cs @@ -34,7 +34,7 @@ private static MethodBase GetTestMethod() if (testMethod == null) { // If called after the first await statement, the test method is no longer on the stack, - // but has been replaced with the compiler-generated async/wait state machine. + // but has been replaced with the compiler-generated async/await state machine. throw new InvalidOperationException("Fakers can only be used from within (the start of) a test method."); } From 330459c2d285cbfb2adbb5ba684ab9f583d8ea07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire?= Date: Sun, 25 Feb 2024 18:33:35 -0500 Subject: [PATCH 40/91] doc: fix POST status code (#1478) --- docs/usage/writing/creating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage/writing/creating.md b/docs/usage/writing/creating.md index ba0a21d52b..7e8a3243af 100644 --- a/docs/usage/writing/creating.md +++ b/docs/usage/writing/creating.md @@ -17,7 +17,7 @@ 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. +Otherwise, the server returns `201 Created`, along with the updated resource and its newly assigned ID. In both cases, a `Location` header is returned that contains the URL to the new resource. From fe957673dd9a60c881674accdee6219cf775cc55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire?= Date: Mon, 26 Feb 2024 04:20:43 -0500 Subject: [PATCH 41/91] doc: clarify wording around 204 for POST (#1481) * doc: clarify wording around 204 for POST * Update creating.md --- docs/usage/writing/creating.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/usage/writing/creating.md b/docs/usage/writing/creating.md index 7e8a3243af..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 `201 Created`, 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. From 442718726beaa41297a1cead397d558dddada480 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:23:24 +0000 Subject: [PATCH 42/91] Bump dotnet-reportgenerator-globaltool from 5.2.1 to 5.2.2 (#1482) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 9658fff117..240023b45a 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "dotnet-reportgenerator-globaltool": { - "version": "5.2.1", + "version": "5.2.2", "commands": [ "reportgenerator" ] From 7a64b9d9a67689f3fa534cf7df3166f0a25b9277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire?= Date: Tue, 27 Feb 2024 19:59:18 -0500 Subject: [PATCH 43/91] Don't crash on empty query string parameter name (#1484) --- .../QueryStrings/QueryStringReader.cs | 5 +++++ .../QueryStrings/QueryStringTests.cs | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/JsonApiDotNetCore/QueryStrings/QueryStringReader.cs b/src/JsonApiDotNetCore/QueryStrings/QueryStringReader.cs index c412d03c94..e3c944a045 100644 --- a/src/JsonApiDotNetCore/QueryStrings/QueryStringReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/QueryStringReader.cs @@ -38,6 +38,11 @@ public void ReadAll(DisableQueryStringAttribute? disableQueryStringAttribute) foreach ((string parameterName, StringValues parameterValue) in _queryStringAccessor.Query) { + if (parameterName.Length == 0) + { + continue; + } + IQueryStringParameterReader? reader = _parameterReaders.FirstOrDefault(nextReader => nextReader.CanRead(parameterName)); if (reader != null) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringTests.cs index 0aa955a219..4f6ee95ad2 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringTests.cs @@ -63,6 +63,24 @@ public async Task Can_use_unknown_query_string_parameter() httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); } + [Theory] + [InlineData("")] + [InlineData("bar")] + public async Task Can_use_empty_query_string_parameter_name(string parameterValue) + { + // Arrange + var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService(); + options.AllowUnknownQueryStringParameters = false; + + string route = $"calendars?={parameterValue}"; + + // Act + (HttpResponseMessage httpResponse, Document _) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + } + [Theory] [InlineData("filter")] [InlineData("sort")] From 72bd6f81167efb2f2ebe9edc1f3c390b4f9824a9 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 2 Mar 2024 03:59:04 +0100 Subject: [PATCH 44/91] Document PR workflow (#1487) --- .github/CONTRIBUTING.md | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) 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 From 8a7fcf1fe1e2c12ba56c59614f2cf019c451b4ab Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 3 Mar 2024 13:43:12 +0100 Subject: [PATCH 45/91] Add missing documentation for type parameters --- .../Queries/Expressions/QueryExpressionVisitor.cs | 6 ++++++ src/JsonApiDotNetCore/Repositories/DbContextResolver.cs | 3 +++ .../Repositories/EntityFrameworkCoreRepository.cs | 6 ++++++ src/JsonApiDotNetCore/Resources/IResourceChangeTracker.cs | 3 +++ .../Resources/QueryStringParameterHandlers.cs | 3 +++ .../Serialization/Objects/SingleOrManyData.cs | 3 +++ 6 files changed, 24 insertions(+) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs index 7dcf44b1f4..12242c29ff 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs @@ -5,6 +5,12 @@ namespace JsonApiDotNetCore.Queries.Expressions; /// /// Implements the visitor design pattern that enables traversing a tree. /// +/// +/// The type to use for passing custom state between visit methods. +/// +/// +/// The type that is returned from visit methods. +/// [PublicAPI] public abstract class QueryExpressionVisitor { diff --git a/src/JsonApiDotNetCore/Repositories/DbContextResolver.cs b/src/JsonApiDotNetCore/Repositories/DbContextResolver.cs index 794eecc6f2..bc275a96c1 100644 --- a/src/JsonApiDotNetCore/Repositories/DbContextResolver.cs +++ b/src/JsonApiDotNetCore/Repositories/DbContextResolver.cs @@ -4,6 +4,9 @@ namespace JsonApiDotNetCore.Repositories; /// +/// +/// The type of the to resolve. +/// [PublicAPI] public sealed class DbContextResolver : IDbContextResolver where TDbContext : DbContext diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index 4c3314ddab..a5f083932b 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -21,6 +21,12 @@ namespace JsonApiDotNetCore.Repositories; /// /// Implements the foundational Repository layer in the JsonApiDotNetCore architecture that uses Entity Framework Core. /// +/// +/// The resource type. +/// +/// +/// The resource identifier type. +/// [PublicAPI] public class EntityFrameworkCoreRepository : IResourceRepository, IRepositorySupportsTransaction where TResource : class, IIdentifiable diff --git a/src/JsonApiDotNetCore/Resources/IResourceChangeTracker.cs b/src/JsonApiDotNetCore/Resources/IResourceChangeTracker.cs index 1f69735f92..13404f2759 100644 --- a/src/JsonApiDotNetCore/Resources/IResourceChangeTracker.cs +++ b/src/JsonApiDotNetCore/Resources/IResourceChangeTracker.cs @@ -3,6 +3,9 @@ namespace JsonApiDotNetCore.Resources; /// /// Used to determine whether additional changes to a resource (side effects), not specified in a POST or PATCH request, have been applied. /// +/// +/// The resource type. +/// public interface IResourceChangeTracker where TResource : class, IIdentifiable { diff --git a/src/JsonApiDotNetCore/Resources/QueryStringParameterHandlers.cs b/src/JsonApiDotNetCore/Resources/QueryStringParameterHandlers.cs index b48c46e26e..7fe8970b53 100644 --- a/src/JsonApiDotNetCore/Resources/QueryStringParameterHandlers.cs +++ b/src/JsonApiDotNetCore/Resources/QueryStringParameterHandlers.cs @@ -6,4 +6,7 @@ namespace JsonApiDotNetCore.Resources; /// This is an alias type intended to simplify the implementation's method signature. See /// for usage details. /// +/// +/// The resource type. +/// public sealed class QueryStringParameterHandlers : Dictionary, StringValues, IQueryable>>; diff --git a/src/JsonApiDotNetCore/Serialization/Objects/SingleOrManyData.cs b/src/JsonApiDotNetCore/Serialization/Objects/SingleOrManyData.cs index 1126f84f26..99884d61e6 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/SingleOrManyData.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/SingleOrManyData.cs @@ -9,6 +9,9 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// Represents the value of the "data" element, which is either null, a single object or an array of objects. Add /// to to properly roundtrip. /// +/// +/// The type of elements being wrapped, typically or . +/// [PublicAPI] public readonly struct SingleOrManyData // The "new()" constraint exists for parity with SingleOrManyDataConverterFactory, which creates empty instances From a3c92c71108d8a56f023bc415d08f65a44e718c4 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Mon, 4 Mar 2024 00:25:53 +0100 Subject: [PATCH 46/91] Fixes in landing page --- docs/home/index.html | 56 ++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/docs/home/index.html b/docs/home/index.html index 2c3e57849b..582eb7f619 100644 --- a/docs/home/index.html +++ b/docs/home/index.html @@ -9,7 +9,7 @@ - + @@ -22,7 +22,10 @@

JsonApiDotNetCore

-

A framework for building JSON:API compliant REST APIs using .NET Core and Entity Framework Core. Includes support for Atomic Operations.

+

+ A framework for building JSON:API compliant REST APIs using .NET Core and Entity Framework Core. + Includes support for Atomic Operations. +

Read more Getting started Contribute on GitHub @@ -43,12 +46,13 @@

A framework for building JSON

Objectives

- 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.

- +

Eliminate boilerplate

We strive to eliminate as much boilerplate as possible by offering out-of-the-box features such as sorting, filtering and pagination.

@@ -69,28 +73,28 @@

Features

The following features are supported, from HTTP all the way down to the database

-
+

Filtering

Perform compound filtering using the filter query string parameter

-
+
-
+

Sorting

Order resources on one or multiple attributes using the sort query string parameter

-

() - .UseSeed(GetFakerSeed()) + .MakeDeterministic() .RuleFor(table => table.LegCount, faker => faker.Random.Int(1, 4))); private readonly Lazy> _lazyChairFaker = new(() => new Faker() - .UseSeed(GetFakerSeed()) + .MakeDeterministic() .RuleFor(chair => chair.LegCount, faker => faker.Random.Int(2, 4))); private readonly Lazy> _lazySofaFaker = new(() => new Faker() - .UseSeed(GetFakerSeed()) + .MakeDeterministic() .RuleFor(sofa => sofa.SeatCount, faker => faker.Random.Int(2, 6))); private readonly Lazy> _lazyPillowFaker = new(() => new Faker() - .UseSeed(GetFakerSeed()) + .MakeDeterministic() .RuleFor(pillow => pillow.Color, faker => faker.Internet.Color())); private readonly Lazy> _lazyBedFaker = new(() => new Faker() - .UseSeed(GetFakerSeed()) + .MakeDeterministic() .RuleFor(bed => bed.IsDouble, faker => faker.Random.Bool())); public Faker Room => _lazyRoomFaker.Value; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Serialization/SerializationFakers.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Serialization/SerializationFakers.cs index ca3b246f3f..ab9e20ade3 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Serialization/SerializationFakers.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Serialization/SerializationFakers.cs @@ -6,7 +6,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Serialization; -internal sealed class SerializationFakers : FakerContainer +internal sealed class SerializationFakers { private static readonly TimeSpan[] MeetingDurations = [ @@ -17,7 +17,7 @@ internal sealed class SerializationFakers : FakerContainer ]; private readonly Lazy> _lazyMeetingFaker = new(() => new Faker() - .UseSeed(GetFakerSeed()) + .MakeDeterministic() .RuleFor(meeting => meeting.Title, faker => faker.Lorem.Word()) .RuleFor(meeting => meeting.StartTime, faker => faker.Date.FutureOffset().TruncateToWholeMilliseconds()) .RuleFor(meeting => meeting.Duration, faker => faker.PickRandom(MeetingDurations)) @@ -25,7 +25,7 @@ internal sealed class SerializationFakers : FakerContainer .RuleFor(meeting => meeting.Longitude, faker => faker.Address.Longitude())); private readonly Lazy> _lazyMeetingAttendeeFaker = new(() => new Faker() - .UseSeed(GetFakerSeed()) + .MakeDeterministic() .RuleFor(attendee => attendee.DisplayName, faker => faker.Random.Utf16String()) .RuleFor(attendee => attendee.HomeAddress, faker => new Address { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionFakers.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionFakers.cs index 4921f57959..1cbf435c45 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionFakers.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionFakers.cs @@ -6,14 +6,14 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.SoftDeletion; -internal sealed class SoftDeletionFakers : FakerContainer +internal sealed class SoftDeletionFakers { private readonly Lazy> _lazyCompanyFaker = new(() => new Faker() - .UseSeed(GetFakerSeed()) + .MakeDeterministic() .RuleFor(company => company.Name, faker => faker.Company.CompanyName())); private readonly Lazy> _lazyDepartmentFaker = new(() => new Faker() - .UseSeed(GetFakerSeed()) + .MakeDeterministic() .RuleFor(department => department.Name, faker => faker.Commerce.Department())); public Faker Company => _lazyCompanyFaker.Value; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyFakers.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyFakers.cs index 3326a36173..9a41aad9da 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyFakers.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyFakers.cs @@ -6,19 +6,19 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ZeroKeys; -internal sealed class ZeroKeyFakers : FakerContainer +internal sealed class ZeroKeyFakers { private readonly Lazy> _lazyGameFaker = new(() => new Faker() - .UseSeed(GetFakerSeed()) + .MakeDeterministic() .RuleFor(game => game.Title, faker => faker.Random.Words())); private readonly Lazy> _lazyPlayerFaker = new(() => new Faker() - .UseSeed(GetFakerSeed()) + .MakeDeterministic() .RuleFor(player => player.Id, faker => faker.Person.UserName) .RuleFor(player => player.EmailAddress, faker => faker.Person.Email)); private readonly Lazy> _lazyMapFaker = new(() => new Faker() - .UseSeed(GetFakerSeed()) + .MakeDeterministic() .RuleFor(map => map.Id, faker => faker.Random.Guid()) .RuleFor(map => map.Name, faker => faker.Random.Words())); diff --git a/test/TestBuildingBlocks/FakerContainer.cs b/test/TestBuildingBlocks/FakerExtensions.cs similarity index 86% rename from test/TestBuildingBlocks/FakerContainer.cs rename to test/TestBuildingBlocks/FakerExtensions.cs index c29700c968..282aa26c6b 100644 --- a/test/TestBuildingBlocks/FakerContainer.cs +++ b/test/TestBuildingBlocks/FakerExtensions.cs @@ -1,21 +1,26 @@ using System.Diagnostics; using System.Reflection; -using Bogus.DataSets; -using FluentAssertions.Extensions; +using Bogus; using Xunit; namespace TestBuildingBlocks; -public abstract class FakerContainer +public static class FakerExtensions { - static FakerContainer() + public static Faker MakeDeterministic(this Faker faker) + where T : class { + int seed = GetFakerSeed(); + faker.UseSeed(seed); + // Setting the system DateTime to kind Utc, so that faker calls like PastOffset() don't depend on the system time zone. // See https://docs.microsoft.com/en-us/dotnet/api/system.datetimeoffset.op_implicit?view=net-6.0#remarks - Date.SystemClock = () => 1.January(2020).At(1, 1, 1).AsUtc(); + faker.UseDateTimeReference(FrozenSystemClock.DefaultDateTimeUtc); + + return faker; } - protected static int GetFakerSeed() + private static int GetFakerSeed() { // The goal here is to have stable data over multiple test runs, but at the same time different data per test case. diff --git a/test/TestBuildingBlocks/FrozenSystemClock.cs b/test/TestBuildingBlocks/FrozenSystemClock.cs index 6ffe8feaaf..031349086d 100644 --- a/test/TestBuildingBlocks/FrozenSystemClock.cs +++ b/test/TestBuildingBlocks/FrozenSystemClock.cs @@ -4,7 +4,8 @@ namespace TestBuildingBlocks; public sealed class FrozenSystemClock : ISystemClock { - private static readonly DateTimeOffset DefaultTime = 1.January(2020).At(1, 1, 1).AsUtc(); + internal static readonly DateTime DefaultDateTimeUtc = 1.January(2020).At(1, 1, 1).AsUtc(); + public static readonly DateTimeOffset DefaultDateTimeOffsetUtc = DefaultDateTimeUtc; - public DateTimeOffset UtcNow { get; set; } = DefaultTime; + public DateTimeOffset UtcNow { get; set; } = DefaultDateTimeOffsetUtc; } From 4ecd587e122556cbae55ca736838e9b1bd9d1714 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 5 May 2024 11:52:30 +0200 Subject: [PATCH 70/91] Update Microsoft.NET.Test.Sdk to v17.9.* --- package-versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-versions.props b/package-versions.props index c809c7e2c9..1a92993eda 100644 --- a/package-versions.props +++ b/package-versions.props @@ -15,7 +15,7 @@ 2.3.* 2.0.* 8.0.* - 17.8.* + 17.9.* 2.6.* 2.5.* From 92009324f9bc386f8eb6cbd13120f036f078c80a Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 5 May 2024 11:53:36 +0200 Subject: [PATCH 71/91] Update xunit to v2.8.* --- package-versions.props | 3 +-- test/TestBuildingBlocks/TestBuildingBlocks.csproj | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package-versions.props b/package-versions.props index 1a92993eda..894fd70d25 100644 --- a/package-versions.props +++ b/package-versions.props @@ -16,8 +16,7 @@ 2.0.* 8.0.* 17.9.* - 2.6.* - 2.5.* + 2.8.* diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index 40e10eb297..ae6cf32ff6 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -19,6 +19,6 @@ - + From 17cb827c69677ffc23818e1b440e7906a0ce52cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 May 2024 13:39:45 +0000 Subject: [PATCH 72/91] Bump jetbrains.resharper.globaltools from 2024.1.1 to 2024.1.2 (#1542) --- .config/dotnet-tools.json | 2 +- src/JsonApiDotNetCore/CollectionExtensions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 7f8d6bab15..5c99098b34 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2024.1.1", + "version": "2024.1.2", "commands": [ "jb" ] diff --git a/src/JsonApiDotNetCore/CollectionExtensions.cs b/src/JsonApiDotNetCore/CollectionExtensions.cs index f413898269..4e02b60fc5 100644 --- a/src/JsonApiDotNetCore/CollectionExtensions.cs +++ b/src/JsonApiDotNetCore/CollectionExtensions.cs @@ -82,7 +82,7 @@ public static bool DictionaryEqual(this IReadOnlyDictionary EmptyIfNull(this IEnumerable? source) { - return source ?? Enumerable.Empty(); + return source ?? []; } public static IEnumerable WhereNotNull(this IEnumerable source) From 7ace826fe1c10ea606b3a7b8e73304a5bf9ef899 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 May 2024 02:56:53 +0000 Subject: [PATCH 73/91] Bump dotnet-reportgenerator-globaltool from 5.2.5 to 5.3.0 (#1545) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 5c99098b34..2e7b1d6d91 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "dotnet-reportgenerator-globaltool": { - "version": "5.2.5", + "version": "5.3.0", "commands": [ "reportgenerator" ] From b0a5f45882c2362627a87e0a0ed4f60eacc885f6 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Mon, 20 May 2024 22:30:39 +0200 Subject: [PATCH 74/91] Update logo --- README.md | 4 +- docs/home/assets/img/apple-touch-icon.png | Bin 1738 -> 3917 bytes docs/home/assets/img/favicon.png | Bin 491 -> 915 bytes docs/home/assets/img/logo.png | Bin 66494 -> 0 bytes docs/home/assets/img/logo.svg | 163 ++++++++++++++++++ docs/home/index.html | 2 +- logo.png | Bin 16356 -> 0 bytes package-icon.png | Bin 0 -> 2905 bytes .../JsonApiDotNetCore.Annotations.csproj | 4 +- .../JsonApiDotNetCore.SourceGenerators.csproj | 4 +- .../JsonApiDotNetCore.csproj | 4 +- 11 files changed, 171 insertions(+), 10 deletions(-) delete mode 100644 docs/home/assets/img/logo.png create mode 100644 docs/home/assets/img/logo.svg delete mode 100644 logo.png create mode 100644 package-icon.png diff --git a/README.md b/README.md index f34dfd1bee..5ae9184e0c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -

- -

+ # JsonApiDotNetCore A framework for building [JSON:API](http://jsonapi.org/) compliant REST APIs using .NET Core and Entity Framework Core. Includes support for [Atomic Operations](https://jsonapi.org/ext/atomic/). diff --git a/docs/home/assets/img/apple-touch-icon.png b/docs/home/assets/img/apple-touch-icon.png index 447cec2c47da7a30359729a36d3c5096dbde5a15..cc7166ba70b29e59ae1b9ebf6e2706863ed251ec 100644 GIT binary patch literal 3917 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 literal 1738 zcmcIj`9Bkk10F)i`$qM?mDu}zk0v?yPNJ{xmZL>1k;(Zs=0-Pq=HJ@Of#2JNtQTaE{F!?`-~dFAZK=x=kmX>)TrVJ|D5#Lk~cYxP4wQ6tcfM= znp7$c^~sEXNDgpGrsX%6l3U#@BE%x`+!Rm1|4MqnNRKONuVdeWhA+&naC*j3wsGq# zg4v0s+OqeQm$mmCk}?y^JglOgKYc9}ieiHDQ^KhuA7^t@s%vOnX)!dfE72mMc>L3R zPIA@c$bxWB^dhRbys+($n>haZ=5O$r#krLS?gZlV%E@7_yJe&+Ec`_@ZDxE4?TB@O zhTXA?e|S4R8&~0jNZ1l=OC{3PWj;Qf>S-Ot>KMvNshS#Hpyf4(+|3Y+C50KaJ?%sE zH=R^sLpHARbyoe>=1!1%DsN#u&=rpjebxJJxT>fl_D|B@u8=$Pxuc$)5nnnwIM>xY zP?%ZAVxu4AjStQDcaOH!_II}oP7HI0*wbG&wz``KsKmyB-pMR%#Zz?F?#^CZ zFzNoSl$5Zdn&M9W`qupP^60?q?W?imG=@Ji*$0sr8hjGHnZdSBRHVak z>$aHSpeMW*R;@^XzmIR+|82WA!cme2Nun6?&8505oFhUT_uqMg;`&s}?D{#AfMJ$V z*svfCmBS_Zp4lC0e1C)0Ycgd2VGP+^1^jcYa+4X#3>s!Cihf!0ytJVSo|(yYK3mdg z6bMEULydI7-lsw?^gK@mt^NG#>kN4Ak>wDFui(OR0M*Aqbufsou6%*a`6TqMYgQdsemu=k=R7W#FF+ZnsVXL+;h+Ih z?({@@9gaq9NGSTgj5!WL10N+klQhrwh)ToO>P#_r0rUo3-V3qZS)DbCYY3Wi%>8^nTKKT;Sqpyspe)%7$(--Odb=s zM{y!&b^&w0X*we1?XX!~{Ps+QpKVW1JTk=_kBIite@b`Kq6;V^6cv&SwU&Y_AUk`s zYf37PW0bI_^}T9adj!+^30cgEmn9tMPsTrl4(XKf>V@%L6==2~JAIkm*J<*#Qr=}p z|JnqJT4O(P#%Q$I)5h}2ZVxf}iS2|1%;gFGSOlch_~#p1r?nXYnF$I7o`(uJ)aNfQMg1osur-J^A8+TUTE;Q;w z-w}I3mW(eW#nkPWnn*~WW~l`%`wkv?f?chR!sjXY=9#w#b3ftLe`=3~B!n1)r3>JMSw zc!s<$-W&rglw0Rnvcn(1H(~|_1XD95ItXhb+*CuyG;$U>fYK;q;l-1r8l5g)AX2YZ z$FG?;9M@M{HFF{bT|`YAsK!na6Uh5|LNP}#%)iavGZv@u8=%`(3uDQ&Bsw!KHzFM7 z*qO2k`I*pjzGz(jN;-tzZ7_!fWiNH=z0o!Jbl#QM+IJk}igWS$zM};@_T8oY2~qM? ze3g~ry14nfx?M5;La?>yI(v3;XZ4Y2+QpI1{CC3pu9KRF53UXX?6nM`V%6{HjgG6I z)H=7M-lrcbP>+T(C18__8cE<)ZNGGV@~MV=F!Hnt4XmQ!&M<1#ptXS?oe}jK>8M^C mF`5n_&O_6``MeIbF}Q>=>>tutfc~iSPqvLU!m7;TcJ$w~_MH6y diff --git a/docs/home/assets/img/favicon.png b/docs/home/assets/img/favicon.png index d752fd5d71545548fb0baf3f3a1b500e13e520b9..de5ad580400ce5985c0b21f5bd853bac72bbf081 100644 GIT binary patch delta 904 zcmaFOJehrhWIZzj1B1(wu46!ov%n*=n1O-sFbFdq&tH+kz`&>$;1l8sl#EuH6sS*Hr2qf_ z7r4*Qb(+&sG-LLx*=12vt5c@sxzDXH@|``UcUE!Olti;hIi6FJttOQPFRDwP-IOz{ zz+-;C+q_4&-lSSipI)*v*Le=mh-;@Fm-{bnO=~2LZG>2{)=1kW>-ecdHwWb zuFITh{d2nOW;Ex_sY;yIUOc-YYkF7ZoHx%ul?N<&`}}ib%&MBOWzBJ`+f&vp={#6p zAGKoVyi*HX_n+8t>(+&5K;x={m+oJ4<-xUAeL0&-!lzCv-3Ama4_sX6H7CVpGLYSn zJ{w3*E8UgrGPlTkPO;zYe78A3k_SC3h8~g^r^-9M;}$AE&fluz!4bEFiA#*G3c8X&k|0>)e}xW zvpDh8IH15tm4O*Wc*VS&WyS^~8 zUi?##u-zSgHeQn|i4P|~`!`Ixy7KeMT3+{S)|X8z4ffZsv3quhzy6Q4(*F>XM|ZnF z2FyOU)%9UoE$_WQl~#t8=JD}7bC>^$D*RRTy;{~e`$d(rahcYiukyuL0+W^Q{F(?% PA`G6celF{r5*V8RYSzne delta 476 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

3E;}e4^3N4e5mxzQMx9k3?#2xn5 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 LDZMY3Fbw=ZL7qLa diff --git a/docs/home/assets/img/logo.svg b/docs/home/assets/img/logo.svg new file mode 100644 index 0000000000..c7339d2031 --- /dev/null +++ b/docs/home/assets/img/logo.svg @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + JsonApiDotNetCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/home/index.html b/docs/home/index.html index 582eb7f619..77f4fdb49f 100644 --- a/docs/home/index.html +++ b/docs/home/index.html @@ -31,7 +31,7 @@

Contribute on GitHub
- project logo + project logo
diff --git a/logo.png b/logo.png deleted file mode 100644 index 78f1acd5214eb049971fcd5f81dc6222f7904d20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16356 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 VPtP;)V=^B~R#Hi#R?H;m{{Y9ir3nB4 diff --git a/package-icon.png b/package-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f95eb770e8420f214e310a782e175fc6d793a3f5 GIT binary patch literal 2905 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 literal 0 HcmV?d00001 diff --git a/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj b/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj index 1b93c24975..04238621da 100644 --- a/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj +++ b/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj @@ -17,7 +17,7 @@ MIT false See https://github.com/json-api-dotnet/JsonApiDotNetCore/releases. - logo.png + package-icon.png PackageReadme.md true true @@ -25,7 +25,7 @@ - + diff --git a/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj b/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj index f784ada6f9..5e1c03f6b7 100644 --- a/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj +++ b/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj @@ -19,13 +19,13 @@ MIT false See https://github.com/json-api-dotnet/JsonApiDotNetCore/releases. - logo.png + package-icon.png PackageReadme.md https://github.com/json-api-dotnet/JsonApiDotNetCore - + diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index 1757b54a82..0f395511a7 100644 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -16,7 +16,7 @@ MIT false See https://github.com/json-api-dotnet/JsonApiDotNetCore/releases. - logo.png + package-icon.png PackageReadme.md true true @@ -24,7 +24,7 @@ - + From dd61f78e97c1f793e558f6ef9d2ea33d0c9c6d8d Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Mon, 20 May 2024 23:28:08 +0200 Subject: [PATCH 75/91] Add dark mode to landing page --- docs/home/assets/dark-mode.css | 16 ++++++++++++++++ docs/home/assets/home.css | 8 ++++++++ docs/home/assets/home.js | 29 +++++++++++++++++++++++++++++ docs/home/index.html | 28 ++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 docs/home/assets/dark-mode.css diff --git a/docs/home/assets/dark-mode.css b/docs/home/assets/dark-mode.css new file mode 100644 index 0000000000..80e9bd516d --- /dev/null +++ b/docs/home/assets/dark-mode.css @@ -0,0 +1,16 @@ +html { + background-color: #171717 !important; + filter: invert(100%) hue-rotate(180deg) brightness(105%) contrast(85%); + -webkit-filter: invert(100%) hue-rotate(180deg) brightness(105%) contrast(85%); +} + +body { + background-color: #FFF !important; +} + +img, +video, +body * [style*="background-image"] { + filter: hue-rotate(180deg) contrast(100%) invert(100%); + -webkit-filter: hue-rotate(180deg) contrast(100%) invert(100%); +} diff --git a/docs/home/assets/home.css b/docs/home/assets/home.css index 273efe261b..5314474112 100644 --- a/docs/home/assets/home.css +++ b/docs/home/assets/home.css @@ -603,3 +603,11 @@ div[sponsor]:hover { padding: 3px 0; } } + +/*-------------------------------------------------------------- +# Theme selection +--------------------------------------------------------------*/ +.btn-theme:focus, +.btn-theme:active { + box-shadow: none !important; +} diff --git a/docs/home/assets/home.js b/docs/home/assets/home.js index ed6571bf23..40e31c15ad 100644 --- a/docs/home/assets/home.js +++ b/docs/home/assets/home.js @@ -1,3 +1,31 @@ +function setTheme(theme) { + const darkModeStyleSheet = document.getElementById('dark-mode-style-sheet'); + const activeTheme = document.getElementById('active-theme'); + + if (theme === "auto") { + darkModeStyleSheet.disabled = !window.matchMedia("(prefers-color-scheme: dark)").matches; + activeTheme.className = "bi-circle-half"; + } + else if (theme === "dark") { + darkModeStyleSheet.disabled = false; + activeTheme.className = "bi bi-moon"; + } else if (theme === "light") { + darkModeStyleSheet.disabled = true; + activeTheme.className = "bi bi-sun"; + } + + localStorage.setItem("theme", theme) +} + +$('.theme-choice').click(function () { + setTheme(this.dataset.theme); +}) + +function initTheme() { + const theme = localStorage.getItem("theme") || "auto"; + setTheme(theme); +} + !(function($) { "use strict"; @@ -89,6 +117,7 @@ } $(window).on('load', function() { aos_init(); + initTheme(); }); })(jQuery); diff --git a/docs/home/index.html b/docs/home/index.html index 77f4fdb49f..e21d52ee9b 100644 --- a/docs/home/index.html +++ b/docs/home/index.html @@ -10,13 +10,41 @@ + + + +
+ +
From a8ff4ec46986702e78c9f876b274d7348d102089 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 25 May 2024 16:15:13 +0200 Subject: [PATCH 76/91] Update package Microsoft.NET.Test.Sdk from 17.9 to 17.10 (#1551) --- package-versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-versions.props b/package-versions.props index 894fd70d25..28e5eb5ef2 100644 --- a/package-versions.props +++ b/package-versions.props @@ -15,7 +15,7 @@ 2.3.* 2.0.* 8.0.* - 17.9.* + 17.10.* 2.8.* From 94d73326a5a635d89eef8c2cae1f93860ff1204f Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 25 May 2024 20:34:28 +0200 Subject: [PATCH 77/91] Permit using a relaxed (simpler) variant of the atomic:operations media type in the Content-Type request/response HTTP header, because OpenAPI code generators often choke on the double quotes in the official media type (producing code that doesn't compile). By default, the Content-Type used for responses is the same as the incoming one, although it can be overruled by sending an Accept HTTP header. --- .../Middleware/HeaderConstants.cs | 1 + .../Middleware/JsonApiMiddleware.cs | 45 +++++--- .../Serialization/Response/JsonApiWriter.cs | 49 +++++++- .../Mixed/AtomicLoggingTests.cs | 4 +- .../Mixed/AtomicRequestBodyTests.cs | 4 + .../Mixed/AtomicTraceLoggingTests.cs | 2 +- .../ContentNegotiation/AcceptHeaderTests.cs | 68 ++++++++++- .../ContentTypeHeaderTests.cs | 109 +++++++++++++++++- .../NonJsonApiControllerTests.cs | 6 + .../ReadWrite/Creating/CreateResourceTests.cs | 4 + .../AddToToManyRelationshipTests.cs | 4 + .../RemoveFromToManyRelationshipTests.cs | 4 + .../ReplaceToManyRelationshipTests.cs | 4 + .../UpdateToOneRelationshipTests.cs | 4 + .../Updating/Resources/UpdateResourceTests.cs | 4 + 15 files changed, 288 insertions(+), 24 deletions(-) diff --git a/src/JsonApiDotNetCore/Middleware/HeaderConstants.cs b/src/JsonApiDotNetCore/Middleware/HeaderConstants.cs index d290ba80eb..2e223e7fea 100644 --- a/src/JsonApiDotNetCore/Middleware/HeaderConstants.cs +++ b/src/JsonApiDotNetCore/Middleware/HeaderConstants.cs @@ -9,4 +9,5 @@ public static class HeaderConstants { public const string MediaType = "application/vnd.api+json"; public const string AtomicOperationsMediaType = $"{MediaType}; ext=\"https://jsonapi.org/ext/atomic\""; + public const string RelaxedAtomicOperationsMediaType = $"{MediaType}; ext=atomic-operations"; } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index 7cd2573727..4d9896ce8a 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -20,8 +20,20 @@ namespace JsonApiDotNetCore.Middleware; [PublicAPI] public sealed class JsonApiMiddleware { - private static readonly MediaTypeHeaderValue MediaType = MediaTypeHeaderValue.Parse(HeaderConstants.MediaType); - private static readonly MediaTypeHeaderValue AtomicOperationsMediaType = MediaTypeHeaderValue.Parse(HeaderConstants.AtomicOperationsMediaType); + private static readonly string[] NonOperationsContentTypes = [HeaderConstants.MediaType]; + private static readonly MediaTypeHeaderValue[] NonOperationsMediaTypes = [MediaTypeHeaderValue.Parse(HeaderConstants.MediaType)]; + + private static readonly string[] OperationsContentTypes = + [ + HeaderConstants.AtomicOperationsMediaType, + HeaderConstants.RelaxedAtomicOperationsMediaType + ]; + + private static readonly MediaTypeHeaderValue[] OperationsMediaTypes = + [ + MediaTypeHeaderValue.Parse(HeaderConstants.AtomicOperationsMediaType), + MediaTypeHeaderValue.Parse(HeaderConstants.RelaxedAtomicOperationsMediaType) + ]; private readonly RequestDelegate? _next; @@ -56,8 +68,8 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin if (primaryResourceType != null) { - if (!await ValidateContentTypeHeaderAsync(HeaderConstants.MediaType, httpContext, options.SerializerWriteOptions) || - !await ValidateAcceptHeaderAsync(MediaType, httpContext, options.SerializerWriteOptions)) + if (!await ValidateContentTypeHeaderAsync(NonOperationsContentTypes, httpContext, options.SerializerWriteOptions) || + !await ValidateAcceptHeaderAsync(NonOperationsMediaTypes, httpContext, options.SerializerWriteOptions)) { return; } @@ -68,8 +80,8 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin } else if (IsRouteForOperations(routeValues)) { - if (!await ValidateContentTypeHeaderAsync(HeaderConstants.AtomicOperationsMediaType, httpContext, options.SerializerWriteOptions) || - !await ValidateAcceptHeaderAsync(AtomicOperationsMediaType, httpContext, options.SerializerWriteOptions)) + if (!await ValidateContentTypeHeaderAsync(OperationsContentTypes, httpContext, options.SerializerWriteOptions) || + !await ValidateAcceptHeaderAsync(OperationsMediaTypes, httpContext, options.SerializerWriteOptions)) { return; } @@ -126,16 +138,19 @@ private async Task ValidateIfMatchHeaderAsync(HttpContext httpContext, Jso : null; } - private static async Task ValidateContentTypeHeaderAsync(string allowedContentType, HttpContext httpContext, JsonSerializerOptions serializerOptions) + private static async Task ValidateContentTypeHeaderAsync(ICollection allowedContentTypes, HttpContext httpContext, + JsonSerializerOptions serializerOptions) { string? contentType = httpContext.Request.ContentType; - if (contentType != null && contentType != allowedContentType) + if (contentType != null && !allowedContentTypes.Contains(contentType, StringComparer.OrdinalIgnoreCase)) { + string allowedValues = string.Join(" or ", allowedContentTypes.Select(value => $"'{value}'")); + await FlushResponseAsync(httpContext.Response, serializerOptions, new ErrorObject(HttpStatusCode.UnsupportedMediaType) { Title = "The specified Content-Type header value is not supported.", - Detail = $"Please specify '{allowedContentType}' instead of '{contentType}' for the Content-Type header value.", + Detail = $"Please specify {allowedValues} instead of '{contentType}' for the Content-Type header value.", Source = new ErrorSource { Header = "Content-Type" @@ -148,7 +163,7 @@ private static async Task ValidateContentTypeHeaderAsync(string allowedCon return true; } - private static async Task ValidateAcceptHeaderAsync(MediaTypeHeaderValue allowedMediaTypeValue, HttpContext httpContext, + private static async Task ValidateAcceptHeaderAsync(ICollection allowedMediaTypes, HttpContext httpContext, JsonSerializerOptions serializerOptions) { string[] acceptHeaders = httpContext.Request.Headers.GetCommaSeparatedValues("Accept"); @@ -164,15 +179,15 @@ private static async Task ValidateAcceptHeaderAsync(MediaTypeHeaderValue a { if (MediaTypeHeaderValue.TryParse(acceptHeader, out MediaTypeHeaderValue? headerValue)) { - headerValue.Quality = null; - if (headerValue.MediaType == "*/*" || headerValue.MediaType == "application/*") { seenCompatibleMediaType = true; break; } - if (allowedMediaTypeValue.Equals(headerValue)) + headerValue.Quality = null; + + if (allowedMediaTypes.Contains(headerValue)) { seenCompatibleMediaType = true; break; @@ -182,10 +197,12 @@ private static async Task ValidateAcceptHeaderAsync(MediaTypeHeaderValue a if (!seenCompatibleMediaType) { + string allowedValues = string.Join(" or ", allowedMediaTypes.Select(value => $"'{value}'")); + await FlushResponseAsync(httpContext.Response, serializerOptions, new ErrorObject(HttpStatusCode.NotAcceptable) { Title = "The specified Accept header value does not contain any supported media types.", - Detail = $"Please include '{allowedMediaTypeValue}' in the Accept header values.", + Detail = $"Please include {allowedValues} in the Accept header values.", Source = new ErrorSource { Header = "Accept" diff --git a/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs b/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs index 22de5284a2..9a4e8869e7 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs @@ -18,6 +18,15 @@ namespace JsonApiDotNetCore.Serialization.Response; /// public sealed class JsonApiWriter : IJsonApiWriter { + private static readonly MediaTypeHeaderValue OperationsMediaType = MediaTypeHeaderValue.Parse(HeaderConstants.AtomicOperationsMediaType); + private static readonly MediaTypeHeaderValue RelaxedOperationsMediaType = MediaTypeHeaderValue.Parse(HeaderConstants.RelaxedAtomicOperationsMediaType); + + private static readonly MediaTypeHeaderValue[] AllowedOperationsMediaTypes = + [ + OperationsMediaType, + RelaxedOperationsMediaType + ]; + private readonly IJsonApiRequest _request; private readonly IJsonApiOptions _options; private readonly IResponseModelAdapter _responseModelAdapter; @@ -70,7 +79,8 @@ public async Task WriteAsync(object? model, HttpContext httpContext) return $"Sending {httpContext.Response.StatusCode} response for {method} request at '{url}' with body: <<{responseBody}>>"; }); - await SendResponseBodyAsync(httpContext.Response, responseBody); + string responseContentType = GetResponseContentType(httpContext.Request); + await SendResponseBodyAsync(httpContext.Response, responseBody, responseContentType); } private static bool CanWriteBody(HttpStatusCode statusCode) @@ -167,11 +177,44 @@ private static bool RequestContainsMatchingETag(IHeaderDictionary requestHeaders return false; } - private async Task SendResponseBodyAsync(HttpResponse httpResponse, string? responseBody) + private string GetResponseContentType(HttpRequest httpRequest) + { + if (_request.Kind != EndpointKind.AtomicOperations) + { + return HeaderConstants.MediaType; + } + + MediaTypeHeaderValue? bestMatch = null; + + foreach (MediaTypeHeaderValue headerValue in httpRequest.GetTypedHeaders().Accept) + { + double quality = headerValue.Quality ?? 1.0; + headerValue.Quality = null; + + if (AllowedOperationsMediaTypes.Contains(headerValue)) + { + if (bestMatch == null || bestMatch.Quality < quality) + { + headerValue.Quality = quality; + bestMatch = headerValue; + } + } + } + + if (bestMatch == null) + { + return httpRequest.ContentType ?? HeaderConstants.AtomicOperationsMediaType; + } + + bestMatch.Quality = null; + return RelaxedOperationsMediaType.Equals(bestMatch) ? HeaderConstants.RelaxedAtomicOperationsMediaType : HeaderConstants.AtomicOperationsMediaType; + } + + private async Task SendResponseBodyAsync(HttpResponse httpResponse, string? responseBody, string contentType) { if (!string.IsNullOrEmpty(responseBody)) { - httpResponse.ContentType = _request.Kind == EndpointKind.AtomicOperations ? HeaderConstants.AtomicOperationsMediaType : HeaderConstants.MediaType; + httpResponse.ContentType = contentType; using IDisposable _ = CodeTimingSessionManager.Current.Measure("Send response body"); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicLoggingTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicLoggingTests.cs index 58824a8bf9..412a3f388c 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicLoggingTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicLoggingTests.cs @@ -36,7 +36,7 @@ public AtomicLoggingTests(IntegrationTestContext(); @@ -88,7 +88,7 @@ public async Task Logs_at_error_level_on_unhandled_exception() } [Fact] - public async Task Logs_at_info_level_on_invalid_request_body() + public async Task Logs_invalid_request_body_error_at_Information_level() { // Arrange var loggerFactory = _testContext.Factory.Services.GetRequiredService(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicRequestBodyTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicRequestBodyTests.cs index ee9d144123..c4416bbac1 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicRequestBodyTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicRequestBodyTests.cs @@ -1,5 +1,6 @@ using System.Net; using FluentAssertions; +using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Serialization.Objects; using TestBuildingBlocks; using Xunit; @@ -29,6 +30,9 @@ public async Task Cannot_process_for_missing_request_body() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); + httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.AtomicOperationsMediaType); + responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicTraceLoggingTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicTraceLoggingTests.cs index 14e1e4fd61..f3c47ed4b5 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicTraceLoggingTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicTraceLoggingTests.cs @@ -33,7 +33,7 @@ public AtomicTraceLoggingTests(IntegrationTestContext(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/AcceptHeaderTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/AcceptHeaderTests.cs index f06df45bb4..27e89d98f8 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/AcceptHeaderTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/AcceptHeaderTests.cs @@ -83,6 +83,9 @@ public async Task Permits_global_wildcard_in_Accept_headers() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); + httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.MediaType); } [Fact] @@ -102,6 +105,9 @@ public async Task Permits_application_wildcard_in_Accept_headers() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); + httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.MediaType); } [Fact] @@ -124,10 +130,59 @@ public async Task Permits_JsonApi_without_parameters_in_Accept_headers() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); + httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.MediaType); } [Fact] - public async Task Permits_JsonApi_with_AtomicOperations_extension_in_Accept_headers_at_operations_endpoint() + public async Task Prefers_JsonApi_with_AtomicOperations_extension_in_Accept_headers_at_operations_endpoint() + { + // Arrange + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "add", + data = new + { + type = "policies", + attributes = new + { + name = "some" + } + } + } + } + }; + + const string route = "/operations"; + const string contentType = HeaderConstants.RelaxedAtomicOperationsMediaType; + + Action setRequestHeaders = headers => + { + headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("text/html")); + headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse($"{HeaderConstants.MediaType}; profile=some")); + headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse(HeaderConstants.MediaType)); + headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse($"{HeaderConstants.MediaType}; unknown=unexpected")); + headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse($"{HeaderConstants.MediaType};EXT=atomic-operations; q=0.2")); + headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse($"{HeaderConstants.MediaType};EXT=\"https://jsonapi.org/ext/atomic\"; q=0.8")); + }; + + // Act + (HttpResponseMessage httpResponse, _) = await _testContext.ExecutePostAsync(route, requestBody, contentType, setRequestHeaders); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); + httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.AtomicOperationsMediaType); + } + + [Fact] + public async Task Prefers_JsonApi_with_relaxed_AtomicOperations_extension_in_Accept_headers_at_operations_endpoint() { // Arrange var requestBody = new @@ -158,7 +213,8 @@ public async Task Permits_JsonApi_with_AtomicOperations_extension_in_Accept_head headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse($"{HeaderConstants.MediaType}; profile=some")); headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse(HeaderConstants.MediaType)); headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse($"{HeaderConstants.MediaType}; unknown=unexpected")); - headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse($"{HeaderConstants.MediaType};ext=\"https://jsonapi.org/ext/atomic\"; q=0.2")); + headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse($"{HeaderConstants.MediaType};EXT=\"https://jsonapi.org/ext/atomic\"; q=0.2")); + headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse($"{HeaderConstants.MediaType};EXT=atomic-operations; q=0.8")); }; // Act @@ -166,6 +222,9 @@ public async Task Permits_JsonApi_with_AtomicOperations_extension_in_Accept_head // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); + httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.RelaxedAtomicOperationsMediaType); } [Fact] @@ -236,10 +295,13 @@ public async Task Denies_JsonApi_in_Accept_headers_at_operations_endpoint() responseDocument.Errors.ShouldHaveCount(1); + const string detail = + $"Please include '{HeaderConstants.AtomicOperationsMediaType}' or '{HeaderConstants.RelaxedAtomicOperationsMediaType}' in the Accept header values."; + ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotAcceptable); error.Title.Should().Be("The specified Accept header value does not contain any supported media types."); - error.Detail.Should().Be("Please include 'application/vnd.api+json; ext=\"https://jsonapi.org/ext/atomic\"' in the Accept header values."); + error.Detail.Should().Be(detail); error.Source.ShouldNotBeNull(); error.Source.Header.Should().Be("Accept"); } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/ContentTypeHeaderTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/ContentTypeHeaderTests.cs index 793dee05c8..4cabba7843 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/ContentTypeHeaderTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/ContentTypeHeaderTests.cs @@ -30,6 +30,7 @@ public async Task Returns_JsonApi_ContentType_header() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.MediaType); } @@ -64,10 +65,47 @@ public async Task Returns_JsonApi_ContentType_header_with_AtomicOperations_exten // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.AtomicOperationsMediaType); } + [Fact] + public async Task Returns_JsonApi_ContentType_header_with_relaxed_AtomicOperations_extension() + { + // Arrange + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "add", + data = new + { + type = "policies", + attributes = new + { + name = "some" + } + } + } + } + }; + + const string route = "/operations"; + const string contentType = HeaderConstants.RelaxedAtomicOperationsMediaType; + + // Act + (HttpResponseMessage httpResponse, _) = await _testContext.ExecutePostAtomicAsync(route, requestBody, contentType); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); + httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.RelaxedAtomicOperationsMediaType); + } + [Fact] public async Task Denies_unknown_ContentType_header() { @@ -163,6 +201,39 @@ public async Task Permits_JsonApi_ContentType_header_with_AtomicOperations_exten httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); } + [Fact] + public async Task Permits_JsonApi_ContentType_header_with_relaxed_AtomicOperations_extension_at_operations_endpoint() + { + // Arrange + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "add", + data = new + { + type = "policies", + attributes = new + { + name = "some" + } + } + } + } + }; + + const string route = "/operations"; + const string contentType = HeaderConstants.RelaxedAtomicOperationsMediaType; + + // Act + (HttpResponseMessage httpResponse, _) = await _testContext.ExecutePostAsync(route, requestBody, contentType); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + } + [Fact] public async Task Denies_JsonApi_ContentType_header_with_profile() { @@ -268,6 +339,41 @@ public async Task Denies_JsonApi_ContentType_header_with_AtomicOperations_extens error.Source.Header.Should().Be("Content-Type"); } + [Fact] + public async Task Denies_JsonApi_ContentType_header_with_relaxed_AtomicOperations_extension_at_resource_endpoint() + { + // Arrange + var requestBody = new + { + data = new + { + type = "policies", + attributes = new + { + name = "some" + } + } + }; + + const string route = "/policies"; + const string contentType = HeaderConstants.RelaxedAtomicOperationsMediaType; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody, contentType); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnsupportedMediaType); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnsupportedMediaType); + error.Title.Should().Be("The specified Content-Type header value is not supported."); + error.Detail.Should().Be($"Please specify 'application/vnd.api+json' instead of '{contentType}' for the Content-Type header value."); + error.Source.ShouldNotBeNull(); + error.Source.Header.Should().Be("Content-Type"); + } + [Fact] public async Task Denies_JsonApi_ContentType_header_with_CharSet() { @@ -373,7 +479,8 @@ public async Task Denies_JsonApi_ContentType_header_at_operations_endpoint() responseDocument.Errors.ShouldHaveCount(1); - const string detail = $"Please specify '{HeaderConstants.AtomicOperationsMediaType}' instead of '{contentType}' for the Content-Type header value."; + const string detail = + $"Please specify '{HeaderConstants.AtomicOperationsMediaType}' or '{HeaderConstants.RelaxedAtomicOperationsMediaType}' instead of '{contentType}' for the Content-Type header value."; ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnsupportedMediaType); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/NonJsonApiControllerTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/NonJsonApiControllerTests.cs index 3c4a6a6bae..bd5029736b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/NonJsonApiControllerTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/NonJsonApiControllerTests.cs @@ -30,6 +30,7 @@ public async Task Get_skips_middleware_and_formatters() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); httpResponse.Content.Headers.ContentType.ToString().Should().Be("application/json; charset=utf-8"); @@ -58,6 +59,7 @@ public async Task Post_skips_middleware_and_formatters() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); httpResponse.Content.Headers.ContentType.ToString().Should().Be("text/plain; charset=utf-8"); @@ -78,6 +80,7 @@ public async Task Post_skips_error_handler() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); httpResponse.Content.Headers.ContentType.ToString().Should().Be("text/plain; charset=utf-8"); @@ -106,6 +109,7 @@ public async Task Put_skips_middleware_and_formatters() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); httpResponse.Content.Headers.ContentType.ToString().Should().Be("text/plain; charset=utf-8"); @@ -126,6 +130,7 @@ public async Task Patch_skips_middleware_and_formatters() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); httpResponse.Content.Headers.ContentType.ToString().Should().Be("text/plain; charset=utf-8"); @@ -146,6 +151,7 @@ public async Task Delete_skips_middleware_and_formatters() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); httpResponse.Content.Headers.ContentType.ToString().Should().Be("text/plain; charset=utf-8"); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs index 5f1f894a9b..917f10be85 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs @@ -2,6 +2,7 @@ using System.Reflection; using FluentAssertions; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization.Objects; using Microsoft.EntityFrameworkCore; @@ -476,6 +477,9 @@ public async Task Cannot_create_resource_for_missing_request_body() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); + httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.MediaType); + responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/AddToToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/AddToToManyRelationshipTests.cs index 879a86caa8..acf46913a0 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/AddToToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/AddToToManyRelationshipTests.cs @@ -1,5 +1,6 @@ using System.Net; using FluentAssertions; +using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Serialization.Objects; using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; @@ -196,6 +197,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); + httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.MediaType); + responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs index 4f3e8d3ab3..8df5e10a60 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using JsonApiDotNetCore; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; using JsonApiDotNetCore.Serialization.Objects; @@ -308,6 +309,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); + httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.MediaType); + responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/ReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/ReplaceToManyRelationshipTests.cs index 431bf89396..48c25db786 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/ReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/ReplaceToManyRelationshipTests.cs @@ -1,5 +1,6 @@ using System.Net; using FluentAssertions; +using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Serialization.Objects; using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; @@ -225,6 +226,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); + httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.MediaType); + responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs index b96fcd6fff..b85e268163 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs @@ -1,5 +1,6 @@ using System.Net; using FluentAssertions; +using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Serialization.Objects; using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; @@ -261,6 +262,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); + httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.MediaType); + responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateResourceTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateResourceTests.cs index 6087f09fff..9d328bd9e4 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateResourceTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateResourceTests.cs @@ -2,6 +2,7 @@ using System.Reflection; using FluentAssertions; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization.Objects; using Microsoft.EntityFrameworkCore; @@ -663,6 +664,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); + httpResponse.Content.Headers.ContentType.ShouldNotBeNull(); + httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.MediaType); + responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; From 461d6d3926181b714ce66ca695819860d4aa5cf6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 May 2024 05:07:56 +0000 Subject: [PATCH 78/91] Bump dotnet-reportgenerator-globaltool from 5.3.0 to 5.3.4 (#1555) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 2e7b1d6d91..211c99567a 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "dotnet-reportgenerator-globaltool": { - "version": "5.3.0", + "version": "5.3.4", "commands": [ "reportgenerator" ] From e47faf3e52793cfcba6414d1df02eae3efe33cf2 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Tue, 18 Jun 2024 02:22:29 +0200 Subject: [PATCH 79/91] Add IAtomicOperationFilter, which is used to constrain the exposed atomic:operations. --- docs/usage/writing/bulk-batch-operations.md | 15 +- .../Controllers/OperationsController.cs | 3 +- .../Controllers/OperationsController.cs | 3 +- .../DefaultOperationFilter.cs | 32 ++++ .../IAtomicOperationFilter.cs | 42 +++++ .../JsonApiApplicationBuilder.cs | 1 + .../BaseJsonApiOperationsController.cs | 71 ++++++- .../JsonApiOperationsController.cs | 3 +- ...omConstrainedOperationsControllerTests.cs} | 18 +- ...ultConstrainedOperationsControllerTests.cs | 173 ++++++++++++++++++ .../CreateMusicTrackOperationsController.cs | 36 +--- .../AtomicOperations/OperationsController.cs | 3 +- .../AtomicOperations/TextLanguage.cs | 4 +- .../Scopes/OperationsController.cs | 3 +- .../OperationsController.cs | 3 +- 15 files changed, 364 insertions(+), 46 deletions(-) create mode 100644 src/JsonApiDotNetCore/AtomicOperations/DefaultOperationFilter.cs create mode 100644 src/JsonApiDotNetCore/AtomicOperations/IAtomicOperationFilter.cs rename test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/{AtomicConstrainedOperationsControllerTests.cs => AtomicCustomConstrainedOperationsControllerTests.cs} (86%) create mode 100644 test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicDefaultConstrainedOperationsControllerTests.cs 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/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/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/JsonApiDotNetCore/AtomicOperations/DefaultOperationFilter.cs b/src/JsonApiDotNetCore/AtomicOperations/DefaultOperationFilter.cs new file mode 100644 index 0000000000..d1ec1bd65c --- /dev/null +++ b/src/JsonApiDotNetCore/AtomicOperations/DefaultOperationFilter.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCore.AtomicOperations; + +/// +internal sealed class DefaultOperationFilter : IAtomicOperationFilter +{ + /// + public bool IsEnabled(ResourceType resourceType, WriteOperationKind writeOperation) + { + var resourceAttribute = resourceType.ClrType.GetCustomAttribute(); + return resourceAttribute != null && Contains(resourceAttribute.GenerateControllerEndpoints, writeOperation); + } + + private static bool Contains(JsonApiEndpoints endpoints, WriteOperationKind writeOperation) + { + return writeOperation switch + { + WriteOperationKind.CreateResource => endpoints.HasFlag(JsonApiEndpoints.Post), + WriteOperationKind.UpdateResource => endpoints.HasFlag(JsonApiEndpoints.Patch), + WriteOperationKind.DeleteResource => endpoints.HasFlag(JsonApiEndpoints.Delete), + WriteOperationKind.SetRelationship => endpoints.HasFlag(JsonApiEndpoints.PatchRelationship), + WriteOperationKind.AddToRelationship => endpoints.HasFlag(JsonApiEndpoints.PostRelationship), + WriteOperationKind.RemoveFromRelationship => endpoints.HasFlag(JsonApiEndpoints.DeleteRelationship), + _ => false + }; + } +} diff --git a/src/JsonApiDotNetCore/AtomicOperations/IAtomicOperationFilter.cs b/src/JsonApiDotNetCore/AtomicOperations/IAtomicOperationFilter.cs new file mode 100644 index 0000000000..240efbf936 --- /dev/null +++ b/src/JsonApiDotNetCore/AtomicOperations/IAtomicOperationFilter.cs @@ -0,0 +1,42 @@ +using JetBrains.Annotations; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCore.AtomicOperations; + +/// +/// Determines whether an operation in an atomic:operations request can be used. +/// +/// +/// The default implementation relies on the usage of . If you're using explicit +/// (non-generated) controllers, register your own implementation to indicate which operations are accessible. +/// +[PublicAPI] +public interface IAtomicOperationFilter +{ + /// + /// An that always returns true. Provided for convenience, to revert to the original behavior from before + /// filtering was introduced. + /// + public static IAtomicOperationFilter AlwaysEnabled { get; } = new AlwaysEnabledOperationFilter(); + + /// + /// Determines whether the specified operation can be used in an atomic:operations request. + /// + /// + /// The targeted primary resource type of the operation. + /// + /// + /// The operation kind. + /// + bool IsEnabled(ResourceType resourceType, WriteOperationKind writeOperation); + + private sealed class AlwaysEnabledOperationFilter : IAtomicOperationFilter + { + public bool IsEnabled(ResourceType resourceType, WriteOperationKind writeOperation) + { + return true; + } + } +} diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs index 2973a664f6..2f725e8c68 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs @@ -300,5 +300,6 @@ private void AddOperationsLayer() _services.TryAddScoped(); _services.TryAddScoped(); _services.TryAddScoped(); + _services.TryAddSingleton(); } } diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs index 596b22794d..5485fad3e0 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs @@ -1,9 +1,11 @@ +using System.Net; using JetBrains.Annotations; using JsonApiDotNetCore.AtomicOperations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Serialization.Objects; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging; @@ -22,10 +24,11 @@ public abstract class BaseJsonApiOperationsController : CoreJsonApiController private readonly IOperationsProcessor _processor; private readonly IJsonApiRequest _request; private readonly ITargetedFields _targetedFields; + private readonly IAtomicOperationFilter _operationFilter; private readonly TraceLogWriter _traceWriter; protected BaseJsonApiOperationsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IOperationsProcessor processor, IJsonApiRequest request, ITargetedFields targetedFields) + IOperationsProcessor processor, IJsonApiRequest request, ITargetedFields targetedFields, IAtomicOperationFilter operationFilter) { ArgumentGuard.NotNull(options); ArgumentGuard.NotNull(resourceGraph); @@ -33,12 +36,14 @@ protected BaseJsonApiOperationsController(IJsonApiOptions options, IResourceGrap ArgumentGuard.NotNull(processor); ArgumentGuard.NotNull(request); ArgumentGuard.NotNull(targetedFields); + ArgumentGuard.NotNull(operationFilter); _options = options; _resourceGraph = resourceGraph; _processor = processor; _request = request; _targetedFields = targetedFields; + _operationFilter = operationFilter; _traceWriter = new TraceLogWriter(loggerFactory); } @@ -111,6 +116,8 @@ public virtual async Task PostOperationsAsync([FromBody] IList PostOperationsAsync([FromBody] IList result != null) ? Ok(results) : NoContent(); } + protected virtual void ValidateEnabledOperations(IList operations) + { + List errors = []; + + for (int operationIndex = 0; operationIndex < operations.Count; operationIndex++) + { + IJsonApiRequest operationRequest = operations[operationIndex].Request; + WriteOperationKind operationKind = operationRequest.WriteOperation!.Value; + + if (operationRequest.Relationship != null && !_operationFilter.IsEnabled(operationRequest.Relationship.LeftType, operationKind)) + { + string operationCode = GetOperationCodeText(operationKind); + + errors.Add(new ErrorObject(HttpStatusCode.UnprocessableEntity) + { + Title = "The requested operation is not accessible.", + Detail = $"The '{operationCode}' relationship operation is not accessible for relationship '{operationRequest.Relationship}' " + + $"on resource type '{operationRequest.Relationship.LeftType}'.", + Source = new ErrorSource + { + Pointer = $"/atomic:operations[{operationIndex}]" + } + }); + } + else if (operationRequest.PrimaryResourceType != null && !_operationFilter.IsEnabled(operationRequest.PrimaryResourceType, operationKind)) + { + string operationCode = GetOperationCodeText(operationKind); + + errors.Add(new ErrorObject(HttpStatusCode.UnprocessableEntity) + { + Title = "The requested operation is not accessible.", + Detail = $"The '{operationCode}' resource operation is not accessible for resource type '{operationRequest.PrimaryResourceType}'.", + Source = new ErrorSource + { + Pointer = $"/atomic:operations[{operationIndex}]" + } + }); + } + } + + if (errors.Count > 0) + { + throw new JsonApiException(errors); + } + } + + private static string GetOperationCodeText(WriteOperationKind operationKind) + { + AtomicOperationCode operationCode = operationKind switch + { + WriteOperationKind.CreateResource => AtomicOperationCode.Add, + WriteOperationKind.UpdateResource => AtomicOperationCode.Update, + WriteOperationKind.DeleteResource => AtomicOperationCode.Remove, + WriteOperationKind.AddToRelationship => AtomicOperationCode.Add, + WriteOperationKind.SetRelationship => AtomicOperationCode.Update, + WriteOperationKind.RemoveFromRelationship => AtomicOperationCode.Remove, + _ => throw new NotSupportedException($"Unknown operation kind '{operationKind}'.") + }; + + return operationCode.ToString().ToLowerInvariant(); + } + protected virtual void ValidateModelState(IList operations) { // We must validate the resource inside each operation manually, because they are typed as IIdentifiable. diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs index bc14d4886e..168800b571 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs @@ -14,7 +14,8 @@ namespace JsonApiDotNetCore.Controllers; /// public abstract class JsonApiOperationsController( IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, - ITargetedFields targetedFields) : BaseJsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields) + ITargetedFields targetedFields, IAtomicOperationFilter operationFilter) : BaseJsonApiOperationsController(options, resourceGraph, loggerFactory, processor, + request, targetedFields, operationFilter) { /// [HttpPost] diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicConstrainedOperationsControllerTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicCustomConstrainedOperationsControllerTests.cs similarity index 86% rename from test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicConstrainedOperationsControllerTests.cs rename to test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicCustomConstrainedOperationsControllerTests.cs index e56e9119bf..51cb1a53a2 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicConstrainedOperationsControllerTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicCustomConstrainedOperationsControllerTests.cs @@ -6,13 +6,13 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.Controllers; -public sealed class AtomicConstrainedOperationsControllerTests +public sealed class AtomicCustomConstrainedOperationsControllerTests : IClassFixture, OperationsDbContext>> { private readonly IntegrationTestContext, OperationsDbContext> _testContext; private readonly OperationsFakers _fakers = new(); - public AtomicConstrainedOperationsControllerTests(IntegrationTestContext, OperationsDbContext> testContext) + public AtomicCustomConstrainedOperationsControllerTests(IntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; @@ -102,14 +102,14 @@ public async Task Cannot_create_resource_for_mismatching_resource_type() ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); - error.Title.Should().Be("Unsupported combination of operation code and resource type at this endpoint."); - error.Detail.Should().Be("This endpoint can only be used to create resources of type 'musicTracks'."); + error.Title.Should().Be("The requested operation is not accessible."); + error.Detail.Should().Be("The 'add' resource operation is not accessible for resource type 'performers'."); error.Source.ShouldNotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } [Fact] - public async Task Cannot_update_resources_for_matching_resource_type() + public async Task Cannot_update_resource_for_matching_resource_type() { // Arrange MusicTrack existingTrack = _fakers.MusicTrack.Generate(); @@ -151,8 +151,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); - error.Title.Should().Be("Unsupported combination of operation code and resource type at this endpoint."); - error.Detail.Should().Be("This endpoint can only be used to create resources of type 'musicTracks'."); + error.Title.Should().Be("The requested operation is not accessible."); + error.Detail.Should().Be("The 'update' resource operation is not accessible for resource type 'musicTracks'."); error.Source.ShouldNotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } @@ -207,8 +207,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); - error.Title.Should().Be("Unsupported combination of operation code and resource type at this endpoint."); - error.Detail.Should().Be("This endpoint can only be used to create resources of type 'musicTracks'."); + error.Title.Should().Be("The requested operation is not accessible."); + error.Detail.Should().Be("The 'add' relationship operation is not accessible for relationship 'performers' on resource type 'musicTracks'."); error.Source.ShouldNotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicDefaultConstrainedOperationsControllerTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicDefaultConstrainedOperationsControllerTests.cs new file mode 100644 index 0000000000..14dc1ab83b --- /dev/null +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicDefaultConstrainedOperationsControllerTests.cs @@ -0,0 +1,173 @@ +using System.Net; +using FluentAssertions; +using JsonApiDotNetCore.Serialization.Objects; +using TestBuildingBlocks; +using Xunit; + +namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.Controllers; + +public sealed class AtomicDefaultConstrainedOperationsControllerTests + : IClassFixture, OperationsDbContext>> +{ + private readonly IntegrationTestContext, OperationsDbContext> _testContext; + private readonly OperationsFakers _fakers = new(); + + public AtomicDefaultConstrainedOperationsControllerTests(IntegrationTestContext, OperationsDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + } + + [Fact] + public async Task Cannot_delete_resource_for_disabled_resource_endpoint() + { + // Arrange + TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.AddInRange(existingLanguage); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "remove", + @ref = new + { + type = "textLanguages", + id = existingLanguage.StringId + } + } + } + }; + + const string route = "/operations"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("The requested operation is not accessible."); + error.Detail.Should().Be("The 'remove' resource operation is not accessible for resource type 'textLanguages'."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); + } + + [Fact] + public async Task Cannot_change_ToMany_relationship_for_disabled_resource_endpoints() + { + // Arrange + TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); + Lyric existingLyric = _fakers.Lyric.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.AddInRange(existingLanguage, existingLyric); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "update", + @ref = new + { + type = "textLanguages", + id = existingLanguage.StringId, + relationship = "lyrics" + }, + data = new[] + { + new + { + type = "lyrics", + id = existingLyric.StringId + } + } + }, + new + { + op = "add", + @ref = new + { + type = "textLanguages", + id = existingLanguage.StringId, + relationship = "lyrics" + }, + data = new[] + { + new + { + type = "lyrics", + id = existingLyric.StringId + } + } + }, + new + { + op = "remove", + @ref = new + { + type = "textLanguages", + id = existingLanguage.StringId, + relationship = "lyrics" + }, + data = new[] + { + new + { + type = "lyrics", + id = existingLyric.StringId + } + } + } + } + }; + + const string route = "/operations"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(3); + + ErrorObject error1 = responseDocument.Errors[0]; + error1.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error1.Title.Should().Be("The requested operation is not accessible."); + error1.Detail.Should().Be("The 'update' relationship operation is not accessible for relationship 'lyrics' on resource type 'textLanguages'."); + error1.Source.ShouldNotBeNull(); + error1.Source.Pointer.Should().Be("/atomic:operations[0]"); + + ErrorObject error2 = responseDocument.Errors[1]; + error2.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error2.Title.Should().Be("The requested operation is not accessible."); + error2.Detail.Should().Be("The 'add' relationship operation is not accessible for relationship 'lyrics' on resource type 'textLanguages'."); + error2.Source.ShouldNotBeNull(); + error2.Source.Pointer.Should().Be("/atomic:operations[1]"); + + ErrorObject error3 = responseDocument.Errors[2]; + error3.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error3.Title.Should().Be("The requested operation is not accessible."); + error3.Detail.Should().Be("The 'remove' relationship operation is not accessible for relationship 'lyrics' on resource type 'textLanguages'."); + error3.Source.ShouldNotBeNull(); + error3.Source.Pointer.Should().Be("/atomic:operations[2]"); + } +} diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs index 01a378e5aa..b3f98df0bc 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs @@ -1,12 +1,9 @@ -using System.Net; using JsonApiDotNetCore.AtomicOperations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Controllers.Annotations; -using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Serialization.Objects; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -16,35 +13,20 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.Controllers; [Route("/operations/musicTracks/create")] public sealed class CreateMusicTrackOperationsController( IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, - ITargetedFields targetedFields) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields) + ITargetedFields targetedFields) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields, + OnlyCreateMusicTracksOperationFilter.Instance) { - public override async Task PostOperationsAsync(IList operations, CancellationToken cancellationToken) + private sealed class OnlyCreateMusicTracksOperationFilter : IAtomicOperationFilter { - AssertOnlyCreatingMusicTracks(operations); + public static readonly OnlyCreateMusicTracksOperationFilter Instance = new(); - return await base.PostOperationsAsync(operations, cancellationToken); - } - - private static void AssertOnlyCreatingMusicTracks(IEnumerable operations) - { - int index = 0; - - foreach (OperationContainer operation in operations) + private OnlyCreateMusicTracksOperationFilter() { - if (operation.Request.WriteOperation != WriteOperationKind.CreateResource || operation.Resource.GetType() != typeof(MusicTrack)) - { - throw new JsonApiException(new ErrorObject(HttpStatusCode.UnprocessableEntity) - { - Title = "Unsupported combination of operation code and resource type at this endpoint.", - Detail = "This endpoint can only be used to create resources of type 'musicTracks'.", - Source = new ErrorSource - { - Pointer = $"/atomic:operations[{index}]" - } - }); - } + } - index++; + public bool IsEnabled(ResourceType resourceType, WriteOperationKind writeOperation) + { + return writeOperation == WriteOperationKind.CreateResource && resourceType.ClrType == typeof(MusicTrack); } } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsController.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsController.cs index 5380300ede..78426804b3 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsController.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsController.cs @@ -9,4 +9,5 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations; 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/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/TextLanguage.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/TextLanguage.cs index 02e8bf6278..e4e440600d 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/TextLanguage.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/TextLanguage.cs @@ -1,11 +1,13 @@ using JetBrains.Annotations; +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(ControllerNamespace = "JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations")] +[Resource(ControllerNamespace = "JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations", + GenerateControllerEndpoints = JsonApiEndpoints.Post | JsonApiEndpoints.Patch)] public sealed class TextLanguage : Identifiable { [Attr] diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/OperationsController.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/OperationsController.cs index 357ff7ef5a..de1cd02c20 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/OperationsController.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes/OperationsController.cs @@ -12,7 +12,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.Authorization.Scopes; 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) { public override async Task PostOperationsAsync(IList operations, CancellationToken cancellationToken) { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/OperationsController.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/OperationsController.cs index dfe7282eac..24300dfc5c 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/OperationsController.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/OperationsController.cs @@ -9,4 +9,5 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ContentNegotiation; 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); From c3d1f7f0eb177079fdca1a87c76596157a2b82f6 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 19 Jun 2024 05:21:12 +0200 Subject: [PATCH 80/91] Return Forbidden when operation is inaccessible, to match resource endpoint status code --- .../BaseJsonApiOperationsController.cs | 4 ++-- ...stomConstrainedOperationsControllerTests.cs | 18 +++++++++--------- ...aultConstrainedOperationsControllerTests.cs | 16 ++++++++-------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs index 5485fad3e0..b169bdd005 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs @@ -140,7 +140,7 @@ protected virtual void ValidateEnabledOperations(IList opera { string operationCode = GetOperationCodeText(operationKind); - errors.Add(new ErrorObject(HttpStatusCode.UnprocessableEntity) + errors.Add(new ErrorObject(HttpStatusCode.Forbidden) { Title = "The requested operation is not accessible.", Detail = $"The '{operationCode}' relationship operation is not accessible for relationship '{operationRequest.Relationship}' " + @@ -155,7 +155,7 @@ protected virtual void ValidateEnabledOperations(IList opera { string operationCode = GetOperationCodeText(operationKind); - errors.Add(new ErrorObject(HttpStatusCode.UnprocessableEntity) + errors.Add(new ErrorObject(HttpStatusCode.Forbidden) { Title = "The requested operation is not accessible.", Detail = $"The '{operationCode}' resource operation is not accessible for resource type '{operationRequest.PrimaryResourceType}'.", diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicCustomConstrainedOperationsControllerTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicCustomConstrainedOperationsControllerTests.cs index 51cb1a53a2..099168b124 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicCustomConstrainedOperationsControllerTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicCustomConstrainedOperationsControllerTests.cs @@ -69,7 +69,7 @@ public async Task Can_create_resources_for_matching_resource_type() } [Fact] - public async Task Cannot_create_resource_for_mismatching_resource_type() + public async Task Cannot_create_resource_for_inaccessible_operation() { // Arrange var requestBody = new @@ -96,12 +96,12 @@ public async Task Cannot_create_resource_for_mismatching_resource_type() (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert - httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + httpResponse.ShouldHaveStatusCode(HttpStatusCode.Forbidden); responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.StatusCode.Should().Be(HttpStatusCode.Forbidden); error.Title.Should().Be("The requested operation is not accessible."); error.Detail.Should().Be("The 'add' resource operation is not accessible for resource type 'performers'."); error.Source.ShouldNotBeNull(); @@ -109,7 +109,7 @@ public async Task Cannot_create_resource_for_mismatching_resource_type() } [Fact] - public async Task Cannot_update_resource_for_matching_resource_type() + public async Task Cannot_update_resource_for_inaccessible_operation() { // Arrange MusicTrack existingTrack = _fakers.MusicTrack.Generate(); @@ -145,12 +145,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert - httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + httpResponse.ShouldHaveStatusCode(HttpStatusCode.Forbidden); responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.StatusCode.Should().Be(HttpStatusCode.Forbidden); error.Title.Should().Be("The requested operation is not accessible."); error.Detail.Should().Be("The 'update' resource operation is not accessible for resource type 'musicTracks'."); error.Source.ShouldNotBeNull(); @@ -158,7 +158,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } [Fact] - public async Task Cannot_add_to_ToMany_relationship_for_matching_resource_type() + public async Task Cannot_add_to_ToMany_relationship_for_inaccessible_operation() { // Arrange MusicTrack existingTrack = _fakers.MusicTrack.Generate(); @@ -201,12 +201,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert - httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + httpResponse.ShouldHaveStatusCode(HttpStatusCode.Forbidden); responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.StatusCode.Should().Be(HttpStatusCode.Forbidden); error.Title.Should().Be("The requested operation is not accessible."); error.Detail.Should().Be("The 'add' relationship operation is not accessible for relationship 'performers' on resource type 'musicTracks'."); error.Source.ShouldNotBeNull(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicDefaultConstrainedOperationsControllerTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicDefaultConstrainedOperationsControllerTests.cs index 14dc1ab83b..caffc32e2b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicDefaultConstrainedOperationsControllerTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Controllers/AtomicDefaultConstrainedOperationsControllerTests.cs @@ -20,7 +20,7 @@ public AtomicDefaultConstrainedOperationsControllerTests(IntegrationTestContext< } [Fact] - public async Task Cannot_delete_resource_for_disabled_resource_endpoint() + public async Task Cannot_delete_resource_for_inaccessible_operation() { // Arrange TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); @@ -53,12 +53,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert - httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + httpResponse.ShouldHaveStatusCode(HttpStatusCode.Forbidden); responseDocument.Errors.ShouldHaveCount(1); ErrorObject error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.StatusCode.Should().Be(HttpStatusCode.Forbidden); error.Title.Should().Be("The requested operation is not accessible."); error.Detail.Should().Be("The 'remove' resource operation is not accessible for resource type 'textLanguages'."); error.Source.ShouldNotBeNull(); @@ -66,7 +66,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } [Fact] - public async Task Cannot_change_ToMany_relationship_for_disabled_resource_endpoints() + public async Task Cannot_change_ToMany_relationship_for_inaccessible_operations() { // Arrange TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); @@ -145,26 +145,26 @@ await _testContext.RunOnDatabaseAsync(async dbContext => (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); // Assert - httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + httpResponse.ShouldHaveStatusCode(HttpStatusCode.Forbidden); responseDocument.Errors.ShouldHaveCount(3); ErrorObject error1 = responseDocument.Errors[0]; - error1.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error1.StatusCode.Should().Be(HttpStatusCode.Forbidden); error1.Title.Should().Be("The requested operation is not accessible."); error1.Detail.Should().Be("The 'update' relationship operation is not accessible for relationship 'lyrics' on resource type 'textLanguages'."); error1.Source.ShouldNotBeNull(); error1.Source.Pointer.Should().Be("/atomic:operations[0]"); ErrorObject error2 = responseDocument.Errors[1]; - error2.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error2.StatusCode.Should().Be(HttpStatusCode.Forbidden); error2.Title.Should().Be("The requested operation is not accessible."); error2.Detail.Should().Be("The 'add' relationship operation is not accessible for relationship 'lyrics' on resource type 'textLanguages'."); error2.Source.ShouldNotBeNull(); error2.Source.Pointer.Should().Be("/atomic:operations[1]"); ErrorObject error3 = responseDocument.Errors[2]; - error3.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error3.StatusCode.Should().Be(HttpStatusCode.Forbidden); error3.Title.Should().Be("The requested operation is not accessible."); error3.Detail.Should().Be("The 'remove' relationship operation is not accessible for relationship 'lyrics' on resource type 'textLanguages'."); error3.Source.ShouldNotBeNull(); From 8b0b90b5eb5c65c0704064105d7adfa18c35f5e2 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 19 Jun 2024 05:29:43 +0200 Subject: [PATCH 81/91] Remove installing PowerShell in cibuild, this is no longer needed --- .github/workflows/build.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9ef1f2f119..da0993c185 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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: | From 81b82adab2998d887e342052d8fe5f46fad1ef27 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 19 Jun 2024 05:43:42 +0200 Subject: [PATCH 82/91] Fixed: return empty object instead of data:null in operation results --- .../WriteOnlyDocumentConverter.cs | 23 ++++++++++++++++++- .../Mixed/AtomicSerializationTests.cs | 4 +--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/JsonApiDotNetCore/Serialization/JsonConverters/WriteOnlyDocumentConverter.cs b/src/JsonApiDotNetCore/Serialization/JsonConverters/WriteOnlyDocumentConverter.cs index 2597afacac..a8f3e7f81e 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonConverters/WriteOnlyDocumentConverter.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonConverters/WriteOnlyDocumentConverter.cs @@ -61,7 +61,28 @@ public override void Write(Utf8JsonWriter writer, Document value, JsonSerializer if (!value.Results.IsNullOrEmpty()) { writer.WritePropertyName(AtomicResultsText); - WriteSubTree(writer, value.Results, options); + writer.WriteStartArray(); + + foreach (AtomicResultObject result in value.Results) + { + writer.WriteStartObject(); + + if (result.Data.IsAssigned) + { + writer.WritePropertyName(DataText); + WriteSubTree(writer, result.Data, options); + } + + if (!result.Meta.IsNullOrEmpty()) + { + writer.WritePropertyName(MetaText); + WriteSubTree(writer, result.Meta, options); + } + + writer.WriteEndObject(); + } + + writer.WriteEndArray(); } if (!value.Errors.IsNullOrEmpty()) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicSerializationTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicSerializationTests.cs index 4f6a2f0a3c..fc6d366f94 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicSerializationTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicSerializationTests.cs @@ -101,9 +101,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => "self": "http://localhost/operations" }, "atomic:results": [ - { - "data": null - }, + {}, { "data": { "type": "textLanguages", From efab186af97f2c5f5672319f92152f22b3cecfd8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 11:38:24 +0000 Subject: [PATCH 83/91] Bump jetbrains.resharper.globaltools from 2024.1.2 to 2024.1.3 (#1558) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 211c99567a..da66b46f5f 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2024.1.2", + "version": "2024.1.3", "commands": [ "jb" ] From ee184d6695579228bc9f3ec5688cf11d77b28e31 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 21:46:59 +0000 Subject: [PATCH 84/91] Bump dotnet-reportgenerator-globaltool from 5.3.4 to 5.3.6 (#1557) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index da66b46f5f..8be5880888 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "dotnet-reportgenerator-globaltool": { - "version": "5.3.4", + "version": "5.3.6", "commands": [ "reportgenerator" ] From 4f3d5bde3f559818df097b3998d21403f9213427 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 22:17:24 +0000 Subject: [PATCH 85/91] Update Microsoft.CodeAnalysis.CSharp from 4.9.* to 4.10.* in tests (#1559) --- package-versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-versions.props b/package-versions.props index 28e5eb5ef2..9ba0b34bbb 100644 --- a/package-versions.props +++ b/package-versions.props @@ -8,7 +8,7 @@ 0.13.* 35.5.* - 4.9.* + 4.10.* 6.0.* 2.1.* 6.12.* From 2a4df13e7e80cd95f0e5bc84f11d6ce9a3adfbe4 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Mon, 26 Feb 2024 02:47:13 +0100 Subject: [PATCH 86/91] Exclude kiota-lock.json from source control due to bug in Kiota --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) 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 From 491b003f1f1980237f9904ad4dec24d4cfb3c945 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 03:50:52 +0000 Subject: [PATCH 87/91] Bump jetbrains.resharper.globaltools from 2024.1.3 to 2024.1.4 (#1569) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 8be5880888..435201037d 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2024.1.3", + "version": "2024.1.4", "commands": [ "jb" ] From 73029067bad3d2985fc0859c5f34283782a5f873 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 30 Jun 2024 13:57:15 +0200 Subject: [PATCH 88/91] Fixed: Do not allow the use of 'lid' when client-generated IDs are required --- .../Adapters/AtomicOperationObjectAdapter.cs | 5 ++- .../Adapters/RelationshipDataAdapter.cs | 2 + .../Adapters/ResourceIdentityAdapter.cs | 19 +++++---- .../Adapters/ResourceIdentityRequirements.cs | 16 ++++++++ ...reateResourceWithClientGeneratedIdTests.cs | 41 ++++++++++++++++++- 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicOperationObjectAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicOperationObjectAdapter.cs index d0574f2fa1..795030598c 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicOperationObjectAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicOperationObjectAdapter.cs @@ -125,7 +125,9 @@ private ResourceIdentityRequirements CreateRefRequirements(RequestAdapterState s return new ResourceIdentityRequirements { EvaluateIdConstraint = resourceType => - ResourceIdentityRequirements.DoEvaluateIdConstraint(resourceType, state.Request.WriteOperation, _options.ClientIdGeneration) + ResourceIdentityRequirements.DoEvaluateIdConstraint(resourceType, state.Request.WriteOperation, _options.ClientIdGeneration), + EvaluateAllowLid = resourceType => + ResourceIdentityRequirements.DoEvaluateAllowLid(resourceType, state.Request.WriteOperation, _options.ClientIdGeneration) }; } @@ -135,6 +137,7 @@ private static ResourceIdentityRequirements CreateDataRequirements(AtomicReferen { ResourceType = refResult.ResourceType, EvaluateIdConstraint = refRequirements.EvaluateIdConstraint, + EvaluateAllowLid = refRequirements.EvaluateAllowLid, IdValue = refResult.Resource.StringId, LidValue = refResult.Resource.LocalId, RelationshipName = refResult.Relationship?.PublicName diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/RelationshipDataAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/RelationshipDataAdapter.cs index 5925524e6b..0e7f292394 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/RelationshipDataAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/RelationshipDataAdapter.cs @@ -1,4 +1,5 @@ using System.Collections; +using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; using JsonApiDotNetCore.Serialization.Objects; @@ -71,6 +72,7 @@ private static SingleOrManyData ToIdentifierData(Singl { ResourceType = relationship.RightType, EvaluateIdConstraint = _ => JsonElementConstraint.Required, + EvaluateAllowLid = _ => state.Request.Kind == EndpointKind.AtomicOperations, RelationshipName = relationship.PublicName }; diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs index be596dae7c..5e25dba0f7 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs @@ -96,18 +96,20 @@ private static void AssertIsCompatibleResourceType(ResourceType actual, Resource private IIdentifiable CreateResource(ResourceIdentity identity, ResourceIdentityRequirements requirements, ResourceType resourceType, RequestAdapterState state) { - if (state.Request.Kind != EndpointKind.AtomicOperations) + AssertNoIdWithLid(identity, state); + + bool allowLid = requirements.EvaluateAllowLid?.Invoke(resourceType) ?? false; + + if (!allowLid) { AssertHasNoLid(identity, state); } - AssertNoIdWithLid(identity, state); - JsonElementConstraint? idConstraint = requirements.EvaluateIdConstraint?.Invoke(resourceType); if (idConstraint == JsonElementConstraint.Required) { - AssertHasIdOrLid(identity, requirements, state); + AssertHasIdOrLid(identity, requirements, allowLid, state); } else if (idConstraint == JsonElementConstraint.Forbidden) { @@ -128,7 +130,10 @@ private static void AssertHasNoLid(ResourceIdentity identity, RequestAdapterStat if (identity.Lid != null) { using IDisposable _ = state.Position.PushElement("lid"); - throw new ModelConversionException(state.Position, "The 'lid' element is not supported at this endpoint.", null); + + throw state.Request.Kind == EndpointKind.AtomicOperations + ? new ModelConversionException(state.Position, "The 'lid' element cannot be used because a client-generated ID is required.", null) + : new ModelConversionException(state.Position, "The 'lid' element is not supported at this endpoint.", null); } } @@ -140,7 +145,7 @@ private static void AssertNoIdWithLid(ResourceIdentity identity, RequestAdapterS } } - private static void AssertHasIdOrLid(ResourceIdentity identity, ResourceIdentityRequirements requirements, RequestAdapterState state) + private static void AssertHasIdOrLid(ResourceIdentity identity, ResourceIdentityRequirements requirements, bool allowLid, RequestAdapterState state) { string? message = null; @@ -154,7 +159,7 @@ private static void AssertHasIdOrLid(ResourceIdentity identity, ResourceIdentity } else if (identity.Id == null && identity.Lid == null) { - message = state.Request.Kind == EndpointKind.AtomicOperations ? "The 'id' or 'lid' element is required." : "The 'id' element is required."; + message = allowLid ? "The 'id' or 'lid' element is required." : "The 'id' element is required."; } if (message != null) diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityRequirements.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityRequirements.cs index 0d26b807d6..0168d2d5ea 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityRequirements.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityRequirements.cs @@ -21,6 +21,11 @@ public sealed class ResourceIdentityRequirements /// public Func? EvaluateIdConstraint { get; init; } + /// + /// When not null, provides a callback to indicate whether the "lid" element can be used instead of the "id" element. Defaults to false. + /// + public Func? EvaluateAllowLid { get; init; } + /// /// When not null, indicates what the value of the "id" element must be. /// @@ -50,4 +55,15 @@ public sealed class ResourceIdentityRequirements } : JsonElementConstraint.Required; } + + internal static bool DoEvaluateAllowLid(ResourceType resourceType, WriteOperationKind? writeOperation, ClientIdGenerationMode globalClientIdGeneration) + { + if (writeOperation == null) + { + return false; + } + + ClientIdGenerationMode clientIdGeneration = resourceType.ClientIdGeneration ?? globalClientIdGeneration; + return !(writeOperation == WriteOperationKind.CreateResource && clientIdGeneration == ClientIdGenerationMode.Required); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs index 62013322e5..e69cfb7d1a 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs @@ -175,7 +175,7 @@ public async Task Cannot_create_resource_for_missing_client_generated_ID() ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); - error.Title.Should().Be("Failed to deserialize request body: The 'id' or 'lid' element is required."); + error.Title.Should().Be("Failed to deserialize request body: The 'id' element is required."); error.Detail.Should().BeNull(); error.Source.ShouldNotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]/data"); @@ -281,6 +281,45 @@ public async Task Cannot_create_resource_for_incompatible_ID() error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); } + [Fact] + public async Task Cannot_create_resource_with_local_ID() + { + // Arrange + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "add", + data = new + { + type = "textLanguages", + lid = "new-server-id" + } + } + } + }; + + const string route = "/operations"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: The 'lid' element cannot be used because a client-generated ID is required."); + error.Detail.Should().BeNull(); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]/data/lid"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } + [Fact] public async Task Cannot_create_resource_for_ID_and_local_ID() { From b81f78077ba2a45257e32df8a92527de2a814abc Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 30 Jun 2024 14:22:55 +0200 Subject: [PATCH 89/91] Fixed: send absolute/relative URL in location header, depending on options.UseRelativeLinks --- .../Controllers/BaseJsonApiController.cs | 13 ++++++++++++- .../Links/AbsoluteLinksWithNamespaceTests.cs | 2 ++ .../Links/AbsoluteLinksWithoutNamespaceTests.cs | 2 ++ .../Links/RelativeLinksWithNamespaceTests.cs | 2 ++ .../Links/RelativeLinksWithoutNamespaceTests.cs | 2 ++ .../ReadWrite/Creating/CreateResourceTests.cs | 4 ++-- 6 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index fb3cd2bd2d..9cbc30dead 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -3,6 +3,8 @@ using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -207,7 +209,7 @@ public virtual async Task PostAsync([FromBody] TResource resource TResource? newResource = await _create.CreateAsync(resource, cancellationToken); string resourceId = (newResource ?? resource).StringId!; - string locationUrl = HttpContext.Request.Path.Add($"/{resourceId}"); + string locationUrl = GetLocationUrl(resourceId); if (newResource == null) { @@ -218,6 +220,15 @@ public virtual async Task PostAsync([FromBody] TResource resource return Created(locationUrl, newResource); } + private string GetLocationUrl(string resourceId) + { + PathString locationPath = HttpContext.Request.Path.Add($"/{resourceId}"); + + return _options.UseRelativeLinks + ? UriHelper.BuildRelative(HttpContext.Request.PathBase, locationPath) + : UriHelper.BuildAbsolute(HttpContext.Request.Scheme, HttpContext.Request.Host, HttpContext.Request.PathBase, locationPath); + } + /// /// Adds resources to a to-many relationship. Example: value.Links.Related.Should().Be($"{photoLink}/album"); }); }); + + httpResponse.Headers.Location.Should().Be(albumLink); } [Fact] diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Links/AbsoluteLinksWithoutNamespaceTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Links/AbsoluteLinksWithoutNamespaceTests.cs index 6ba9636128..6efcf034fa 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Links/AbsoluteLinksWithoutNamespaceTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Links/AbsoluteLinksWithoutNamespaceTests.cs @@ -394,6 +394,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => value.Links.Related.Should().Be($"{photoLink}/album"); }); }); + + httpResponse.Headers.Location.Should().Be(albumLink); } [Fact] diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Links/RelativeLinksWithNamespaceTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Links/RelativeLinksWithNamespaceTests.cs index 604f9c3e90..fbb642844f 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Links/RelativeLinksWithNamespaceTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Links/RelativeLinksWithNamespaceTests.cs @@ -394,6 +394,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => value.Links.Related.Should().Be($"{photoLink}/album"); }); }); + + httpResponse.Headers.Location.Should().Be(albumLink); } [Fact] diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Links/RelativeLinksWithoutNamespaceTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Links/RelativeLinksWithoutNamespaceTests.cs index 6ce1effd7c..f767b0cca6 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Links/RelativeLinksWithoutNamespaceTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Links/RelativeLinksWithoutNamespaceTests.cs @@ -394,6 +394,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => value.Links.Related.Should().Be($"{photoLink}/album"); }); }); + + httpResponse.Headers.Location.Should().Be(albumLink); } [Fact] diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs index 917f10be85..39af545d0c 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs @@ -58,7 +58,7 @@ public async Task Sets_location_header_for_created_resource() httpResponse.ShouldHaveStatusCode(HttpStatusCode.Created); string newWorkItemId = responseDocument.Data.SingleValue.ShouldNotBeNull().Id.ShouldNotBeNull(); - httpResponse.Headers.Location.Should().Be($"/workItems/{newWorkItemId}"); + httpResponse.Headers.Location.Should().Be($"http://localhost/workItems/{newWorkItemId}"); responseDocument.Links.ShouldNotBeNull(); responseDocument.Links.Self.Should().Be("http://localhost/workItems/"); @@ -66,7 +66,7 @@ public async Task Sets_location_header_for_created_resource() responseDocument.Data.SingleValue.ShouldNotBeNull(); responseDocument.Data.SingleValue.Links.ShouldNotBeNull(); - responseDocument.Data.SingleValue.Links.Self.Should().Be($"http://localhost{httpResponse.Headers.Location}"); + responseDocument.Data.SingleValue.Links.Self.Should().Be($"{httpResponse.Headers.Location}"); } [Fact] From 3897ebd5c1308642ebc289bf75330ce08b1b7e7d Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 30 Jun 2024 16:26:35 +0200 Subject: [PATCH 90/91] Add [DisallowNull] on TId in pipeline parameters --- .../Repositories/DapperRepository.cs | 7 ++-- .../Services/InMemoryResourceService.cs | 13 +++--- .../Processors/AddToRelationshipProcessor.cs | 2 +- .../Processors/DeleteProcessor.cs | 2 +- .../RemoveFromRelationshipProcessor.cs | 2 +- .../Processors/SetRelationshipProcessor.cs | 2 +- .../Processors/UpdateProcessor.cs | 2 +- .../Controllers/BaseJsonApiController.cs | 21 +++++----- .../Controllers/JsonApiController.cs | 21 ++++++---- .../Queries/IQueryLayerComposer.cs | 11 ++--- .../Queries/QueryLayerComposer.cs | 24 ++++++----- .../EntityFrameworkCoreRepository.cs | 6 +-- .../IResourceRepositoryAccessor.cs | 7 ++-- .../Repositories/IResourceWriteRepository.cs | 8 ++-- .../ResourceRepositoryAccessor.cs | 7 ++-- .../Services/IAddToRelationshipService.cs | 4 +- .../Services/IDeleteService.cs | 3 +- .../Services/IGetByIdService.cs | 3 +- .../Services/IGetRelationshipService.cs | 3 +- .../Services/IGetSecondaryService.cs | 3 +- .../IRemoveFromRelationshipService.cs | 4 +- .../Services/ISetRelationshipService.cs | 3 +- .../Services/IUpdateService.cs | 3 +- .../Services/JsonApiResourceService.cs | 42 +++++++++++-------- .../MultiTenantResourceService.cs | 9 ++-- .../SoftDeletionAwareResourceService.cs | 11 ++--- 26 files changed, 128 insertions(+), 95 deletions(-) diff --git a/src/Examples/DapperExample/Repositories/DapperRepository.cs b/src/Examples/DapperExample/Repositories/DapperRepository.cs index bbbeda2ea3..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; @@ -177,7 +178,7 @@ public async Task CountAsync(FilterExpression? filter, CancellationToken ca } /// - 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/NoEntityFrameworkExample/Services/InMemoryResourceService.cs b/src/Examples/NoEntityFrameworkExample/Services/InMemoryResourceService.cs index e9b37560fc..ee9d2196e9 100644 --- a/src/Examples/NoEntityFrameworkExample/Services/InMemoryResourceService.cs +++ b/src/Examples/NoEntityFrameworkExample/Services/InMemoryResourceService.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Diagnostics.CodeAnalysis; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Queries; @@ -114,7 +115,7 @@ private bool SetPrimaryTotalCountIsZero() } /// - public Task GetAsync(TId id, CancellationToken cancellationToken) + public Task GetAsync([DisallowNull] TId id, CancellationToken cancellationToken) { QueryLayer queryLayer = _queryLayerComposer.ComposeForGetById(id, _resourceType, TopFieldSelection.PreserveExisting); @@ -124,14 +125,14 @@ public Task GetAsync(TId id, CancellationToken cancellationToken) if (resource == null) { - throw new ResourceNotFoundException(id!.ToString()!, _resourceType.PublicName); + throw new ResourceNotFoundException(id.ToString()!, _resourceType.PublicName); } return Task.FromResult(resource); } /// - public Task GetSecondaryAsync(TId id, string relationshipName, CancellationToken cancellationToken) + public Task GetSecondaryAsync([DisallowNull] TId id, string relationshipName, CancellationToken cancellationToken) { RelationshipAttribute? relationship = _resourceType.FindRelationshipByPublicName(relationshipName); @@ -151,7 +152,7 @@ public Task GetAsync(TId id, CancellationToken cancellationToken) if (primaryResource == null) { - throw new ResourceNotFoundException(id!.ToString()!, _resourceType.PublicName); + throw new ResourceNotFoundException(id.ToString()!, _resourceType.PublicName); } object? rightValue = relationship.GetValue(primaryResource); @@ -164,7 +165,7 @@ public Task GetAsync(TId id, CancellationToken cancellationToken) return Task.FromResult(rightValue); } - private void SetNonPrimaryTotalCount(TId id, RelationshipAttribute relationship) + private void SetNonPrimaryTotalCount([DisallowNull] TId id, RelationshipAttribute relationship) { if (_options.IncludeTotalResourceCount && relationship is HasManyAttribute hasManyRelationship) { @@ -188,7 +189,7 @@ private void SetNonPrimaryTotalCount(TId id, RelationshipAttribute relationship) } /// - public Task GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken) + public Task GetRelationshipAsync([DisallowNull] TId id, string relationshipName, CancellationToken cancellationToken) { return GetSecondaryAsync(id, relationshipName, cancellationToken); } diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs index 5e63a2d276..fa835910ef 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs @@ -26,7 +26,7 @@ public AddToRelationshipProcessor(IAddToRelationshipService serv var leftId = (TId)operation.Resource.GetTypedId(); ISet rightResourceIds = operation.GetSecondaryResources(); - await _service.AddToToManyRelationshipAsync(leftId, operation.Request.Relationship!.PublicName, rightResourceIds, cancellationToken); + await _service.AddToToManyRelationshipAsync(leftId!, operation.Request.Relationship!.PublicName, rightResourceIds, cancellationToken); return null; } diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs index 6652d4ddec..d758e065bc 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs @@ -24,7 +24,7 @@ public DeleteProcessor(IDeleteService service) ArgumentGuard.NotNull(operation); var id = (TId)operation.Resource.GetTypedId(); - await _service.DeleteAsync(id, cancellationToken); + await _service.DeleteAsync(id!, cancellationToken); return null; } diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs index 5fae04f710..6222a01fd8 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs @@ -26,7 +26,7 @@ public RemoveFromRelationshipProcessor(IRemoveFromRelationshipService rightResourceIds = operation.GetSecondaryResources(); - await _service.RemoveFromToManyRelationshipAsync(leftId, operation.Request.Relationship!.PublicName, rightResourceIds, cancellationToken); + await _service.RemoveFromToManyRelationshipAsync(leftId!, operation.Request.Relationship!.PublicName, rightResourceIds, cancellationToken); return null; } diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs index 2de4229aa7..3eeaa77fb3 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs @@ -28,7 +28,7 @@ public SetRelationshipProcessor(ISetRelationshipService service) var leftId = (TId)operation.Resource.GetTypedId(); object? rightValue = GetRelationshipRightValue(operation); - await _service.SetRelationshipAsync(leftId, operation.Request.Relationship!.PublicName, rightValue, cancellationToken); + await _service.SetRelationshipAsync(leftId!, operation.Request.Relationship!.PublicName, rightValue, cancellationToken); return null; } diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs index b221951fd2..32fe2a1eb5 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs @@ -24,7 +24,7 @@ public UpdateProcessor(IUpdateService service) ArgumentGuard.NotNull(operation); var resource = (TResource)operation.Resource; - TResource? updated = await _service.UpdateAsync(resource.Id, resource, cancellationToken); + TResource? updated = await _service.UpdateAsync(resource.Id!, resource, cancellationToken); return updated == null ? null : operation.WithResource(updated); } diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index 9cbc30dead..bef962a971 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Middleware; @@ -108,7 +109,7 @@ public virtual async Task GetAsync(CancellationToken cancellation /// GET /articles/1 HTTP/1.1 /// ]]> /// - public virtual async Task GetAsync(TId id, CancellationToken cancellationToken) + public virtual async Task GetAsync([DisallowNull] TId id, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { @@ -133,7 +134,7 @@ public virtual async Task GetAsync(TId id, CancellationToken canc /// GET /articles/1/revisions HTTP/1.1 /// ]]> /// - public virtual async Task GetSecondaryAsync(TId id, string relationshipName, CancellationToken cancellationToken) + public virtual async Task GetSecondaryAsync([DisallowNull] TId id, string relationshipName, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { @@ -162,7 +163,7 @@ public virtual async Task GetSecondaryAsync(TId id, string relati /// GET /articles/1/relationships/revisions HTTP/1.1 /// ]]> /// - public virtual async Task GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken) + public virtual async Task GetRelationshipAsync([DisallowNull] TId id, string relationshipName, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { @@ -246,8 +247,8 @@ private string GetLocationUrl(string resourceId) /// /// Propagates notification that request handling should be canceled. /// - public virtual async Task PostRelationshipAsync(TId id, string relationshipName, [FromBody] ISet rightResourceIds, - CancellationToken cancellationToken) + public virtual async Task PostRelationshipAsync([DisallowNull] TId id, string relationshipName, + [FromBody] ISet rightResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { @@ -275,7 +276,7 @@ public virtual async Task PostRelationshipAsync(TId id, string re /// PATCH /articles/1 HTTP/1.1 /// ]]> /// - public virtual async Task PatchAsync(TId id, [FromBody] TResource resource, CancellationToken cancellationToken) + public virtual async Task PatchAsync([DisallowNull] TId id, [FromBody] TResource resource, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { @@ -321,7 +322,7 @@ public virtual async Task PatchAsync(TId id, [FromBody] TResource /// /// Propagates notification that request handling should be canceled. /// - public virtual async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object? rightValue, + public virtual async Task PatchRelationshipAsync([DisallowNull] TId id, string relationshipName, [FromBody] object? rightValue, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new @@ -348,7 +349,7 @@ public virtual async Task PatchRelationshipAsync(TId id, string r /// DELETE /articles/1 HTTP/1.1 /// ]]> /// - public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToken) + public virtual async Task DeleteAsync([DisallowNull] TId id, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { @@ -382,8 +383,8 @@ public virtual async Task DeleteAsync(TId id, CancellationToken c /// /// Propagates notification that request handling should be canceled. /// - public virtual async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet rightResourceIds, - CancellationToken cancellationToken) + public virtual async Task DeleteRelationshipAsync([DisallowNull] TId id, string relationshipName, + [FromBody] ISet rightResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index 127b37ca84..e5be3cbc71 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Services; @@ -50,7 +51,7 @@ public override async Task GetAsync(CancellationToken cancellatio /// [HttpGet("{id}")] [HttpHead("{id}")] - public override async Task GetAsync([Required] TId id, CancellationToken cancellationToken) + public override async Task GetAsync([Required] [DisallowNull] TId id, CancellationToken cancellationToken) { return await base.GetAsync(id, cancellationToken); } @@ -58,7 +59,8 @@ public override async Task GetAsync([Required] TId id, Cancellati /// [HttpGet("{id}/{relationshipName}")] [HttpHead("{id}/{relationshipName}")] - public override async Task GetSecondaryAsync([Required] TId id, [Required] string relationshipName, CancellationToken cancellationToken) + public override async Task GetSecondaryAsync([Required] [DisallowNull] TId id, [Required] string relationshipName, + CancellationToken cancellationToken) { return await base.GetSecondaryAsync(id, relationshipName, cancellationToken); } @@ -66,7 +68,8 @@ public override async Task GetSecondaryAsync([Required] TId id, [ /// [HttpGet("{id}/relationships/{relationshipName}")] [HttpHead("{id}/relationships/{relationshipName}")] - public override async Task GetRelationshipAsync([Required] TId id, [Required] string relationshipName, CancellationToken cancellationToken) + public override async Task GetRelationshipAsync([Required] [DisallowNull] TId id, [Required] string relationshipName, + CancellationToken cancellationToken) { return await base.GetRelationshipAsync(id, relationshipName, cancellationToken); } @@ -80,7 +83,7 @@ public override async Task PostAsync([Required] TResource resourc /// [HttpPost("{id}/relationships/{relationshipName}")] - public override async Task PostRelationshipAsync([Required] TId id, [Required] string relationshipName, + public override async Task PostRelationshipAsync([Required] [DisallowNull] TId id, [Required] string relationshipName, [Required] ISet rightResourceIds, CancellationToken cancellationToken) { return await base.PostRelationshipAsync(id, relationshipName, rightResourceIds, cancellationToken); @@ -88,7 +91,7 @@ public override async Task PostRelationshipAsync([Required] TId i /// [HttpPatch("{id}")] - public override async Task PatchAsync([Required] TId id, [Required] TResource resource, CancellationToken cancellationToken) + public override async Task PatchAsync([Required] [DisallowNull] TId id, [Required] TResource resource, CancellationToken cancellationToken) { return await base.PatchAsync(id, resource, cancellationToken); } @@ -96,22 +99,22 @@ public override async Task PatchAsync([Required] TId id, [Require /// [HttpPatch("{id}/relationships/{relationshipName}")] // Parameter `[Required] object? rightValue` makes Swashbuckle generate the OpenAPI request body as required. We don't actually validate ModelState, so it doesn't hurt. - public override async Task PatchRelationshipAsync([Required] TId id, [Required] string relationshipName, [Required] object? rightValue, - CancellationToken cancellationToken) + public override async Task PatchRelationshipAsync([Required] [DisallowNull] TId id, [Required] string relationshipName, + [Required] object? rightValue, CancellationToken cancellationToken) { return await base.PatchRelationshipAsync(id, relationshipName, rightValue, cancellationToken); } /// [HttpDelete("{id}")] - public override async Task DeleteAsync([Required] TId id, CancellationToken cancellationToken) + public override async Task DeleteAsync([Required] [DisallowNull] TId id, CancellationToken cancellationToken) { return await base.DeleteAsync(id, cancellationToken); } /// [HttpDelete("{id}/relationships/{relationshipName}")] - public override async Task DeleteRelationshipAsync([Required] TId id, [Required] string relationshipName, + public override async Task DeleteRelationshipAsync([Required] [DisallowNull] TId id, [Required] string relationshipName, [Required] ISet rightResourceIds, CancellationToken cancellationToken) { return await base.DeleteRelationshipAsync(id, relationshipName, rightResourceIds, cancellationToken); diff --git a/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs index a9d99c3b13..8b14590e83 100644 --- a/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Queries.Expressions; using JsonApiDotNetCore.Resources; @@ -18,7 +19,7 @@ public interface IQueryLayerComposer /// /// Builds a filter from constraints, used to determine total resource count on a secondary collection endpoint. /// - FilterExpression? GetSecondaryFilterFromConstraints(TId primaryId, HasManyAttribute hasManyRelationship); + FilterExpression? GetSecondaryFilterFromConstraints([DisallowNull] TId primaryId, HasManyAttribute hasManyRelationship); /// /// Collects constraints and builds a out of them, used to retrieve the actual resources. @@ -28,7 +29,7 @@ public interface IQueryLayerComposer /// /// Collects constraints and builds a out of them, used to retrieve one resource. /// - QueryLayer ComposeForGetById(TId id, ResourceType primaryResourceType, TopFieldSelection fieldSelection); + QueryLayer ComposeForGetById([DisallowNull] TId id, ResourceType primaryResourceType, TopFieldSelection fieldSelection); /// /// Collects constraints and builds the secondary layer for a relationship endpoint. @@ -38,14 +39,14 @@ public interface IQueryLayerComposer /// /// Wraps a layer for a secondary endpoint into a primary layer, rewriting top-level includes. /// - QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, ResourceType primaryResourceType, TId primaryId, + QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, ResourceType primaryResourceType, [DisallowNull] TId primaryId, RelationshipAttribute relationship); /// /// Builds a query that retrieves the primary resource, including all of its attributes and all targeted relationships, during a create/update/delete /// request. /// - QueryLayer ComposeForUpdate(TId id, ResourceType primaryResourceType); + QueryLayer ComposeForUpdate([DisallowNull] TId id, ResourceType primaryResourceType); /// /// Builds a query for each targeted relationship with a filter to match on its right resource IDs. @@ -60,5 +61,5 @@ QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, Resourc /// /// Builds a query for a to-many relationship with a filter to match on its left and right resource IDs. /// - QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, TId leftId, ICollection rightResourceIds); + QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, [DisallowNull] TId leftId, ICollection rightResourceIds); } diff --git a/src/JsonApiDotNetCore/Queries/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/QueryLayerComposer.cs index 8e0c97e6c0..1c4ca77119 100644 --- a/src/JsonApiDotNetCore/Queries/QueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/QueryLayerComposer.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Diagnostics; @@ -63,7 +64,7 @@ public QueryLayerComposer(IEnumerable constraintProvid } /// - public FilterExpression? GetSecondaryFilterFromConstraints(TId primaryId, HasManyAttribute hasManyRelationship) + public FilterExpression? GetSecondaryFilterFromConstraints([DisallowNull] TId primaryId, HasManyAttribute hasManyRelationship) { ArgumentGuard.NotNull(hasManyRelationship); @@ -102,26 +103,29 @@ public QueryLayerComposer(IEnumerable constraintProvid return LogicalExpression.Compose(LogicalOperator.And, inverseFilter, primaryFilter, secondaryFilter); } - private static FilterExpression GetInverseRelationshipFilter(TId primaryId, HasManyAttribute relationship, RelationshipAttribute inverseRelationship) + private static FilterExpression GetInverseRelationshipFilter([DisallowNull] TId primaryId, HasManyAttribute relationship, + RelationshipAttribute inverseRelationship) { return inverseRelationship is HasManyAttribute hasManyInverseRelationship ? GetInverseHasManyRelationshipFilter(primaryId, relationship, hasManyInverseRelationship) : GetInverseHasOneRelationshipFilter(primaryId, relationship, (HasOneAttribute)inverseRelationship); } - private static FilterExpression GetInverseHasOneRelationshipFilter(TId primaryId, HasManyAttribute relationship, HasOneAttribute inverseRelationship) + private static FilterExpression GetInverseHasOneRelationshipFilter([DisallowNull] TId primaryId, HasManyAttribute relationship, + HasOneAttribute inverseRelationship) { AttrAttribute idAttribute = GetIdAttribute(relationship.LeftType); var idChain = new ResourceFieldChainExpression(ImmutableArray.Create(inverseRelationship, idAttribute)); - return new ComparisonExpression(ComparisonOperator.Equals, idChain, new LiteralConstantExpression(primaryId!)); + return new ComparisonExpression(ComparisonOperator.Equals, idChain, new LiteralConstantExpression(primaryId)); } - private static FilterExpression GetInverseHasManyRelationshipFilter(TId primaryId, HasManyAttribute relationship, HasManyAttribute inverseRelationship) + private static FilterExpression GetInverseHasManyRelationshipFilter([DisallowNull] TId primaryId, HasManyAttribute relationship, + HasManyAttribute inverseRelationship) { AttrAttribute idAttribute = GetIdAttribute(relationship.LeftType); var idChain = new ResourceFieldChainExpression(ImmutableArray.Create(idAttribute)); - var idComparison = new ComparisonExpression(ComparisonOperator.Equals, idChain, new LiteralConstantExpression(primaryId!)); + var idComparison = new ComparisonExpression(ComparisonOperator.Equals, idChain, new LiteralConstantExpression(primaryId)); return new HasExpression(new ResourceFieldChainExpression(inverseRelationship), idComparison); } @@ -263,7 +267,7 @@ private static IImmutableSet ApplyIncludeElementUpdate } /// - public QueryLayer ComposeForGetById(TId id, ResourceType primaryResourceType, TopFieldSelection fieldSelection) + public QueryLayer ComposeForGetById([DisallowNull] TId id, ResourceType primaryResourceType, TopFieldSelection fieldSelection) { ArgumentGuard.NotNull(primaryResourceType); @@ -314,7 +318,7 @@ private FieldSelection GetSelectionForRelationship(ResourceType secondaryResourc } /// - public QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, ResourceType primaryResourceType, TId primaryId, + public QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, ResourceType primaryResourceType, [DisallowNull] TId primaryId, RelationshipAttribute relationship) { ArgumentGuard.NotNull(secondaryLayer); @@ -377,7 +381,7 @@ private IncludeExpression RewriteIncludeForSecondaryEndpoint(IncludeExpression? } /// - public QueryLayer ComposeForUpdate(TId id, ResourceType primaryResourceType) + public QueryLayer ComposeForUpdate([DisallowNull] TId id, ResourceType primaryResourceType) { ArgumentGuard.NotNull(primaryResourceType); @@ -440,7 +444,7 @@ public QueryLayer ComposeForGetRelationshipRightIds(RelationshipAttribute relati } /// - public QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, TId leftId, ICollection rightResourceIds) + public QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, [DisallowNull] TId leftId, ICollection rightResourceIds) { ArgumentGuard.NotNull(hasManyRelationship); ArgumentGuard.NotNull(rightResourceIds); diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index a5f083932b..72eeff5c27 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -179,7 +179,7 @@ protected virtual IQueryable GetAll() } /// - public virtual Task GetForCreateAsync(Type resourceClrType, TId id, CancellationToken cancellationToken) + public virtual Task GetForCreateAsync(Type resourceClrType, [DisallowNull] TId id, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { @@ -332,7 +332,7 @@ protected void AssertIsNotClearingRequiredToOneRelationship(RelationshipAttribut } /// - public virtual async Task DeleteAsync(TResource? resourceFromDatabase, TId id, CancellationToken cancellationToken) + public virtual async Task DeleteAsync(TResource? resourceFromDatabase, [DisallowNull] TId id, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { @@ -432,7 +432,7 @@ public virtual async Task SetRelationshipAsync(TResource leftResource, object? r } /// - public virtual async Task AddToToManyRelationshipAsync(TResource? leftResource, TId leftId, ISet rightResourceIds, + public virtual async Task AddToToManyRelationshipAsync(TResource? leftResource, [DisallowNull] TId leftId, ISet rightResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new diff --git a/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs b/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs index 149fef1cfb..a58542c239 100644 --- a/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Queries; using JsonApiDotNetCore.Queries.Expressions; @@ -29,7 +30,7 @@ Task> GetAsync(QueryLayer queryLayer, /// /// Invokes for the specified resource type. /// - Task GetForCreateAsync(Type resourceClrType, TId id, CancellationToken cancellationToken) + Task GetForCreateAsync(Type resourceClrType, [DisallowNull] TId id, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// @@ -53,7 +54,7 @@ Task UpdateAsync(TResource resourceFromRequest, TResource resourceFro /// /// Invokes for the specified resource type. /// - Task DeleteAsync(TResource? resourceFromDatabase, TId id, CancellationToken cancellationToken) + Task DeleteAsync(TResource? resourceFromDatabase, [DisallowNull] TId id, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// @@ -65,7 +66,7 @@ Task SetRelationshipAsync(TResource leftResource, object? rightValue, /// /// Invokes for the specified resource type. /// - Task AddToToManyRelationshipAsync(TResource? leftResource, TId leftId, ISet rightResourceIds, + Task AddToToManyRelationshipAsync(TResource? leftResource, [DisallowNull] TId leftId, ISet rightResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable; diff --git a/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs b/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs index fb0267d18a..49d2c60d73 100644 --- a/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using JsonApiDotNetCore.Queries; using JsonApiDotNetCore.Resources; @@ -23,7 +24,7 @@ public interface IResourceWriteRepository /// /// This method can be overridden to assign resource-specific required relationships. /// - Task GetForCreateAsync(Type resourceClrType, TId id, CancellationToken cancellationToken); + Task GetForCreateAsync(Type resourceClrType, [DisallowNull] TId id, CancellationToken cancellationToken); /// /// Creates a new resource in the underlying data store. @@ -43,7 +44,7 @@ public interface IResourceWriteRepository /// /// Deletes an existing resource from the underlying data store. /// - Task DeleteAsync(TResource? resourceFromDatabase, TId id, CancellationToken cancellationToken); + Task DeleteAsync(TResource? resourceFromDatabase, [DisallowNull] TId id, CancellationToken cancellationToken); /// /// Performs a complete replacement of the relationship in the underlying data store. @@ -53,7 +54,8 @@ public interface IResourceWriteRepository /// /// Adds resources to a to-many relationship in the underlying data store. /// - Task AddToToManyRelationshipAsync(TResource? leftResource, TId leftId, ISet rightResourceIds, CancellationToken cancellationToken); + Task AddToToManyRelationshipAsync(TResource? leftResource, [DisallowNull] TId leftId, ISet rightResourceIds, + CancellationToken cancellationToken); /// /// Removes resources from a to-many relationship in the underlying data store. diff --git a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs index 6370ddb883..6f19f51c1d 100644 --- a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Errors; @@ -53,7 +54,7 @@ public async Task CountAsync(ResourceType resourceType, FilterExpression? f } /// - public async Task GetForCreateAsync(Type resourceClrType, TId id, CancellationToken cancellationToken) + public async Task GetForCreateAsync(Type resourceClrType, [DisallowNull] TId id, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); @@ -85,7 +86,7 @@ public async Task UpdateAsync(TResource resourceFromRequest, TResourc } /// - public async Task DeleteAsync(TResource? resourceFromDatabase, TId id, CancellationToken cancellationToken) + public async Task DeleteAsync(TResource? resourceFromDatabase, [DisallowNull] TId id, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); @@ -101,7 +102,7 @@ public async Task SetRelationshipAsync(TResource leftResource, object } /// - public async Task AddToToManyRelationshipAsync(TResource? leftResource, TId leftId, ISet rightResourceIds, + public async Task AddToToManyRelationshipAsync(TResource? leftResource, [DisallowNull] TId leftId, ISet rightResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable { diff --git a/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs b/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs index 58fb122a50..07c9234513 100644 --- a/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using JsonApiDotNetCore.Resources; @@ -25,5 +26,6 @@ public interface IAddToRelationshipService /// /// Propagates notification that request handling should be canceled. /// - Task AddToToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken); + Task AddToToManyRelationshipAsync([DisallowNull] TId leftId, string relationshipName, ISet rightResourceIds, + CancellationToken cancellationToken); } diff --git a/src/JsonApiDotNetCore/Services/IDeleteService.cs b/src/JsonApiDotNetCore/Services/IDeleteService.cs index 9bdfcd143b..375181e529 100644 --- a/src/JsonApiDotNetCore/Services/IDeleteService.cs +++ b/src/JsonApiDotNetCore/Services/IDeleteService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JsonApiDotNetCore.Resources; // ReSharper disable UnusedTypeParameter @@ -11,5 +12,5 @@ public interface IDeleteService /// /// Handles a JSON:API request to delete an existing resource. /// - Task DeleteAsync(TId id, CancellationToken cancellationToken); + Task DeleteAsync([DisallowNull] TId id, CancellationToken cancellationToken); } diff --git a/src/JsonApiDotNetCore/Services/IGetByIdService.cs b/src/JsonApiDotNetCore/Services/IGetByIdService.cs index 4bf34788eb..fc95e0af1e 100644 --- a/src/JsonApiDotNetCore/Services/IGetByIdService.cs +++ b/src/JsonApiDotNetCore/Services/IGetByIdService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JsonApiDotNetCore.Resources; namespace JsonApiDotNetCore.Services; @@ -9,5 +10,5 @@ public interface IGetByIdService /// /// Handles a JSON:API request to retrieve a single resource for a primary endpoint. /// - Task GetAsync(TId id, CancellationToken cancellationToken); + Task GetAsync([DisallowNull] TId id, CancellationToken cancellationToken); } diff --git a/src/JsonApiDotNetCore/Services/IGetRelationshipService.cs b/src/JsonApiDotNetCore/Services/IGetRelationshipService.cs index afd284a7ce..34b9880bea 100644 --- a/src/JsonApiDotNetCore/Services/IGetRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/IGetRelationshipService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JsonApiDotNetCore.Resources; // ReSharper disable UnusedTypeParameter @@ -11,5 +12,5 @@ public interface IGetRelationshipService /// /// Handles a JSON:API request to retrieve a single relationship. /// - Task GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken); + Task GetRelationshipAsync([DisallowNull] TId id, string relationshipName, CancellationToken cancellationToken); } diff --git a/src/JsonApiDotNetCore/Services/IGetSecondaryService.cs b/src/JsonApiDotNetCore/Services/IGetSecondaryService.cs index 9f8c528552..33d47db454 100644 --- a/src/JsonApiDotNetCore/Services/IGetSecondaryService.cs +++ b/src/JsonApiDotNetCore/Services/IGetSecondaryService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JsonApiDotNetCore.Resources; // ReSharper disable UnusedTypeParameter @@ -12,5 +13,5 @@ public interface IGetSecondaryService /// Handles a JSON:API request to retrieve a single resource or a collection of resources for a secondary endpoint, such as /articles/1/author or /// /articles/1/revisions. /// - Task GetSecondaryAsync(TId id, string relationshipName, CancellationToken cancellationToken); + Task GetSecondaryAsync([DisallowNull] TId id, string relationshipName, CancellationToken cancellationToken); } diff --git a/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs b/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs index cb572801bb..d3844610c7 100644 --- a/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JsonApiDotNetCore.Resources; // ReSharper disable UnusedTypeParameter @@ -23,5 +24,6 @@ public interface IRemoveFromRelationshipService /// /// Propagates notification that request handling should be canceled. /// - Task RemoveFromToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken); + Task RemoveFromToManyRelationshipAsync([DisallowNull] TId leftId, string relationshipName, ISet rightResourceIds, + CancellationToken cancellationToken); } diff --git a/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs b/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs index 3050394beb..05e8c8c606 100644 --- a/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JsonApiDotNetCore.Resources; // ReSharper disable UnusedTypeParameter @@ -23,5 +24,5 @@ public interface ISetRelationshipService /// /// Propagates notification that request handling should be canceled. /// - Task SetRelationshipAsync(TId leftId, string relationshipName, object? rightValue, CancellationToken cancellationToken); + Task SetRelationshipAsync([DisallowNull] TId leftId, string relationshipName, object? rightValue, CancellationToken cancellationToken); } diff --git a/src/JsonApiDotNetCore/Services/IUpdateService.cs b/src/JsonApiDotNetCore/Services/IUpdateService.cs index f742a1fc2e..b8349c3909 100644 --- a/src/JsonApiDotNetCore/Services/IUpdateService.cs +++ b/src/JsonApiDotNetCore/Services/IUpdateService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JsonApiDotNetCore.Resources; namespace JsonApiDotNetCore.Services; @@ -10,5 +11,5 @@ public interface IUpdateService /// Handles a JSON:API request to update the attributes and/or relationships of an existing resource. Only the values of sent attributes are replaced. /// And only the values of sent relationships are replaced. /// - Task UpdateAsync(TId id, TResource resource, CancellationToken cancellationToken); + Task UpdateAsync([DisallowNull] TId id, TResource resource, CancellationToken cancellationToken); } diff --git a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs index 5984b6215b..478d38ab72 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -85,7 +86,7 @@ public virtual async Task> GetAsync(CancellationT } /// - public virtual async Task GetAsync(TId id, CancellationToken cancellationToken) + public virtual async Task GetAsync([DisallowNull] TId id, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { @@ -98,7 +99,7 @@ public virtual async Task GetAsync(TId id, CancellationToken cancella } /// - public virtual async Task GetSecondaryAsync(TId id, string relationshipName, CancellationToken cancellationToken) + public virtual async Task GetSecondaryAsync([DisallowNull] TId id, string relationshipName, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { @@ -137,7 +138,7 @@ public virtual async Task GetAsync(TId id, CancellationToken cancella } /// - public virtual async Task GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken) + public virtual async Task GetRelationshipAsync([DisallowNull] TId id, string relationshipName, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { @@ -177,7 +178,8 @@ public virtual async Task GetAsync(TId id, CancellationToken cancella return rightValue; } - private async Task RetrieveResourceCountForNonPrimaryEndpointAsync(TId id, HasManyAttribute relationship, CancellationToken cancellationToken) + private async Task RetrieveResourceCountForNonPrimaryEndpointAsync([DisallowNull] TId id, HasManyAttribute relationship, + CancellationToken cancellationToken) { FilterExpression? secondaryFilter = _queryLayerComposer.GetSecondaryFilterFromConstraints(id, relationship); @@ -205,7 +207,10 @@ private async Task RetrieveResourceCountForNonPrimaryEndpointAsync(TId id, HasMa await AccurizeResourceTypesInHierarchyToAssignInRelationshipsAsync(resourceFromRequest, cancellationToken); Type resourceClrType = resourceFromRequest.GetClrType(); - TResource resourceForDatabase = await _repositoryAccessor.GetForCreateAsync(resourceClrType, resourceFromRequest.Id, cancellationToken); + + TResource resourceForDatabase = + await _repositoryAccessor.GetForCreateAsync(resourceClrType, resourceFromRequest.Id!, cancellationToken); + AccurizeJsonApiRequest(resourceForDatabase); _resourceChangeTracker.SetInitiallyStoredAttributeValues(resourceForDatabase); @@ -223,7 +228,7 @@ private async Task RetrieveResourceCountForNonPrimaryEndpointAsync(TId id, HasMa throw; } - TResource resourceFromDatabase = await GetPrimaryResourceByIdAsync(resourceForDatabase.Id, TopFieldSelection.WithAllAttributes, cancellationToken); + TResource resourceFromDatabase = await GetPrimaryResourceByIdAsync(resourceForDatabase.Id!, TopFieldSelection.WithAllAttributes, cancellationToken); _resourceChangeTracker.SetFinallyStoredAttributeValues(resourceFromDatabase); @@ -235,7 +240,7 @@ protected async Task AssertPrimaryResourceDoesNotExistAsync(TResource resource, { if (!Equals(resource.Id, default(TId))) { - TResource? existingResource = await GetPrimaryResourceByIdOrDefaultAsync(resource.Id, TopFieldSelection.OnlyIdAttribute, cancellationToken); + TResource? existingResource = await GetPrimaryResourceByIdOrDefaultAsync(resource.Id!, TopFieldSelection.OnlyIdAttribute, cancellationToken); if (existingResource != null) { @@ -329,7 +334,7 @@ private async IAsyncEnumerable GetMissingRightRes } /// - public virtual async Task AddToToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, + public virtual async Task AddToToManyRelationshipAsync([DisallowNull] TId leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new @@ -385,7 +390,7 @@ public virtual async Task AddToToManyRelationshipAsync(TId leftId, string relati } } - private async Task RemoveExistingIdsFromRelationshipRightSideAsync(HasManyAttribute hasManyRelationship, TId leftId, + private async Task RemoveExistingIdsFromRelationshipRightSideAsync(HasManyAttribute hasManyRelationship, [DisallowNull] TId leftId, ISet rightResourceIds, CancellationToken cancellationToken) { TResource leftResource = await GetForHasManyUpdateAsync(hasManyRelationship, leftId, rightResourceIds, cancellationToken); @@ -398,8 +403,8 @@ private async Task RemoveExistingIdsFromRelationshipRightSideAsync(Ha return leftResource; } - private async Task GetForHasManyUpdateAsync(HasManyAttribute hasManyRelationship, TId leftId, ISet rightResourceIds, - CancellationToken cancellationToken) + private async Task GetForHasManyUpdateAsync(HasManyAttribute hasManyRelationship, [DisallowNull] TId leftId, + ISet rightResourceIds, CancellationToken cancellationToken) { QueryLayer queryLayer = _queryLayerComposer.ComposeForHasMany(hasManyRelationship, leftId, rightResourceIds); var leftResource = await _repositoryAccessor.GetForUpdateAsync(queryLayer, cancellationToken); @@ -438,7 +443,7 @@ private async Task GetForHasManyUpdateAsync(HasManyAttribute hasManyR } /// - public virtual async Task UpdateAsync(TId id, TResource resource, CancellationToken cancellationToken) + public virtual async Task UpdateAsync([DisallowNull] TId id, TResource resource, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { @@ -481,7 +486,7 @@ private async Task GetForHasManyUpdateAsync(HasManyAttribute hasManyR } /// - public virtual async Task SetRelationshipAsync(TId leftId, string relationshipName, object? rightValue, CancellationToken cancellationToken) + public virtual async Task SetRelationshipAsync([DisallowNull] TId leftId, string relationshipName, object? rightValue, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { @@ -519,7 +524,7 @@ public virtual async Task SetRelationshipAsync(TId leftId, string relationshipNa } /// - public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToken) + public virtual async Task DeleteAsync([DisallowNull] TId id, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { @@ -552,7 +557,7 @@ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToke } /// - public virtual async Task RemoveFromToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, + public virtual async Task RemoveFromToManyRelationshipAsync([DisallowNull] TId leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new @@ -581,7 +586,7 @@ public virtual async Task RemoveFromToManyRelationshipAsync(TId leftId, string r await _repositoryAccessor.RemoveFromToManyRelationshipAsync(resourceFromDatabase, effectiveRightResourceIds, cancellationToken); } - protected async Task GetPrimaryResourceByIdAsync(TId id, TopFieldSelection fieldSelection, CancellationToken cancellationToken) + protected async Task GetPrimaryResourceByIdAsync([DisallowNull] TId id, TopFieldSelection fieldSelection, CancellationToken cancellationToken) { TResource? primaryResource = await GetPrimaryResourceByIdOrDefaultAsync(id, fieldSelection, cancellationToken); AssertPrimaryResourceExists(primaryResource); @@ -589,7 +594,8 @@ protected async Task GetPrimaryResourceByIdAsync(TId id, TopFieldSele return primaryResource; } - private async Task GetPrimaryResourceByIdOrDefaultAsync(TId id, TopFieldSelection fieldSelection, CancellationToken cancellationToken) + private async Task GetPrimaryResourceByIdOrDefaultAsync([DisallowNull] TId id, TopFieldSelection fieldSelection, + CancellationToken cancellationToken) { AssertPrimaryResourceTypeInJsonApiRequestIsNotNull(_request.PrimaryResourceType); @@ -599,7 +605,7 @@ protected async Task GetPrimaryResourceByIdAsync(TId id, TopFieldSele return primaryResources.SingleOrDefault(); } - protected async Task GetPrimaryResourceForUpdateAsync(TId id, CancellationToken cancellationToken) + protected async Task GetPrimaryResourceForUpdateAsync([DisallowNull] TId id, CancellationToken cancellationToken) { AssertPrimaryResourceTypeInJsonApiRequestIsNotNull(_request.PrimaryResourceType); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs index 9918cbaff3..89a2d497dd 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Middleware; @@ -45,21 +46,21 @@ protected override async Task InitializeResourceAsync(TResource resourceForDatab return await base.CreateAsync(resource, cancellationToken); } - public override async Task UpdateAsync(TId id, TResource resource, CancellationToken cancellationToken) + public override async Task UpdateAsync([DisallowNull] TId id, TResource resource, CancellationToken cancellationToken) { await AssertResourcesToAssignInRelationshipsExistAsync(resource, cancellationToken); return await base.UpdateAsync(id, resource, cancellationToken); } - public override async Task SetRelationshipAsync(TId leftId, string relationshipName, object? rightValue, CancellationToken cancellationToken) + public override async Task SetRelationshipAsync([DisallowNull] TId leftId, string relationshipName, object? rightValue, CancellationToken cancellationToken) { await AssertRightResourcesExistAsync(rightValue, cancellationToken); await base.SetRelationshipAsync(leftId, relationshipName, rightValue, cancellationToken); } - public override async Task AddToToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, + public override async Task AddToToManyRelationshipAsync([DisallowNull] TId leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { _ = await GetPrimaryResourceByIdAsync(leftId, TopFieldSelection.OnlyIdAttribute, cancellationToken); @@ -68,7 +69,7 @@ public override async Task AddToToManyRelationshipAsync(TId leftId, string relat await base.AddToToManyRelationshipAsync(leftId, relationshipName, rightResourceIds, cancellationToken); } - public override async Task DeleteAsync(TId id, CancellationToken cancellationToken) + public override async Task DeleteAsync([DisallowNull] TId id, CancellationToken cancellationToken) { _ = await GetPrimaryResourceByIdAsync(id, TopFieldSelection.OnlyIdAttribute, cancellationToken); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs index 9baef849ed..8f1ce719e8 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Middleware; @@ -37,7 +38,7 @@ public class SoftDeletionAwareResourceService( return await base.CreateAsync(resource, cancellationToken); } - public override async Task UpdateAsync(TId id, TResource resource, CancellationToken cancellationToken) + public override async Task UpdateAsync([DisallowNull] TId id, TResource resource, CancellationToken cancellationToken) { if (_targetedFields.Relationships.Any(relationship => IsSoftDeletable(relationship.RightType.ClrType))) { @@ -47,7 +48,7 @@ public class SoftDeletionAwareResourceService( return await base.UpdateAsync(id, resource, cancellationToken); } - public override async Task SetRelationshipAsync(TId leftId, string relationshipName, object? rightValue, CancellationToken cancellationToken) + public override async Task SetRelationshipAsync([DisallowNull] TId leftId, string relationshipName, object? rightValue, CancellationToken cancellationToken) { if (IsSoftDeletable(_request.Relationship!.RightType.ClrType)) { @@ -57,7 +58,7 @@ public override async Task SetRelationshipAsync(TId leftId, string relationshipN await base.SetRelationshipAsync(leftId, relationshipName, rightValue, cancellationToken); } - public override async Task AddToToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, + public override async Task AddToToManyRelationshipAsync([DisallowNull] TId leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { if (IsSoftDeletable(typeof(TResource))) @@ -73,7 +74,7 @@ public override async Task AddToToManyRelationshipAsync(TId leftId, string relat await base.AddToToManyRelationshipAsync(leftId, relationshipName, rightResourceIds, cancellationToken); } - public override async Task DeleteAsync(TId id, CancellationToken cancellationToken) + public override async Task DeleteAsync([DisallowNull] TId id, CancellationToken cancellationToken) { if (IsSoftDeletable(typeof(TResource))) { @@ -85,7 +86,7 @@ public override async Task DeleteAsync(TId id, CancellationToken cancellationTok } } - private async Task SoftDeleteAsync(TId id, CancellationToken cancellationToken) + private async Task SoftDeleteAsync([DisallowNull] TId id, CancellationToken cancellationToken) { TResource resourceFromDatabase = await GetPrimaryResourceForUpdateAsync(id, cancellationToken); From ee41f122807febf75afc7d0300e34337ffd1fa4d Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 30 Jun 2024 20:25:43 +0200 Subject: [PATCH 91/91] Increment version to 5.6.0 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 534f9bb7af..860217f52e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -27,6 +27,6 @@ false $(MSBuildThisFileDirectory)CodingGuidelines.ruleset $(MSBuildThisFileDirectory)tests.runsettings - 5.5.2 + 5.6.0