diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 0a05541b2e..8c08ac9c20 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"jetbrains.resharper.globaltools": {
- "version": "2021.2.2",
+ "version": "2021.3.0",
"commands": [
"jb"
]
@@ -21,7 +21,7 @@
]
},
"dotnet-reportgenerator-globaltool": {
- "version": "4.8.12",
+ "version": "5.0.0",
"commands": [
"reportgenerator"
]
diff --git a/Directory.Build.props b/Directory.Build.props
index da45eb941e..36c9ede075 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,22 +1,23 @@
- net5.0
- 5.0.*
- 5.0.*
- 5.0.*
- 3.*
- 2.11.10
+ net6.0
+ 6.0.*
+ 6.0.*
+ 6.0.*
+ 4.*
+ 2.*
5.0.0
$(MSBuildThisFileDirectory)CodingGuidelines.ruleset
9999
enable
+ enable
false
false
-
+
@@ -28,11 +29,8 @@
- 33.1.1
3.1.0
- 6.2.0
4.16.1
- 2.4.*
17.0.0
diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings
index 595cae92d8..0ca7c35d77 100644
--- a/JsonApiDotNetCore.sln.DotSettings
+++ b/JsonApiDotNetCore.sln.DotSettings
@@ -27,6 +27,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
SUGGESTION
SUGGESTION
SUGGESTION
+ WARNING
SUGGESTION
SUGGESTION
SUGGESTION
@@ -83,7 +84,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
WARNING
WARNING
WARNING
- <?xml version="1.0" encoding="utf-16"?><Profile name="JADNC Full Cleanup"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><HtmlReformatCode>True</HtmlReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags></Profile>
+ <?xml version="1.0" encoding="utf-16"?><Profile name="JADNC Full Cleanup"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><HtmlReformatCode>True</HtmlReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags></Profile>
JADNC Full Cleanup
Required
Required
@@ -91,10 +92,12 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
Required
Conditional
False
+ False
1
1
1
1
+ False
True
True
True
diff --git a/README.md b/README.md
index 5df6ed6b9f..71e5f523e5 100644
--- a/README.md
+++ b/README.md
@@ -56,20 +56,15 @@ public class Article : Identifiable
### Middleware
```c#
-public class Startup
-{
- public IServiceProvider ConfigureServices(IServiceCollection services)
- {
- services.AddJsonApi();
- }
-
- public void Configure(IApplicationBuilder app)
- {
- app.UseRouting();
- app.UseJsonApi();
- app.UseEndpoints(endpoints => endpoints.MapControllers());
- }
-}
+// Program.cs
+
+builder.Services.AddJsonApi();
+
+// ...
+
+app.UseRouting();
+app.UseJsonApi();
+app.MapControllers();
```
## Compatibility
@@ -77,16 +72,14 @@ public class Startup
The following chart should help you pick the best version, based on your environment.
See also our [versioning policy](./VERSIONING_POLICY.md).
-| JsonApiDotNetCore | .NET | Entity Framework Core | Status |
-| ----------------- | -------- | --------------------- | -------------------------- |
-| 3.x | Core 2.x | 2.x | Released |
-| 4.x | Core 3.1 | 3.1 | Released |
-| | Core 3.1 | 5 | |
-| | 5 | 5 | |
-| | 6 | 5 | |
-| v5.x (pending) | 5 | 5 | On AppVeyor, to-be-dropped |
-| | 6 | 5 | On AppVeyor, to-be-dropped |
-| | 6 | 6 | Requires build from master |
+| JsonApiDotNetCore | Status | .NET | Entity Framework Core |
+| ----------------- | ----------- | -------- | --------------------- |
+| 3.x | Stable | Core 2.x | 2.x |
+| 4.x | Stable | Core 3.1 | 3.1 |
+| | | Core 3.1 | 5 |
+| | | 5 | 5 |
+| | | 6 | 5 |
+| v5.x | Pre-release | 6 | 6 |
## Contributing
diff --git a/ROADMAP.md b/ROADMAP.md
index a1fc9267f3..9a890f2014 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -25,7 +25,7 @@ The need for breaking changes has blocked several efforts in the v4.x release, s
- [x] Improved paging links [#1010](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1010)
- [x] Configuration validation [#170](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/170)
- [x] Auto-generated controllers [#732](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/732) [#365](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/365)
-- [ ] Support .NET 6 with EF Core 6 [#1109](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1109)
+- [x] Support .NET 6 with EF Core 6 [#1109](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1109)
Aside from the list above, we have interest in the following topics. It's too soon yet to decide whether they'll make it into v5.x or in a later major version.
diff --git a/benchmarks/Benchmarks.csproj b/benchmarks/Benchmarks.csproj
index 225c3a75d7..4bde435c15 100644
--- a/benchmarks/Benchmarks.csproj
+++ b/benchmarks/Benchmarks.csproj
@@ -1,7 +1,7 @@
Exe
- $(NetCoreAppVersion)
+ $(TargetFrameworkName)
diff --git a/benchmarks/Deserialization/DeserializationBenchmarkBase.cs b/benchmarks/Deserialization/DeserializationBenchmarkBase.cs
index b21d7c85e7..bbf746d1a8 100644
--- a/benchmarks/Deserialization/DeserializationBenchmarkBase.cs
+++ b/benchmarks/Deserialization/DeserializationBenchmarkBase.cs
@@ -1,5 +1,3 @@
-using System;
-using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Text.Json;
using JetBrains.Annotations;
@@ -11,114 +9,111 @@
using JsonApiDotNetCore.Serialization.Request.Adapters;
using Microsoft.Extensions.Logging.Abstractions;
-namespace Benchmarks.Deserialization
-{
- public abstract class DeserializationBenchmarkBase
- {
- protected readonly JsonSerializerOptions SerializerReadOptions;
- protected readonly DocumentAdapter DocumentAdapter;
+namespace Benchmarks.Deserialization;
- protected DeserializationBenchmarkBase()
- {
- var options = new JsonApiOptions();
- IResourceGraph resourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build();
- options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph));
- SerializerReadOptions = ((IJsonApiOptions)options).SerializerReadOptions;
+public abstract class DeserializationBenchmarkBase
+{
+ protected readonly JsonSerializerOptions SerializerReadOptions;
+ protected readonly DocumentAdapter DocumentAdapter;
- var serviceContainer = new ServiceContainer();
- var resourceFactory = new ResourceFactory(serviceContainer);
- var resourceDefinitionAccessor = new ResourceDefinitionAccessor(resourceGraph, serviceContainer);
+ protected DeserializationBenchmarkBase()
+ {
+ var options = new JsonApiOptions();
+ IResourceGraph resourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build();
+ options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph));
+ SerializerReadOptions = ((IJsonApiOptions)options).SerializerReadOptions;
- serviceContainer.AddService(typeof(IResourceDefinitionAccessor), resourceDefinitionAccessor);
+ var serviceContainer = new ServiceContainer();
+ var resourceFactory = new ResourceFactory(serviceContainer);
+ var resourceDefinitionAccessor = new ResourceDefinitionAccessor(resourceGraph, serviceContainer);
- serviceContainer.AddService(typeof(IResourceDefinition),
- new JsonApiResourceDefinition(resourceGraph));
+ serviceContainer.AddService(typeof(IResourceDefinitionAccessor), resourceDefinitionAccessor);
+ serviceContainer.AddService(typeof(IResourceDefinition), new JsonApiResourceDefinition(resourceGraph));
- // ReSharper disable once VirtualMemberCallInConstructor
- JsonApiRequest request = CreateJsonApiRequest(resourceGraph);
- var targetedFields = new TargetedFields();
+ // ReSharper disable once VirtualMemberCallInConstructor
+ JsonApiRequest request = CreateJsonApiRequest(resourceGraph);
+ var targetedFields = new TargetedFields();
- var resourceIdentifierObjectAdapter = new ResourceIdentifierObjectAdapter(resourceGraph, resourceFactory);
- var relationshipDataAdapter = new RelationshipDataAdapter(resourceIdentifierObjectAdapter);
- var resourceObjectAdapter = new ResourceObjectAdapter(resourceGraph, resourceFactory, options, relationshipDataAdapter);
- var resourceDataAdapter = new ResourceDataAdapter(resourceDefinitionAccessor, resourceObjectAdapter);
+ var resourceIdentifierObjectAdapter = new ResourceIdentifierObjectAdapter(resourceGraph, resourceFactory);
+ var relationshipDataAdapter = new RelationshipDataAdapter(resourceIdentifierObjectAdapter);
+ var resourceObjectAdapter = new ResourceObjectAdapter(resourceGraph, resourceFactory, options, relationshipDataAdapter);
+ var resourceDataAdapter = new ResourceDataAdapter(resourceDefinitionAccessor, resourceObjectAdapter);
- var atomicReferenceAdapter = new AtomicReferenceAdapter(resourceGraph, resourceFactory);
- var atomicOperationResourceDataAdapter = new ResourceDataInOperationsRequestAdapter(resourceDefinitionAccessor, resourceObjectAdapter);
+ var atomicReferenceAdapter = new AtomicReferenceAdapter(resourceGraph, resourceFactory);
+ var atomicOperationResourceDataAdapter = new ResourceDataInOperationsRequestAdapter(resourceDefinitionAccessor, resourceObjectAdapter);
- var atomicOperationObjectAdapter = new AtomicOperationObjectAdapter(options, atomicReferenceAdapter,
- atomicOperationResourceDataAdapter, relationshipDataAdapter);
+ var atomicOperationObjectAdapter = new AtomicOperationObjectAdapter(options, atomicReferenceAdapter,
+ atomicOperationResourceDataAdapter, relationshipDataAdapter);
- var resourceDocumentAdapter = new DocumentInResourceOrRelationshipRequestAdapter(options, resourceDataAdapter, relationshipDataAdapter);
- var operationsDocumentAdapter = new DocumentInOperationsRequestAdapter(options, atomicOperationObjectAdapter);
+ var resourceDocumentAdapter = new DocumentInResourceOrRelationshipRequestAdapter(options, resourceDataAdapter, relationshipDataAdapter);
+ var operationsDocumentAdapter = new DocumentInOperationsRequestAdapter(options, atomicOperationObjectAdapter);
- DocumentAdapter = new DocumentAdapter(request, targetedFields, resourceDocumentAdapter, operationsDocumentAdapter);
- }
+ DocumentAdapter = new DocumentAdapter(request, targetedFields, resourceDocumentAdapter, operationsDocumentAdapter);
+ }
- protected abstract JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph);
+ protected abstract JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph);
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public sealed class IncomingResource : Identifiable
- {
- [Attr]
- public bool Attribute01 { get; set; }
+ [UsedImplicitly(ImplicitUseTargetFlags.Members)]
+ public sealed class IncomingResource : Identifiable
+ {
+ [Attr]
+ public bool Attribute01 { get; set; }
- [Attr]
- public char Attribute02 { get; set; }
+ [Attr]
+ public char Attribute02 { get; set; }
- [Attr]
- public ulong? Attribute03 { get; set; }
+ [Attr]
+ public ulong? Attribute03 { get; set; }
- [Attr]
- public decimal Attribute04 { get; set; }
+ [Attr]
+ public decimal Attribute04 { get; set; }
- [Attr]
- public float? Attribute05 { get; set; }
+ [Attr]
+ public float? Attribute05 { get; set; }
- [Attr]
- public string Attribute06 { get; set; } = null!;
+ [Attr]
+ public string Attribute06 { get; set; } = null!;
- [Attr]
- public DateTime? Attribute07 { get; set; }
+ [Attr]
+ public DateTime? Attribute07 { get; set; }
- [Attr]
- public DateTimeOffset? Attribute08 { get; set; }
+ [Attr]
+ public DateTimeOffset? Attribute08 { get; set; }
- [Attr]
- public TimeSpan? Attribute09 { get; set; }
+ [Attr]
+ public TimeSpan? Attribute09 { get; set; }
- [Attr]
- public DayOfWeek Attribute10 { get; set; }
+ [Attr]
+ public DayOfWeek Attribute10 { get; set; }
- [HasOne]
- public IncomingResource Single1 { get; set; } = null!;
+ [HasOne]
+ public IncomingResource Single1 { get; set; } = null!;
- [HasOne]
- public IncomingResource Single2 { get; set; } = null!;
+ [HasOne]
+ public IncomingResource Single2 { get; set; } = null!;
- [HasOne]
- public IncomingResource Single3 { get; set; } = null!;
+ [HasOne]
+ public IncomingResource Single3 { get; set; } = null!;
- [HasOne]
- public IncomingResource Single4 { get; set; } = null!;
+ [HasOne]
+ public IncomingResource Single4 { get; set; } = null!;
- [HasOne]
- public IncomingResource Single5 { get; set; } = null!;
+ [HasOne]
+ public IncomingResource Single5 { get; set; } = null!;
- [HasMany]
- public ISet Multi1 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi1 { get; set; } = null!;
- [HasMany]
- public ISet Multi2 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi2 { get; set; } = null!;
- [HasMany]
- public ISet Multi3 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi3 { get; set; } = null!;
- [HasMany]
- public ISet Multi4 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi4 { get; set; } = null!;
- [HasMany]
- public ISet Multi5 { get; set; } = null!;
- }
+ [HasMany]
+ public ISet Multi5 { get; set; } = null!;
}
}
diff --git a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs
index 0181f4ccbc..d28684e27b 100644
--- a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs
+++ b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs
@@ -1,285 +1,283 @@
-using System;
using System.Text.Json;
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Middleware;
using JsonApiDotNetCore.Serialization.Objects;
-namespace Benchmarks.Deserialization
+namespace Benchmarks.Deserialization;
+
+[MarkdownExporter]
+// ReSharper disable once ClassCanBeSealed.Global
+public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
- [MarkdownExporter]
- // ReSharper disable once ClassCanBeSealed.Global
- public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
+ private static readonly string RequestBody = JsonSerializer.Serialize(new
{
- private static readonly string RequestBody = JsonSerializer.Serialize(new
+ atomic__operations = new object[]
{
- atomic__operations = new object[]
+ new
{
- new
+ op = "add",
+ data = new
{
- op = "add",
- data = new
+ type = "incomingResources",
+ lid = "a-1",
+ attributes = new
+ {
+ attribute01 = true,
+ attribute02 = 'A',
+ attribute03 = 100UL,
+ attribute04 = 100.001m,
+ attribute05 = 200.002f,
+ attribute06 = "text",
+ attribute07 = DateTime.MaxValue,
+ attribute08 = DateTimeOffset.MaxValue,
+ attribute09 = TimeSpan.MaxValue,
+ attribute10 = DayOfWeek.Friday
+ },
+ relationships = new
{
- type = "incomingResources",
- lid = "a-1",
- attributes = new
+ single1 = new
{
- attribute01 = true,
- attribute02 = 'A',
- attribute03 = 100UL,
- attribute04 = 100.001m,
- attribute05 = 200.002f,
- attribute06 = "text",
- attribute07 = DateTime.MaxValue,
- attribute08 = DateTimeOffset.MaxValue,
- attribute09 = TimeSpan.MaxValue,
- attribute10 = DayOfWeek.Friday
+ data = new
+ {
+ type = "incomingResources",
+ id = "101"
+ }
},
- relationships = new
+ single2 = new
{
- single1 = new
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "101"
- }
- },
- single2 = new
+ type = "incomingResources",
+ id = "102"
+ }
+ },
+ single3 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "102"
- }
- },
- single3 = new
+ type = "incomingResources",
+ id = "103"
+ }
+ },
+ single4 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "103"
- }
- },
- single4 = new
+ type = "incomingResources",
+ id = "104"
+ }
+ },
+ single5 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "104"
- }
- },
- single5 = new
+ type = "incomingResources",
+ id = "105"
+ }
+ },
+ multi1 = new
+ {
+ data = new[]
{
- data = new
+ new
{
type = "incomingResources",
- id = "105"
- }
- },
- multi1 = new
- {
- data = new[]
- {
- new
- {
- type = "incomingResources",
- id = "201"
- }
+ id = "201"
}
- },
- multi2 = new
+ }
+ },
+ multi2 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "202"
- }
+ type = "incomingResources",
+ id = "202"
}
- },
- multi3 = new
+ }
+ },
+ multi3 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "203"
- }
+ type = "incomingResources",
+ id = "203"
}
- },
- multi4 = new
+ }
+ },
+ multi4 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "204"
- }
+ type = "incomingResources",
+ id = "204"
}
- },
- multi5 = new
+ }
+ },
+ multi5 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "205"
- }
+ type = "incomingResources",
+ id = "205"
}
}
}
}
- },
- new
+ }
+ },
+ new
+ {
+ op = "update",
+ data = new
{
- op = "update",
- data = new
+ type = "incomingResources",
+ id = "1",
+ attributes = new
+ {
+ attribute01 = true,
+ attribute02 = 'A',
+ attribute03 = 100UL,
+ attribute04 = 100.001m,
+ attribute05 = 200.002f,
+ attribute06 = "text",
+ attribute07 = DateTime.MaxValue,
+ attribute08 = DateTimeOffset.MaxValue,
+ attribute09 = TimeSpan.MaxValue,
+ attribute10 = DayOfWeek.Friday
+ },
+ relationships = new
{
- type = "incomingResources",
- id = "1",
- attributes = new
+ single1 = new
{
- attribute01 = true,
- attribute02 = 'A',
- attribute03 = 100UL,
- attribute04 = 100.001m,
- attribute05 = 200.002f,
- attribute06 = "text",
- attribute07 = DateTime.MaxValue,
- attribute08 = DateTimeOffset.MaxValue,
- attribute09 = TimeSpan.MaxValue,
- attribute10 = DayOfWeek.Friday
+ data = new
+ {
+ type = "incomingResources",
+ id = "101"
+ }
},
- relationships = new
+ single2 = new
{
- single1 = new
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "101"
- }
- },
- single2 = new
+ type = "incomingResources",
+ id = "102"
+ }
+ },
+ single3 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "102"
- }
- },
- single3 = new
+ type = "incomingResources",
+ id = "103"
+ }
+ },
+ single4 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "103"
- }
- },
- single4 = new
+ type = "incomingResources",
+ id = "104"
+ }
+ },
+ single5 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "104"
- }
- },
- single5 = new
+ type = "incomingResources",
+ id = "105"
+ }
+ },
+ multi1 = new
+ {
+ data = new[]
{
- data = new
+ new
{
type = "incomingResources",
- id = "105"
- }
- },
- multi1 = new
- {
- data = new[]
- {
- new
- {
- type = "incomingResources",
- id = "201"
- }
+ id = "201"
}
- },
- multi2 = new
+ }
+ },
+ multi2 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "202"
- }
+ type = "incomingResources",
+ id = "202"
}
- },
- multi3 = new
+ }
+ },
+ multi3 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "203"
- }
+ type = "incomingResources",
+ id = "203"
}
- },
- multi4 = new
+ }
+ },
+ multi4 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "204"
- }
+ type = "incomingResources",
+ id = "204"
}
- },
- multi5 = new
+ }
+ },
+ multi5 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "205"
- }
+ type = "incomingResources",
+ id = "205"
}
}
}
}
- },
- new
+ }
+ },
+ new
+ {
+ op = "remove",
+ @ref = new
{
- op = "remove",
- @ref = new
- {
- type = "incomingResources",
- lid = "a-1"
- }
+ type = "incomingResources",
+ lid = "a-1"
}
}
- }).Replace("atomic__operations", "atomic:operations");
-
- [Benchmark]
- public object? DeserializeOperationsRequest()
- {
- var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions)!;
- return DocumentAdapter.Convert(document);
}
+ }).Replace("atomic__operations", "atomic:operations");
- protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
+ [Benchmark]
+ public object? DeserializeOperationsRequest()
+ {
+ var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions)!;
+ return DocumentAdapter.Convert(document);
+ }
+
+ protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
+ {
+ return new JsonApiRequest
{
- return new JsonApiRequest
- {
- Kind = EndpointKind.AtomicOperations
- };
- }
+ Kind = EndpointKind.AtomicOperations
+ };
}
}
diff --git a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs
index e154306819..23a6205bf5 100644
--- a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs
+++ b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs
@@ -1,150 +1,148 @@
-using System;
using System.Text.Json;
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Middleware;
using JsonApiDotNetCore.Serialization.Objects;
-namespace Benchmarks.Deserialization
+namespace Benchmarks.Deserialization;
+
+[MarkdownExporter]
+// ReSharper disable once ClassCanBeSealed.Global
+public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
{
- [MarkdownExporter]
- // ReSharper disable once ClassCanBeSealed.Global
- public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
+ private static readonly string RequestBody = JsonSerializer.Serialize(new
{
- private static readonly string RequestBody = JsonSerializer.Serialize(new
+ data = new
{
- data = new
+ type = "incomingResources",
+ attributes = new
+ {
+ attribute01 = true,
+ attribute02 = 'A',
+ attribute03 = 100UL,
+ attribute04 = 100.001m,
+ attribute05 = 200.002f,
+ attribute06 = "text",
+ attribute07 = DateTime.MaxValue,
+ attribute08 = DateTimeOffset.MaxValue,
+ attribute09 = TimeSpan.MaxValue,
+ attribute10 = DayOfWeek.Friday
+ },
+ relationships = new
{
- type = "incomingResources",
- attributes = new
+ single1 = new
{
- attribute01 = true,
- attribute02 = 'A',
- attribute03 = 100UL,
- attribute04 = 100.001m,
- attribute05 = 200.002f,
- attribute06 = "text",
- attribute07 = DateTime.MaxValue,
- attribute08 = DateTimeOffset.MaxValue,
- attribute09 = TimeSpan.MaxValue,
- attribute10 = DayOfWeek.Friday
+ data = new
+ {
+ type = "incomingResources",
+ id = "101"
+ }
},
- relationships = new
+ single2 = new
{
- single1 = new
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "101"
- }
- },
- single2 = new
+ type = "incomingResources",
+ id = "102"
+ }
+ },
+ single3 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "102"
- }
- },
- single3 = new
+ type = "incomingResources",
+ id = "103"
+ }
+ },
+ single4 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "103"
- }
- },
- single4 = new
+ type = "incomingResources",
+ id = "104"
+ }
+ },
+ single5 = new
+ {
+ data = new
{
- data = new
- {
- type = "incomingResources",
- id = "104"
- }
- },
- single5 = new
+ type = "incomingResources",
+ id = "105"
+ }
+ },
+ multi1 = new
+ {
+ data = new[]
{
- data = new
+ new
{
type = "incomingResources",
- id = "105"
+ id = "201"
}
- },
- multi1 = new
- {
- data = new[]
- {
- new
- {
- type = "incomingResources",
- id = "201"
- }
- }
- },
- multi2 = new
+ }
+ },
+ multi2 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "202"
- }
+ type = "incomingResources",
+ id = "202"
}
- },
- multi3 = new
+ }
+ },
+ multi3 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "203"
- }
+ type = "incomingResources",
+ id = "203"
}
- },
- multi4 = new
+ }
+ },
+ multi4 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "204"
- }
+ type = "incomingResources",
+ id = "204"
}
- },
- multi5 = new
+ }
+ },
+ multi5 = new
+ {
+ data = new[]
{
- data = new[]
+ new
{
- new
- {
- type = "incomingResources",
- id = "205"
- }
+ type = "incomingResources",
+ id = "205"
}
}
}
}
- });
-
- [Benchmark]
- public object? DeserializeResourceRequest()
- {
- var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions)!;
- return DocumentAdapter.Convert(document);
}
+ });
- protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
+ [Benchmark]
+ public object? DeserializeResourceRequest()
+ {
+ var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions)!;
+ return DocumentAdapter.Convert(document);
+ }
+
+ protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
+ {
+ return new JsonApiRequest
{
- return new JsonApiRequest
- {
- Kind = EndpointKind.Primary,
- PrimaryResourceType = resourceGraph.GetResourceType(),
- WriteOperation = WriteOperationKind.CreateResource
- };
- }
+ Kind = EndpointKind.Primary,
+ PrimaryResourceType = resourceGraph.GetResourceType(),
+ WriteOperation = WriteOperationKind.CreateResource
+ };
}
}
diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs
index 45406133dd..818b9ab5e5 100644
--- a/benchmarks/Program.cs
+++ b/benchmarks/Program.cs
@@ -3,22 +3,21 @@
using Benchmarks.QueryString;
using Benchmarks.Serialization;
-namespace Benchmarks
+namespace Benchmarks;
+
+internal static class Program
{
- internal static class Program
+ private static void Main(string[] args)
{
- private static void Main(string[] args)
+ var switcher = new BenchmarkSwitcher(new[]
{
- var switcher = new BenchmarkSwitcher(new[]
- {
- typeof(ResourceDeserializationBenchmarks),
- typeof(OperationsDeserializationBenchmarks),
- typeof(ResourceSerializationBenchmarks),
- typeof(OperationsSerializationBenchmarks),
- typeof(QueryStringParserBenchmarks)
- });
+ typeof(ResourceDeserializationBenchmarks),
+ typeof(OperationsDeserializationBenchmarks),
+ typeof(ResourceSerializationBenchmarks),
+ typeof(OperationsSerializationBenchmarks),
+ typeof(QueryStringParserBenchmarks)
+ });
- switcher.Run(args);
- }
+ switcher.Run(args);
}
}
diff --git a/benchmarks/QueryString/QueryStringParserBenchmarks.cs b/benchmarks/QueryString/QueryStringParserBenchmarks.cs
index 42d34f8ce4..efa4f12659 100644
--- a/benchmarks/QueryString/QueryStringParserBenchmarks.cs
+++ b/benchmarks/QueryString/QueryStringParserBenchmarks.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.Design;
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore;
@@ -11,94 +10,92 @@
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging.Abstractions;
-namespace Benchmarks.QueryString
+namespace Benchmarks.QueryString;
+
+// ReSharper disable once ClassCanBeSealed.Global
+[MarkdownExporter]
+[SimpleJob(3, 10, 20)]
+[MemoryDiagnoser]
+public class QueryStringParserBenchmarks
{
- // ReSharper disable once ClassCanBeSealed.Global
- [MarkdownExporter]
- [SimpleJob(3, 10, 20)]
- [MemoryDiagnoser]
- public class QueryStringParserBenchmarks
+ private readonly FakeRequestQueryStringAccessor _queryStringAccessor = new();
+ private readonly QueryStringReader _queryStringReader;
+
+ public QueryStringParserBenchmarks()
{
- private readonly FakeRequestQueryStringAccessor _queryStringAccessor = new();
- private readonly QueryStringReader _queryStringReader;
+ IJsonApiOptions options = new JsonApiOptions
+ {
+ EnableLegacyFilterNotation = true
+ };
- public QueryStringParserBenchmarks()
+ IResourceGraph resourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add("alt-resource-name").Build();
+
+ var request = new JsonApiRequest
{
- IJsonApiOptions options = new JsonApiOptions
- {
- EnableLegacyFilterNotation = true
- };
+ PrimaryResourceType = resourceGraph.GetResourceType(typeof(QueryableResource)),
+ IsCollection = true
+ };
- IResourceGraph resourceGraph =
- new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add("alt-resource-name").Build();
+ var resourceFactory = new ResourceFactory(new ServiceContainer());
- var request = new JsonApiRequest
- {
- PrimaryResourceType = resourceGraph.GetResourceType(typeof(QueryableResource)),
- IsCollection = true
- };
+ var includeReader = new IncludeQueryStringParameterReader(request, resourceGraph, options);
+ var filterReader = new FilterQueryStringParameterReader(request, resourceGraph, resourceFactory, options);
+ var sortReader = new SortQueryStringParameterReader(request, resourceGraph);
+ var sparseFieldSetReader = new SparseFieldSetQueryStringParameterReader(request, resourceGraph);
+ var paginationReader = new PaginationQueryStringParameterReader(request, resourceGraph, options);
- var resourceFactory = new ResourceFactory(new ServiceContainer());
+ IQueryStringParameterReader[] readers = ArrayFactory.Create(includeReader, filterReader, sortReader,
+ sparseFieldSetReader, paginationReader);
- var includeReader = new IncludeQueryStringParameterReader(request, resourceGraph, options);
- var filterReader = new FilterQueryStringParameterReader(request, resourceGraph, resourceFactory, options);
- var sortReader = new SortQueryStringParameterReader(request, resourceGraph);
- var sparseFieldSetReader = new SparseFieldSetQueryStringParameterReader(request, resourceGraph);
- var paginationReader = new PaginationQueryStringParameterReader(request, resourceGraph, options);
+ _queryStringReader = new QueryStringReader(options, _queryStringAccessor, readers, NullLoggerFactory.Instance);
+ }
- IQueryStringParameterReader[] readers = ArrayFactory.Create(includeReader, filterReader, sortReader,
- sparseFieldSetReader, paginationReader);
+ [Benchmark]
+ public void AscendingSort()
+ {
+ const string queryString = "?sort=alt-attr-name";
- _queryStringReader = new QueryStringReader(options, _queryStringAccessor, readers, NullLoggerFactory.Instance);
- }
+ _queryStringAccessor.SetQueryString(queryString);
+ _queryStringReader.ReadAll(null);
+ }
- [Benchmark]
- public void AscendingSort()
- {
- const string queryString = "?sort=alt-attr-name";
+ [Benchmark]
+ public void DescendingSort()
+ {
+ const string queryString = "?sort=-alt-attr-name";
- _queryStringAccessor.SetQueryString(queryString);
- _queryStringReader.ReadAll(null);
- }
+ _queryStringAccessor.SetQueryString(queryString);
+ _queryStringReader.ReadAll(null);
+ }
- [Benchmark]
- public void DescendingSort()
+ [Benchmark]
+ public void ComplexQuery()
+ {
+ Run(100, () =>
{
- const string queryString = "?sort=-alt-attr-name";
+ const string queryString =
+ "?filter[alt-attr-name]=abc,eq:abc&sort=-alt-attr-name&include=child&page[size]=1&fields[alt-resource-name]=alt-attr-name";
_queryStringAccessor.SetQueryString(queryString);
_queryStringReader.ReadAll(null);
- }
+ });
+ }
- [Benchmark]
- public void ComplexQuery()
+ private void Run(int iterations, Action action)
+ {
+ for (int index = 0; index < iterations; index++)
{
- Run(100, () =>
- {
- const string queryString =
- "?filter[alt-attr-name]=abc,eq:abc&sort=-alt-attr-name&include=child&page[size]=1&fields[alt-resource-name]=alt-attr-name";
-
- _queryStringAccessor.SetQueryString(queryString);
- _queryStringReader.ReadAll(null);
- });
+ action();
}
+ }
- private void Run(int iterations, Action action)
- {
- for (int index = 0; index < iterations; index++)
- {
- action();
- }
- }
+ private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor
+ {
+ public IQueryCollection Query { get; private set; } = new QueryCollection();
- private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor
+ public void SetQueryString(string queryString)
{
- public IQueryCollection Query { get; private set; } = new QueryCollection();
-
- public void SetQueryString(string queryString)
- {
- Query = new QueryCollection(QueryHelpers.ParseQuery(queryString));
- }
+ Query = new QueryCollection(QueryHelpers.ParseQuery(queryString));
}
}
}
diff --git a/benchmarks/QueryString/QueryableResource.cs b/benchmarks/QueryString/QueryableResource.cs
index bcf0a5075a..7c26474ae4 100644
--- a/benchmarks/QueryString/QueryableResource.cs
+++ b/benchmarks/QueryString/QueryableResource.cs
@@ -2,15 +2,14 @@
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
-namespace Benchmarks.QueryString
+namespace Benchmarks.QueryString;
+
+[UsedImplicitly(ImplicitUseTargetFlags.Members)]
+public sealed class QueryableResource : Identifiable
{
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public sealed class QueryableResource : Identifiable
- {
- [Attr(PublicName = "alt-attr-name")]
- public string? Name { get; set; }
+ [Attr(PublicName = "alt-attr-name")]
+ public string? Name { get; set; }
- [HasOne]
- public QueryableResource? Child { get; set; }
- }
+ [HasOne]
+ public QueryableResource? Child { get; set; }
}
diff --git a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs
index fef0d67a12..7076ca5cb8 100644
--- a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs
+++ b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs
@@ -1,5 +1,3 @@
-using System;
-using System.Collections.Generic;
using System.Text.Json;
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore.Configuration;
@@ -8,130 +6,129 @@
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Serialization.Objects;
-namespace Benchmarks.Serialization
+namespace Benchmarks.Serialization;
+
+[MarkdownExporter]
+// ReSharper disable once ClassCanBeSealed.Global
+public class OperationsSerializationBenchmarks : SerializationBenchmarkBase
{
- [MarkdownExporter]
- // ReSharper disable once ClassCanBeSealed.Global
- public class OperationsSerializationBenchmarks : SerializationBenchmarkBase
- {
- private readonly IEnumerable _responseOperations;
+ private readonly IEnumerable _responseOperations;
- public OperationsSerializationBenchmarks()
- {
- // ReSharper disable once VirtualMemberCallInConstructor
- JsonApiRequest request = CreateJsonApiRequest(ResourceGraph);
+ public OperationsSerializationBenchmarks()
+ {
+ // ReSharper disable once VirtualMemberCallInConstructor
+ JsonApiRequest request = CreateJsonApiRequest(ResourceGraph);
- _responseOperations = CreateResponseOperations(request);
- }
+ _responseOperations = CreateResponseOperations(request);
+ }
- private static IEnumerable CreateResponseOperations(IJsonApiRequest request)
+ private static IEnumerable CreateResponseOperations(IJsonApiRequest request)
+ {
+ var resource1 = new OutgoingResource
{
- var resource1 = new OutgoingResource
- {
- Id = 1,
- Attribute01 = true,
- Attribute02 = 'A',
- Attribute03 = 100UL,
- Attribute04 = 100.001m,
- Attribute05 = 100.002f,
- Attribute06 = "text1",
- Attribute07 = new DateTime(2001, 1, 1),
- Attribute08 = new DateTimeOffset(2001, 1, 1, 0, 0, 0, TimeSpan.FromHours(1)),
- Attribute09 = new TimeSpan(1, 0, 0),
- Attribute10 = DayOfWeek.Sunday
- };
-
- var resource2 = new OutgoingResource
- {
- Id = 2,
- Attribute01 = false,
- Attribute02 = 'B',
- Attribute03 = 200UL,
- Attribute04 = 200.001m,
- Attribute05 = 200.002f,
- Attribute06 = "text2",
- Attribute07 = new DateTime(2002, 2, 2),
- Attribute08 = new DateTimeOffset(2002, 2, 2, 0, 0, 0, TimeSpan.FromHours(2)),
- Attribute09 = new TimeSpan(2, 0, 0),
- Attribute10 = DayOfWeek.Monday
- };
+ Id = 1,
+ Attribute01 = true,
+ Attribute02 = 'A',
+ Attribute03 = 100UL,
+ Attribute04 = 100.001m,
+ Attribute05 = 100.002f,
+ Attribute06 = "text1",
+ Attribute07 = new DateTime(2001, 1, 1),
+ Attribute08 = new DateTimeOffset(2001, 1, 1, 0, 0, 0, TimeSpan.FromHours(1)),
+ Attribute09 = new TimeSpan(1, 0, 0),
+ Attribute10 = DayOfWeek.Sunday
+ };
- var resource3 = new OutgoingResource
- {
- Id = 3,
- Attribute01 = true,
- Attribute02 = 'C',
- Attribute03 = 300UL,
- Attribute04 = 300.001m,
- Attribute05 = 300.002f,
- Attribute06 = "text3",
- Attribute07 = new DateTime(2003, 3, 3),
- Attribute08 = new DateTimeOffset(2003, 3, 3, 0, 0, 0, TimeSpan.FromHours(3)),
- Attribute09 = new TimeSpan(3, 0, 0),
- Attribute10 = DayOfWeek.Tuesday
- };
+ var resource2 = new OutgoingResource
+ {
+ Id = 2,
+ Attribute01 = false,
+ Attribute02 = 'B',
+ Attribute03 = 200UL,
+ Attribute04 = 200.001m,
+ Attribute05 = 200.002f,
+ Attribute06 = "text2",
+ Attribute07 = new DateTime(2002, 2, 2),
+ Attribute08 = new DateTimeOffset(2002, 2, 2, 0, 0, 0, TimeSpan.FromHours(2)),
+ Attribute09 = new TimeSpan(2, 0, 0),
+ Attribute10 = DayOfWeek.Monday
+ };
- var resource4 = new OutgoingResource
- {
- Id = 4,
- Attribute01 = false,
- Attribute02 = 'D',
- Attribute03 = 400UL,
- Attribute04 = 400.001m,
- Attribute05 = 400.002f,
- Attribute06 = "text4",
- Attribute07 = new DateTime(2004, 4, 4),
- Attribute08 = new DateTimeOffset(2004, 4, 4, 0, 0, 0, TimeSpan.FromHours(4)),
- Attribute09 = new TimeSpan(4, 0, 0),
- Attribute10 = DayOfWeek.Wednesday
- };
+ var resource3 = new OutgoingResource
+ {
+ Id = 3,
+ Attribute01 = true,
+ Attribute02 = 'C',
+ Attribute03 = 300UL,
+ Attribute04 = 300.001m,
+ Attribute05 = 300.002f,
+ Attribute06 = "text3",
+ Attribute07 = new DateTime(2003, 3, 3),
+ Attribute08 = new DateTimeOffset(2003, 3, 3, 0, 0, 0, TimeSpan.FromHours(3)),
+ Attribute09 = new TimeSpan(3, 0, 0),
+ Attribute10 = DayOfWeek.Tuesday
+ };
- var resource5 = new OutgoingResource
- {
- Id = 5,
- Attribute01 = true,
- Attribute02 = 'E',
- Attribute03 = 500UL,
- Attribute04 = 500.001m,
- Attribute05 = 500.002f,
- Attribute06 = "text5",
- Attribute07 = new DateTime(2005, 5, 5),
- Attribute08 = new DateTimeOffset(2005, 5, 5, 0, 0, 0, TimeSpan.FromHours(5)),
- Attribute09 = new TimeSpan(5, 0, 0),
- Attribute10 = DayOfWeek.Thursday
- };
+ var resource4 = new OutgoingResource
+ {
+ Id = 4,
+ Attribute01 = false,
+ Attribute02 = 'D',
+ Attribute03 = 400UL,
+ Attribute04 = 400.001m,
+ Attribute05 = 400.002f,
+ Attribute06 = "text4",
+ Attribute07 = new DateTime(2004, 4, 4),
+ Attribute08 = new DateTimeOffset(2004, 4, 4, 0, 0, 0, TimeSpan.FromHours(4)),
+ Attribute09 = new TimeSpan(4, 0, 0),
+ Attribute10 = DayOfWeek.Wednesday
+ };
- var targetedFields = new TargetedFields();
+ var resource5 = new OutgoingResource
+ {
+ Id = 5,
+ Attribute01 = true,
+ Attribute02 = 'E',
+ Attribute03 = 500UL,
+ Attribute04 = 500.001m,
+ Attribute05 = 500.002f,
+ Attribute06 = "text5",
+ Attribute07 = new DateTime(2005, 5, 5),
+ Attribute08 = new DateTimeOffset(2005, 5, 5, 0, 0, 0, TimeSpan.FromHours(5)),
+ Attribute09 = new TimeSpan(5, 0, 0),
+ Attribute10 = DayOfWeek.Thursday
+ };
- return new List
- {
- new(resource1, targetedFields, request),
- new(resource2, targetedFields, request),
- new(resource3, targetedFields, request),
- new(resource4, targetedFields, request),
- new(resource5, targetedFields, request)
- };
- }
+ var targetedFields = new TargetedFields();
- [Benchmark]
- public string SerializeOperationsResponse()
+ return new List
{
- Document responseDocument = ResponseModelAdapter.Convert(_responseOperations);
- return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions);
- }
+ new(resource1, targetedFields, request),
+ new(resource2, targetedFields, request),
+ new(resource3, targetedFields, request),
+ new(resource4, targetedFields, request),
+ new(resource5, targetedFields, request)
+ };
+ }
- protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
- {
- return new JsonApiRequest
- {
- Kind = EndpointKind.AtomicOperations,
- PrimaryResourceType = resourceGraph.GetResourceType()
- };
- }
+ [Benchmark]
+ public string SerializeOperationsResponse()
+ {
+ Document responseDocument = ResponseModelAdapter.Convert(_responseOperations);
+ return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions);
+ }
- protected override IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceGraph resourceGraph)
+ protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
+ {
+ return new JsonApiRequest
{
- return new EvaluatedIncludeCache();
- }
+ Kind = EndpointKind.AtomicOperations,
+ PrimaryResourceType = resourceGraph.GetResourceType()
+ };
+ }
+
+ protected override IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceGraph resourceGraph)
+ {
+ return new EvaluatedIncludeCache();
}
}
diff --git a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs
index 3435265262..6b716a5401 100644
--- a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs
+++ b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs
@@ -1,5 +1,3 @@
-using System;
-using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text.Json;
using BenchmarkDotNet.Attributes;
@@ -11,133 +9,132 @@
using JsonApiDotNetCore.Resources.Annotations;
using JsonApiDotNetCore.Serialization.Objects;
-namespace Benchmarks.Serialization
+namespace Benchmarks.Serialization;
+
+[MarkdownExporter]
+// ReSharper disable once ClassCanBeSealed.Global
+public class ResourceSerializationBenchmarks : SerializationBenchmarkBase
{
- [MarkdownExporter]
- // ReSharper disable once ClassCanBeSealed.Global
- public class ResourceSerializationBenchmarks : SerializationBenchmarkBase
+ private static readonly OutgoingResource ResponseResource = CreateResponseResource();
+
+ private static OutgoingResource CreateResponseResource()
{
- private static readonly OutgoingResource ResponseResource = CreateResponseResource();
+ var resource1 = new OutgoingResource
+ {
+ Id = 1,
+ Attribute01 = true,
+ Attribute02 = 'A',
+ Attribute03 = 100UL,
+ Attribute04 = 100.001m,
+ Attribute05 = 100.002f,
+ Attribute06 = "text1",
+ Attribute07 = new DateTime(2001, 1, 1),
+ Attribute08 = new DateTimeOffset(2001, 1, 1, 0, 0, 0, TimeSpan.FromHours(1)),
+ Attribute09 = new TimeSpan(1, 0, 0),
+ Attribute10 = DayOfWeek.Sunday
+ };
- private static OutgoingResource CreateResponseResource()
+ var resource2 = new OutgoingResource
{
- var resource1 = new OutgoingResource
- {
- Id = 1,
- Attribute01 = true,
- Attribute02 = 'A',
- Attribute03 = 100UL,
- Attribute04 = 100.001m,
- Attribute05 = 100.002f,
- Attribute06 = "text1",
- Attribute07 = new DateTime(2001, 1, 1),
- Attribute08 = new DateTimeOffset(2001, 1, 1, 0, 0, 0, TimeSpan.FromHours(1)),
- Attribute09 = new TimeSpan(1, 0, 0),
- Attribute10 = DayOfWeek.Sunday
- };
-
- var resource2 = new OutgoingResource
- {
- Id = 2,
- Attribute01 = false,
- Attribute02 = 'B',
- Attribute03 = 200UL,
- Attribute04 = 200.001m,
- Attribute05 = 200.002f,
- Attribute06 = "text2",
- Attribute07 = new DateTime(2002, 2, 2),
- Attribute08 = new DateTimeOffset(2002, 2, 2, 0, 0, 0, TimeSpan.FromHours(2)),
- Attribute09 = new TimeSpan(2, 0, 0),
- Attribute10 = DayOfWeek.Monday
- };
-
- var resource3 = new OutgoingResource
- {
- Id = 3,
- Attribute01 = true,
- Attribute02 = 'C',
- Attribute03 = 300UL,
- Attribute04 = 300.001m,
- Attribute05 = 300.002f,
- Attribute06 = "text3",
- Attribute07 = new DateTime(2003, 3, 3),
- Attribute08 = new DateTimeOffset(2003, 3, 3, 0, 0, 0, TimeSpan.FromHours(3)),
- Attribute09 = new TimeSpan(3, 0, 0),
- Attribute10 = DayOfWeek.Tuesday
- };
-
- var resource4 = new OutgoingResource
- {
- Id = 4,
- Attribute01 = false,
- Attribute02 = 'D',
- Attribute03 = 400UL,
- Attribute04 = 400.001m,
- Attribute05 = 400.002f,
- Attribute06 = "text4",
- Attribute07 = new DateTime(2004, 4, 4),
- Attribute08 = new DateTimeOffset(2004, 4, 4, 0, 0, 0, TimeSpan.FromHours(4)),
- Attribute09 = new TimeSpan(4, 0, 0),
- Attribute10 = DayOfWeek.Wednesday
- };
-
- var resource5 = new OutgoingResource
- {
- Id = 5,
- Attribute01 = true,
- Attribute02 = 'E',
- Attribute03 = 500UL,
- Attribute04 = 500.001m,
- Attribute05 = 500.002f,
- Attribute06 = "text5",
- Attribute07 = new DateTime(2005, 5, 5),
- Attribute08 = new DateTimeOffset(2005, 5, 5, 0, 0, 0, TimeSpan.FromHours(5)),
- Attribute09 = new TimeSpan(5, 0, 0),
- Attribute10 = DayOfWeek.Thursday
- };
-
- resource1.Single2 = resource2;
- resource2.Single3 = resource3;
- resource3.Multi4 = resource4.AsHashSet();
- resource4.Multi5 = resource5.AsHashSet();
-
- return resource1;
- }
-
- [Benchmark]
- public string SerializeResourceResponse()
+ Id = 2,
+ Attribute01 = false,
+ Attribute02 = 'B',
+ Attribute03 = 200UL,
+ Attribute04 = 200.001m,
+ Attribute05 = 200.002f,
+ Attribute06 = "text2",
+ Attribute07 = new DateTime(2002, 2, 2),
+ Attribute08 = new DateTimeOffset(2002, 2, 2, 0, 0, 0, TimeSpan.FromHours(2)),
+ Attribute09 = new TimeSpan(2, 0, 0),
+ Attribute10 = DayOfWeek.Monday
+ };
+
+ var resource3 = new OutgoingResource
{
- Document responseDocument = ResponseModelAdapter.Convert(ResponseResource);
- return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions);
- }
+ Id = 3,
+ Attribute01 = true,
+ Attribute02 = 'C',
+ Attribute03 = 300UL,
+ Attribute04 = 300.001m,
+ Attribute05 = 300.002f,
+ Attribute06 = "text3",
+ Attribute07 = new DateTime(2003, 3, 3),
+ Attribute08 = new DateTimeOffset(2003, 3, 3, 0, 0, 0, TimeSpan.FromHours(3)),
+ Attribute09 = new TimeSpan(3, 0, 0),
+ Attribute10 = DayOfWeek.Tuesday
+ };
- protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
+ var resource4 = new OutgoingResource
{
- return new JsonApiRequest
- {
- Kind = EndpointKind.Primary,
- PrimaryResourceType = resourceGraph.GetResourceType()
- };
- }
-
- protected override IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceGraph resourceGraph)
+ Id = 4,
+ Attribute01 = false,
+ Attribute02 = 'D',
+ Attribute03 = 400UL,
+ Attribute04 = 400.001m,
+ Attribute05 = 400.002f,
+ Attribute06 = "text4",
+ Attribute07 = new DateTime(2004, 4, 4),
+ Attribute08 = new DateTimeOffset(2004, 4, 4, 0, 0, 0, TimeSpan.FromHours(4)),
+ Attribute09 = new TimeSpan(4, 0, 0),
+ Attribute10 = DayOfWeek.Wednesday
+ };
+
+ var resource5 = new OutgoingResource
{
- ResourceType resourceAType = resourceGraph.GetResourceType();
+ Id = 5,
+ Attribute01 = true,
+ Attribute02 = 'E',
+ Attribute03 = 500UL,
+ Attribute04 = 500.001m,
+ Attribute05 = 500.002f,
+ Attribute06 = "text5",
+ Attribute07 = new DateTime(2005, 5, 5),
+ Attribute08 = new DateTimeOffset(2005, 5, 5, 0, 0, 0, TimeSpan.FromHours(5)),
+ Attribute09 = new TimeSpan(5, 0, 0),
+ Attribute10 = DayOfWeek.Thursday
+ };
+
+ resource1.Single2 = resource2;
+ resource2.Single3 = resource3;
+ resource3.Multi4 = resource4.AsHashSet();
+ resource4.Multi5 = resource5.AsHashSet();
+
+ return resource1;
+ }
+
+ [Benchmark]
+ public string SerializeResourceResponse()
+ {
+ Document responseDocument = ResponseModelAdapter.Convert(ResponseResource);
+ return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions);
+ }
+
+ protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
+ {
+ return new JsonApiRequest
+ {
+ Kind = EndpointKind.Primary,
+ PrimaryResourceType = resourceGraph.GetResourceType()
+ };
+ }
+
+ protected override IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceGraph resourceGraph)
+ {
+ ResourceType resourceAType = resourceGraph.GetResourceType();
- RelationshipAttribute single2 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Single2));
- RelationshipAttribute single3 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Single3));
- RelationshipAttribute multi4 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Multi4));
- RelationshipAttribute multi5 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Multi5));
+ RelationshipAttribute single2 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Single2));
+ RelationshipAttribute single3 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Single3));
+ RelationshipAttribute multi4 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Multi4));
+ RelationshipAttribute multi5 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Multi5));
- ImmutableArray chain = ImmutableArray.Create(single2, single3, multi4, multi5);
- IEnumerable chains = new ResourceFieldChainExpression(chain).AsEnumerable();
+ ImmutableArray chain = ImmutableArray.Create(single2, single3, multi4, multi5);
+ IEnumerable chains = new ResourceFieldChainExpression(chain).AsEnumerable();
- var converter = new IncludeChainConverter();
- IncludeExpression include = converter.FromRelationshipChains(chains);
+ var converter = new IncludeChainConverter();
+ IncludeExpression include = converter.FromRelationshipChains(chains);
- var cache = new EvaluatedIncludeCache();
- cache.Set(include);
- return cache;
- }
+ var cache = new EvaluatedIncludeCache();
+ cache.Set(include);
+ return cache;
}
}
diff --git a/benchmarks/Serialization/SerializationBenchmarkBase.cs b/benchmarks/Serialization/SerializationBenchmarkBase.cs
index 84d28c22ab..befa5049e8 100644
--- a/benchmarks/Serialization/SerializationBenchmarkBase.cs
+++ b/benchmarks/Serialization/SerializationBenchmarkBase.cs
@@ -1,10 +1,6 @@
-using System;
-using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text.Json;
using System.Text.Json.Serialization;
-using System.Threading;
-using System.Threading.Tasks;
using JetBrains.Annotations;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Middleware;
@@ -19,249 +15,248 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions;
-namespace Benchmarks.Serialization
+namespace Benchmarks.Serialization;
+
+public abstract class SerializationBenchmarkBase
{
- public abstract class SerializationBenchmarkBase
- {
- protected readonly JsonSerializerOptions SerializerWriteOptions;
- protected readonly IResponseModelAdapter ResponseModelAdapter;
- protected readonly IResourceGraph ResourceGraph;
+ protected readonly JsonSerializerOptions SerializerWriteOptions;
+ protected readonly IResponseModelAdapter ResponseModelAdapter;
+ protected readonly IResourceGraph ResourceGraph;
- protected SerializationBenchmarkBase()
+ protected SerializationBenchmarkBase()
+ {
+ var options = new JsonApiOptions
{
- var options = new JsonApiOptions
+ SerializerOptions =
{
- SerializerOptions =
+ Converters =
{
- Converters =
- {
- new JsonStringEnumConverter()
- }
+ new JsonStringEnumConverter()
}
- };
+ }
+ };
- ResourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build();
- SerializerWriteOptions = ((IJsonApiOptions)options).SerializerWriteOptions;
+ ResourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build();
+ SerializerWriteOptions = ((IJsonApiOptions)options).SerializerWriteOptions;
- // ReSharper disable VirtualMemberCallInConstructor
- JsonApiRequest request = CreateJsonApiRequest(ResourceGraph);
- IEvaluatedIncludeCache evaluatedIncludeCache = CreateEvaluatedIncludeCache(ResourceGraph);
- // ReSharper restore VirtualMemberCallInConstructor
+ // ReSharper disable VirtualMemberCallInConstructor
+ JsonApiRequest request = CreateJsonApiRequest(ResourceGraph);
+ IEvaluatedIncludeCache evaluatedIncludeCache = CreateEvaluatedIncludeCache(ResourceGraph);
+ // ReSharper restore VirtualMemberCallInConstructor
- var linkBuilder = new FakeLinkBuilder();
- var metaBuilder = new FakeMetaBuilder();
- IQueryConstraintProvider[] constraintProviders = Array.Empty();
- var resourceDefinitionAccessor = new FakeResourceDefinitionAccessor();
- var sparseFieldSetCache = new SparseFieldSetCache(constraintProviders, resourceDefinitionAccessor);
- var requestQueryStringAccessor = new FakeRequestQueryStringAccessor();
+ var linkBuilder = new FakeLinkBuilder();
+ var metaBuilder = new FakeMetaBuilder();
+ IQueryConstraintProvider[] constraintProviders = Array.Empty();
+ var resourceDefinitionAccessor = new FakeResourceDefinitionAccessor();
+ var sparseFieldSetCache = new SparseFieldSetCache(constraintProviders, resourceDefinitionAccessor);
+ var requestQueryStringAccessor = new FakeRequestQueryStringAccessor();
- ResponseModelAdapter = new ResponseModelAdapter(request, options, linkBuilder, metaBuilder, resourceDefinitionAccessor, evaluatedIncludeCache,
- sparseFieldSetCache, requestQueryStringAccessor);
- }
+ ResponseModelAdapter = new ResponseModelAdapter(request, options, linkBuilder, metaBuilder, resourceDefinitionAccessor, evaluatedIncludeCache,
+ sparseFieldSetCache, requestQueryStringAccessor);
+ }
- protected abstract JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph);
+ protected abstract JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph);
- protected abstract IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceGraph resourceGraph);
+ protected abstract IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceGraph resourceGraph);
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public sealed class OutgoingResource : Identifiable
- {
- [Attr]
- public bool Attribute01 { get; set; }
+ [UsedImplicitly(ImplicitUseTargetFlags.Members)]
+ public sealed class OutgoingResource : Identifiable
+ {
+ [Attr]
+ public bool Attribute01 { get; set; }
- [Attr]
- public char Attribute02 { get; set; }
+ [Attr]
+ public char Attribute02 { get; set; }
- [Attr]
- public ulong? Attribute03 { get; set; }
+ [Attr]
+ public ulong? Attribute03 { get; set; }
- [Attr]
- public decimal Attribute04 { get; set; }
+ [Attr]
+ public decimal Attribute04 { get; set; }
- [Attr]
- public float? Attribute05 { get; set; }
+ [Attr]
+ public float? Attribute05 { get; set; }
- [Attr]
- public string Attribute06 { get; set; } = null!;
+ [Attr]
+ public string Attribute06 { get; set; } = null!;
- [Attr]
- public DateTime? Attribute07 { get; set; }
+ [Attr]
+ public DateTime? Attribute07 { get; set; }
- [Attr]
- public DateTimeOffset? Attribute08 { get; set; }
+ [Attr]
+ public DateTimeOffset? Attribute08 { get; set; }
- [Attr]
- public TimeSpan? Attribute09 { get; set; }
+ [Attr]
+ public TimeSpan? Attribute09 { get; set; }
- [Attr]
- public DayOfWeek Attribute10 { get; set; }
+ [Attr]
+ public DayOfWeek Attribute10 { get; set; }
- [HasOne]
- public OutgoingResource Single1 { get; set; } = null!;
+ [HasOne]
+ public OutgoingResource Single1 { get; set; } = null!;
- [HasOne]
- public OutgoingResource Single2 { get; set; } = null!;
+ [HasOne]
+ public OutgoingResource Single2 { get; set; } = null!;
- [HasOne]
- public OutgoingResource Single3 { get; set; } = null!;
+ [HasOne]
+ public OutgoingResource Single3 { get; set; } = null!;
- [HasOne]
- public OutgoingResource Single4 { get; set; } = null!;
+ [HasOne]
+ public OutgoingResource Single4 { get; set; } = null!;
- [HasOne]
- public OutgoingResource Single5 { get; set; } = null!;
+ [HasOne]
+ public OutgoingResource Single5 { get; set; } = null!;
- [HasMany]
- public ISet Multi1 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi1 { get; set; } = null!;
- [HasMany]
- public ISet Multi2 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi2 { get; set; } = null!;
- [HasMany]
- public ISet Multi3 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi3 { get; set; } = null!;
- [HasMany]
- public ISet Multi4 { get; set; } = null!;
+ [HasMany]
+ public ISet Multi4 { get; set; } = null!;
- [HasMany]
- public ISet Multi5 { get; set; } = null!;
- }
+ [HasMany]
+ public ISet Multi5 { get; set; } = null!;
+ }
- private sealed class FakeResourceDefinitionAccessor : IResourceDefinitionAccessor
+ private sealed class FakeResourceDefinitionAccessor : IResourceDefinitionAccessor
+ {
+ public IImmutableSet OnApplyIncludes(ResourceType resourceType, IImmutableSet existingIncludes)
{
- public IImmutableSet OnApplyIncludes(ResourceType resourceType, IImmutableSet existingIncludes)
- {
- return existingIncludes;
- }
-
- public FilterExpression? OnApplyFilter(ResourceType resourceType, FilterExpression? existingFilter)
- {
- return existingFilter;
- }
+ return existingIncludes;
+ }
- public SortExpression? OnApplySort(ResourceType resourceType, SortExpression? existingSort)
- {
- return existingSort;
- }
+ public FilterExpression? OnApplyFilter(ResourceType resourceType, FilterExpression? existingFilter)
+ {
+ return existingFilter;
+ }
- public PaginationExpression? OnApplyPagination(ResourceType resourceType, PaginationExpression? existingPagination)
- {
- return existingPagination;
- }
+ public SortExpression? OnApplySort(ResourceType resourceType, SortExpression? existingSort)
+ {
+ return existingSort;
+ }
- public SparseFieldSetExpression? OnApplySparseFieldSet(ResourceType resourceType, SparseFieldSetExpression? existingSparseFieldSet)
- {
- return existingSparseFieldSet;
- }
+ public PaginationExpression? OnApplyPagination(ResourceType resourceType, PaginationExpression? existingPagination)
+ {
+ return existingPagination;
+ }
- public object? GetQueryableHandlerForQueryStringParameter(Type resourceClrType, string parameterName)
- {
- return null;
- }
+ public SparseFieldSetExpression? OnApplySparseFieldSet(ResourceType resourceType, SparseFieldSetExpression? existingSparseFieldSet)
+ {
+ return existingSparseFieldSet;
+ }
- public IDictionary? GetMeta(ResourceType resourceType, IIdentifiable resourceInstance)
- {
- return null;
- }
+ public object? GetQueryableHandlerForQueryStringParameter(Type resourceClrType, string parameterName)
+ {
+ return null;
+ }
- public Task OnPrepareWriteAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.CompletedTask;
- }
+ public IDictionary? GetMeta(ResourceType resourceType, IIdentifiable resourceInstance)
+ {
+ return null;
+ }
- public Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship,
- IIdentifiable? rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.FromResult(rightResourceId);
- }
+ public Task OnPrepareWriteAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.CompletedTask;
+ }
- public Task OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship,
- ISet rightResourceIds, WriteOperationKind writeOperation, CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.CompletedTask;
- }
+ public Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship,
+ IIdentifiable? rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.FromResult(rightResourceId);
+ }
- public Task OnAddToRelationshipAsync(TId leftResourceId, HasManyAttribute hasManyRelationship, ISet rightResourceIds,
- CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.CompletedTask;
- }
+ public Task OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds,
+ WriteOperationKind writeOperation, CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.CompletedTask;
+ }
- public Task OnRemoveFromRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship,
- ISet rightResourceIds, CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.CompletedTask;
- }
+ public Task OnAddToRelationshipAsync(TId leftResourceId, HasManyAttribute hasManyRelationship, ISet rightResourceIds,
+ CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.CompletedTask;
+ }
- public Task OnWritingAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.CompletedTask;
- }
+ public Task OnRemoveFromRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds,
+ CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.CompletedTask;
+ }
- public Task OnWriteSucceededAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
- where TResource : class, IIdentifiable
- {
- return Task.CompletedTask;
- }
+ public Task OnWritingAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.CompletedTask;
+ }
- public void OnDeserialize(IIdentifiable resource)
- {
- }
+ public Task OnWriteSucceededAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
+ where TResource : class, IIdentifiable
+ {
+ return Task.CompletedTask;
+ }
- public void OnSerialize(IIdentifiable resource)
- {
- }
+ public void OnDeserialize(IIdentifiable resource)
+ {
}
- private sealed class FakeLinkBuilder : ILinkBuilder
+ public void OnSerialize(IIdentifiable resource)
{
- public TopLevelLinks GetTopLevelLinks()
- {
- return new TopLevelLinks
- {
- Self = "TopLevel:Self"
- };
- }
+ }
+ }
- public ResourceLinks GetResourceLinks(ResourceType resourceType, IIdentifiable resource)
+ private sealed class FakeLinkBuilder : ILinkBuilder
+ {
+ public TopLevelLinks GetTopLevelLinks()
+ {
+ return new TopLevelLinks
{
- return new ResourceLinks
- {
- Self = "Resource:Self"
- };
- }
+ Self = "TopLevel:Self"
+ };
+ }
- public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable leftResource)
+ public ResourceLinks GetResourceLinks(ResourceType resourceType, IIdentifiable resource)
+ {
+ return new ResourceLinks
{
- return new RelationshipLinks
- {
- Self = "Relationship:Self",
- Related = "Relationship:Related"
- };
- }
+ Self = "Resource:Self"
+ };
}
- private sealed class FakeMetaBuilder : IMetaBuilder
+ public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable leftResource)
{
- public void Add(IReadOnlyDictionary values)
+ return new RelationshipLinks
{
- }
+ Self = "Relationship:Self",
+ Related = "Relationship:Related"
+ };
+ }
+ }
- public IDictionary? Build()
- {
- return null;
- }
+ private sealed class FakeMetaBuilder : IMetaBuilder
+ {
+ public void Add(IReadOnlyDictionary values)
+ {
}
- private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor
+ public IDictionary? Build()
{
- public IQueryCollection Query { get; } = new QueryCollection(0);
+ return null;
}
}
+
+ private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor
+ {
+ public IQueryCollection Query { get; } = new QueryCollection(0);
+ }
}
diff --git a/docs/getting-started/step-by-step.md b/docs/getting-started/step-by-step.md
index 697546561e..57090d2d09 100644
--- a/docs/getting-started/step-by-step.md
+++ b/docs/getting-started/step-by-step.md
@@ -3,21 +3,21 @@
The most basic use case leverages Entity Framework Core.
The shortest path to a running API looks like:
-- Create a new web app
+- Create a new API project
- Install
- Define models
- Define the DbContext
-- Add Middleware and Services
+- Add services and middleware
- Seed the database
-- Start the app
+- Start the API
This page will walk you through the **simplest** use case. More detailed examples can be found in the detailed usage subsections.
-### Create A New Web App
+### Create a new API project
```
-mkdir MyApp
-cd MyApp
+mkdir MyApi
+cd MyApi
dotnet new webapi
```
@@ -31,7 +31,7 @@ dotnet add package JsonApiDotNetCore
Install-Package JsonApiDotNetCore
```
-### Define Models
+### Define models
Define your domain models such that they implement `IIdentifiable`.
The easiest way to do this is to inherit from `Identifiable`.
@@ -47,7 +47,7 @@ public class Person : Identifiable
}
```
-### Define DbContext
+### Define the DbContext
Nothing special here, just an ordinary `DbContext`.
@@ -63,46 +63,56 @@ public class AppDbContext : DbContext
}
```
-### Middleware and Services
+### Add services and middleware
-Finally, add the services by adding the following to your Startup.ConfigureServices:
+Finally, register the services and middleware by adding them to your Program.cs:
```c#
-// This method gets called by the runtime. Use this method to add services to the container.
-public void ConfigureServices(IServiceCollection services)
+WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+// Add the Entity Framework Core DbContext like you normally would.
+builder.Services.AddDbContext(options =>
{
- // Add the Entity Framework Core DbContext like you normally would
- services.AddDbContext(options =>
- {
- // Use whatever provider you want, this is just an example
- options.UseNpgsql(GetDbConnectionString());
- });
+ string connectionString = GetConnectionString();
- // Add JsonApiDotNetCore
- services.AddJsonApi();
-}
-```
+ // Use whatever provider you want, this is just an example.
+ options.UseNpgsql(connectionString);
+});
-Add the middleware to the Startup.Configure method.
+// Add JsonApiDotNetCore services.
+builder.Services.AddJsonApi();
-```c#
-// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
-public void Configure(IApplicationBuilder app)
-{
- app.UseRouting();
- app.UseJsonApi();
- app.UseEndpoints(endpoints => endpoints.MapControllers());
-}
+WebApplication app = builder.Build();
+
+// Configure the HTTP request pipeline.
+
+app.UseRouting();
+
+// Add JsonApiDotNetCore middleware.
+app.UseJsonApi();
+
+app.MapControllers();
+
+app.Run();
```
-### Seeding the Database
+### Seed the database
-One way to seed the database is in your Configure method:
+One way to seed the database is from your Program.cs:
```c#
-public void Configure(IApplicationBuilder app, AppDbContext dbContext)
+await CreateDatabaseAsync(app.Services);
+
+app.Run();
+
+static async Task CreateDatabaseAsync(IServiceProvider serviceProvider)
{
- dbContext.Database.EnsureCreated();
+ await using AsyncServiceScope scope = serviceProvider.CreateAsyncScope();
+
+ var dbContext = scope.ServiceProvider.GetRequiredService();
+ await dbContext.Database.EnsureCreatedAsync();
if (!dbContext.People.Any())
{
@@ -111,16 +121,12 @@ public void Configure(IApplicationBuilder app, AppDbContext dbContext)
Name = "John Doe"
});
- dbContext.SaveChanges();
+ await dbContext.SaveChangesAsync();
}
-
- app.UseRouting();
- app.UseJsonApi();
- app.UseEndpoints(endpoints => endpoints.MapControllers());
}
```
-### Start the App
+### Start the API
```
dotnet run
diff --git a/docs/usage/errors.md b/docs/usage/errors.md
index 3278526e6c..67ce6fde17 100644
--- a/docs/usage/errors.md
+++ b/docs/usage/errors.md
@@ -88,11 +88,6 @@ public class CustomExceptionHandler : ExceptionHandler
}
}
-public class Startup
-{
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddScoped();
- }
-}
+// Program.cs
+builder.Services.AddScoped();
```
diff --git a/docs/usage/extensibility/layer-overview.md b/docs/usage/extensibility/layer-overview.md
index 8afd1b38bb..2fe99e2fbd 100644
--- a/docs/usage/extensibility/layer-overview.md
+++ b/docs/usage/extensibility/layer-overview.md
@@ -25,18 +25,15 @@ on your needs, you may want to replace other parts by deriving from the built-in
**Note:** If you're using [auto-discovery](~/usage/resource-graph.md#auto-discovery), then resource services, repositories and resource definitions will be automatically registered for you.
-Replacing built-in services is done on a per-resource basis and can be done through dependency injection in your Startup.cs file.
+Replacing built-in services is done on a per-resource basis and can be done at startup.
For convenience, extension methods are provided to register layers on all their implemented interfaces.
```c#
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddResourceService();
- services.AddResourceRepository();
- services.AddResourceDefinition();
-
- services.AddScoped();
- services.AddScoped();
-}
+// Program.cs
+builder.Services.AddResourceService();
+builder.Services.AddResourceRepository();
+builder.Services.AddResourceDefinition();
+
+builder.Services.AddScoped();
+builder.Services.AddScoped();
```
diff --git a/docs/usage/extensibility/middleware.md b/docs/usage/extensibility/middleware.md
index 9be350250a..62528893d3 100644
--- a/docs/usage/extensibility/middleware.md
+++ b/docs/usage/extensibility/middleware.md
@@ -10,46 +10,54 @@ It is possible to replace the built-in middleware components by configuring the
The following example replaces the internal exception filter with a custom implementation.
```c#
-/// In Startup.ConfigureServices
-services.AddService();
+// Program.cs
+builder.Services.AddService();
```
## Configuring `MvcOptions`
-The following example replaces all internal filters with a custom filter.
+The following example replaces the built-in query string action filter with a custom filter.
```c#
-public class Startup
+// Program.cs
+
+// Add services to the container.
+
+builder.Services.AddScoped();
+
+IMvcCoreBuilder mvcCoreBuilder = builder.Services.AddMvcCore();
+builder.Services.AddJsonApi(mvcBuilder: mvcCoreBuilder);
+
+Action? postConfigureMvcOptions = null;
+
+// Ensure this is placed after the AddJsonApi() call.
+mvcCoreBuilder.AddMvcOptions(mvcOptions =>
+{
+ postConfigureMvcOptions?.Invoke(mvcOptions);
+});
+
+// Configure the HTTP request pipeline.
+
+// Ensure this is placed before the MapControllers() call.
+postConfigureMvcOptions = mvcOptions =>
{
- private Action _postConfigureMvcOptions;
-
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddSingleton();
-
- IMvcCoreBuilder builder = services.AddMvcCore();
- services.AddJsonApi(mvcBuilder: builder);
-
- // Ensure this call is placed after the AddJsonApi call.
- builder.AddMvcOptions(mvcOptions =>
- {
- _postConfigureMvcOptions.Invoke(mvcOptions);
- });
- }
-
- public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
- {
- // Ensure this call is placed before the UseEndpoints call.
- _postConfigureMvcOptions = mvcOptions =>
- {
- mvcOptions.Filters.Clear();
- mvcOptions.Filters.Insert(0,
- app.ApplicationServices.GetService());
- };
-
- app.UseRouting();
- app.UseJsonApi();
- app.UseEndpoints(endpoints => endpoints.MapControllers());
- }
-}
+ IFilterMetadata existingFilter = mvcOptions.Filters.Single(filter =>
+ filter is ServiceFilterAttribute serviceFilter &&
+ serviceFilter.ServiceType == typeof(IAsyncQueryStringActionFilter));
+
+ mvcOptions.Filters.Remove(existingFilter);
+
+ using IServiceScope scope = app.Services.CreateScope();
+
+ var newFilter =
+ scope.ServiceProvider.GetRequiredService();
+
+ mvcOptions.Filters.Insert(0, newFilter);
+};
+
+app.UseRouting();
+app.UseJsonApi();
+app.MapControllers();
+
+app.Run();
```
diff --git a/docs/usage/extensibility/query-strings.md b/docs/usage/extensibility/query-strings.md
index 1411717ffd..83d9f2e0ea 100644
--- a/docs/usage/extensibility/query-strings.md
+++ b/docs/usage/extensibility/query-strings.md
@@ -24,15 +24,17 @@ See [here](~/usage/extensibility/resource-definitions.md#custom-query-string-par
In order to add parsing of custom query string parameters, you can implement the `IQueryStringParameterReader` interface and register your reader.
```c#
-public class YourQueryStringParameterReader : IQueryStringParameterReader
+public class CustomQueryStringParameterReader : IQueryStringParameterReader
{
// ...
}
```
```c#
-services.AddScoped();
-services.AddScoped(sp => sp.GetService());
+// Program.cs
+builder.Services.AddScoped();
+builder.Services.AddScoped(serviceProvider =>
+ serviceProvider.GetRequiredService());
```
Now you can inject your custom reader in resource services, repositories, resource definitions etc.
diff --git a/docs/usage/extensibility/repositories.md b/docs/usage/extensibility/repositories.md
index 7d76f2389a..102406e71b 100644
--- a/docs/usage/extensibility/repositories.md
+++ b/docs/usage/extensibility/repositories.md
@@ -3,15 +3,11 @@
If you want to use a data access technology other than Entity Framework Core, you can create an implementation of `IResourceRepository`.
If you only need minor changes you can override the methods defined in `EntityFrameworkCoreRepository`.
-The repository should then be registered in Startup.cs.
-
```c#
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddScoped, ArticleRepository>();
- services.AddScoped, ArticleRepository>();
- services.AddScoped, ArticleRepository>();
-}
+// Program.cs
+builder.Services.AddScoped, ArticleRepository>();
+builder.Services.AddScoped, ArticleRepository>();
+builder.Services.AddScoped, ArticleRepository>();
```
In v4.0 we introduced an extension method that you can use to register a resource repository on all of its JsonApiDotNetCore interfaces.
@@ -20,13 +16,8 @@ This is helpful when you implement (a subset of) the resource interfaces and wan
**Note:** If you're using [auto-discovery](~/usage/resource-graph.md#auto-discovery), this happens automatically.
```c#
-public class Startup
-{
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddResourceRepository();
- }
-}
+// Program.cs
+builder.Services.AddResourceRepository();
```
A sample implementation that performs authorization might look like this.
@@ -64,7 +55,8 @@ If you need to use multiple Entity Framework Core DbContexts, first create a rep
This example shows a single `DbContextARepository` for all entities that are members of `DbContextA`.
```c#
-public class DbContextARepository : EntityFrameworkCoreRepository
+public class DbContextARepository
+ : EntityFrameworkCoreRepository
where TResource : class, IIdentifiable
{
public DbContextARepository(ITargetedFields targetedFields,
@@ -83,13 +75,12 @@ public class DbContextARepository : EntityFrameworkCoreRepositor
Then register the added types and use the non-generic overload of `AddJsonApi` to add their resources to the graph.
```c#
-// In Startup.ConfigureServices
-
-services.AddDbContext(options => options.UseSqlite("Data Source=A.db"));
-services.AddDbContext(options => options.UseSqlite("Data Source=B.db"));
+// Program.cs
+builder.Services.AddDbContext(options => options.UseSqlite("Data Source=A.db"));
+builder.Services.AddDbContext(options => options.UseSqlite("Data Source=B.db"));
-services.AddScoped, DbContextARepository>();
-services.AddScoped, DbContextBRepository>();
+builder.Services.AddJsonApi(dbContextTypes: new[] { typeof(DbContextA), typeof(DbContextB) });
-services.AddJsonApi(dbContextTypes: new[] { typeof(DbContextA), typeof(DbContextB) });
+builder.Services.AddScoped, DbContextARepository>();
+builder.Services.AddScoped, DbContextBRepository>();
```
diff --git a/docs/usage/extensibility/resource-definitions.md b/docs/usage/extensibility/resource-definitions.md
index 4c9eeeb8a6..8696811e16 100644
--- a/docs/usage/extensibility/resource-definitions.md
+++ b/docs/usage/extensibility/resource-definitions.md
@@ -10,20 +10,15 @@ In v4.2 we introduced an extension method that you can use to register your reso
**Note:** If you're using [auto-discovery](~/usage/resource-graph.md#auto-discovery), this happens automatically.
```c#
-public class Startup
-{
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddResourceDefinition();
- }
-}
+// Program.cs
+builder.Services.AddResourceDefinition();
```
**Note:** Prior to the introduction of auto-discovery (in v3), you needed to register the
resource definition on the container yourself:
```c#
-services.AddScoped, ProductDefinition>();
+builder.Services.AddScoped, ArticleDefinition>();
```
## Customizing queries
diff --git a/docs/usage/extensibility/services.md b/docs/usage/extensibility/services.md
index 5270f4bbd8..bc3dd5bff8 100644
--- a/docs/usage/extensibility/services.md
+++ b/docs/usage/extensibility/services.md
@@ -48,17 +48,16 @@ As previously discussed, this library uses Entity Framework Core by default.
If you'd like to use another ORM that does not provide what JsonApiResourceService depends upon, you can use a custom `IResourceService` implementation.
```c#
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- // add the service override for Product
- services.AddScoped, ProductService>();
+// Program.cs
- // add your own Data Access Object
- services.AddScoped();
-}
+// Add the service override for Product.
+builder.Services.AddScoped, ProductService>();
+
+// Add your own Data Access Object.
+builder.Services.AddScoped();
// ProductService.cs
+
public class ProductService : IResourceService
{
private readonly IProductDao _dao;
@@ -128,14 +127,9 @@ public class ArticleService : ICreateService, IDeleteService, ArticleService>();
- services.AddScoped, ArticleService>();
- }
-}
+// Program.cs
+builder.Services.AddScoped, ArticleService>();
+builder.Services.AddScoped, ArticleService>();
```
In v3.0 we introduced an extension method that you can use to register a resource service on all of its JsonApiDotNetCore interfaces.
@@ -144,13 +138,8 @@ This is helpful when you implement (a subset of) the resource interfaces and wan
**Note:** If you're using [auto-discovery](~/usage/resource-graph.md#auto-discovery), this happens automatically.
```c#
-public class Startup
-{
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddResourceService();
- }
-}
+// Program.cs
+builder.Services.AddResourceService();
```
Then on your model, pass in the set of endpoints to expose (the ones that you've registered services for):
diff --git a/docs/usage/meta.md b/docs/usage/meta.md
index 29c074b8b6..a115e25740 100644
--- a/docs/usage/meta.md
+++ b/docs/usage/meta.md
@@ -8,11 +8,12 @@ Global metadata can be added to the root of the response document by registering
This is useful if you need access to other registered services to build the meta object.
```c#
-#nullable enable
+// Program.cs
+builder.Services.AddSingleton();
-// In Startup.ConfigureServices
-services.AddSingleton();
+// CopyrightResponseMeta.cs
+#nullable enable
public sealed class CopyrightResponseMeta : IResponseMeta
{
public IReadOnlyDictionary GetMeta()
diff --git a/docs/usage/options.md b/docs/usage/options.md
index 2f350b8bf9..83d535bce4 100644
--- a/docs/usage/options.md
+++ b/docs/usage/options.md
@@ -1,19 +1,13 @@
# Global Options
-Configuration can be applied when adding the services to the dependency injection container.
+Configuration can be applied when adding services to the dependency injection container at startup.
```c#
-public class Startup
+// Program.cs
+builder.Services.AddJsonApi(options =>
{
- // This method gets called by the runtime. Use this method to add services to the container.
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddJsonApi(options =>
- {
- // configure the options here
- });
- }
-}
+ // Configure the options here...
+});
```
## Client Generated IDs
diff --git a/docs/usage/resource-graph.md b/docs/usage/resource-graph.md
index 4232419bc6..4010cbea5f 100644
--- a/docs/usage/resource-graph.md
+++ b/docs/usage/resource-graph.md
@@ -23,36 +23,27 @@ Auto-discovery refers to the process of reflecting on an assembly and
detecting all of the JSON:API resources, resource definitions, resource services and repositories.
The following command builds the resource graph using all `IIdentifiable` implementations and registers the services mentioned.
-You can enable auto-discovery for the current assembly by adding the following to your `Startup` class.
+You can enable auto-discovery for the current assembly by adding the following at startup.
```c#
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddJsonApi(discovery => discovery.AddCurrentAssembly());
-}
+// Program.cs
+builder.Services.AddJsonApi(discovery => discovery.AddCurrentAssembly());
```
### Specifying an Entity Framework Core DbContext
-If you are using Entity Framework Core as your ORM, you can add all the models of a `DbContext` to the resource graph.
+If you are using Entity Framework Core as your ORM, you can add all the models of a `DbContext` to the resource graph.
```c#
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddJsonApi();
-}
+// Program.cs
+builder.Services.AddJsonApi();
```
Be aware that this does not register resource definitions, resource services and repositories. You can combine it with auto-discovery to achieve this.
```c#
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddJsonApi(discovery => discovery.AddCurrentAssembly());
-}
+// Program.cs
+builder.Services.AddJsonApi(discovery => discovery.AddCurrentAssembly());
```
### Manual Specification
@@ -60,14 +51,9 @@ public void ConfigureServices(IServiceCollection services)
You can manually construct the graph.
```c#
-// Startup.cs
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddJsonApi(resources: builder =>
- {
- builder.Add();
- });
-}
+// Program.cs
+builder.Services.AddJsonApi(resources: resourceGraphBuilder =>
+ resourceGraphBuilder.Add());
```
## Resource Name
@@ -76,9 +62,10 @@ The public resource name is exposed through the `type` member in the JSON:API pa
1. The `publicName` parameter when manually adding a resource to the graph.
```c#
-services.AddJsonApi(resources: builder =>
+// Program.cs
+builder.Services.AddJsonApi(resources: resourceGraphBuilder =>
{
- builder.Add(publicName: "individuals");
+ resourceGraphBuilder.Add(publicName: "individuals");
});
```
diff --git a/docs/usage/routing.md b/docs/usage/routing.md
index c4eb8ae0ba..a264622931 100644
--- a/docs/usage/routing.md
+++ b/docs/usage/routing.md
@@ -7,13 +7,11 @@ An endpoint URL provides access to a resource or a relationship. Resource endpoi
In the relationship endpoint "/articles/1/relationships/comments", "articles" is the left side of the relationship and "comments" the right side.
## Namespacing and versioning of URLs
-You can add a namespace to all URLs by specifying it in ConfigureServices.
+You can add a namespace to all URLs by specifying it at startup.
```c#
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddJsonApi(options => options.Namespace = "api/v1");
-}
+// Program.cs
+builder.Services.AddJsonApi(options => options.Namespace = "api/v1");
```
Which results in URLs like: https://yourdomain.com/api/v1/people
@@ -91,8 +89,6 @@ public class OrderLineController : JsonApiController
It is possible to replace the built-in routing convention with a [custom routing convention](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/application-model?view=aspnetcore-3.1#sample-custom-routing-convention) by registering an implementation of `IJsonApiRoutingConvention`.
```c#
-public void ConfigureServices(IServiceCollection services)
-{
- services.AddSingleton();
-}
+// Program.cs
+builder.Services.AddSingleton();
```
diff --git a/docs/usage/writing/bulk-batch-operations.md b/docs/usage/writing/bulk-batch-operations.md
index 21fe04b636..1ac35fd3fc 100644
--- a/docs/usage/writing/bulk-batch-operations.md
+++ b/docs/usage/writing/bulk-batch-operations.md
@@ -85,10 +85,11 @@ For example requests, see our suite of tests in JsonApiDotNetCoreTests.Integrati
## Configuration
-The maximum number of operations per request defaults to 10, which you can change from Startup.cs:
+The maximum number of operations per request defaults to 10, which you can change at startup:
```c#
-services.AddJsonApi(options => options.MaximumOperationsPerRequest = 250);
+// Program.cs
+builder.Services.AddJsonApi(options => options.MaximumOperationsPerRequest = 250);
```
Or, if you want to allow unconstrained, set it to `null` instead.
diff --git a/src/Examples/GettingStarted/Data/SampleDbContext.cs b/src/Examples/GettingStarted/Data/SampleDbContext.cs
index c5460db810..44662c388b 100644
--- a/src/Examples/GettingStarted/Data/SampleDbContext.cs
+++ b/src/Examples/GettingStarted/Data/SampleDbContext.cs
@@ -2,16 +2,15 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
-namespace GettingStarted.Data
+namespace GettingStarted.Data;
+
+[UsedImplicitly(ImplicitUseTargetFlags.Members)]
+public class SampleDbContext : DbContext
{
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public class SampleDbContext : DbContext
- {
- public DbSet Books => Set();
+ public DbSet Books => Set();
- public SampleDbContext(DbContextOptions options)
- : base(options)
- {
- }
+ public SampleDbContext(DbContextOptions options)
+ : base(options)
+ {
}
}
diff --git a/src/Examples/GettingStarted/GettingStarted.csproj b/src/Examples/GettingStarted/GettingStarted.csproj
index ffffc7b2ee..ab152b79d5 100644
--- a/src/Examples/GettingStarted/GettingStarted.csproj
+++ b/src/Examples/GettingStarted/GettingStarted.csproj
@@ -1,11 +1,12 @@
- $(NetCoreAppVersion)
+ $(TargetFrameworkName)
-
+
diff --git a/src/Examples/GettingStarted/Models/Book.cs b/src/Examples/GettingStarted/Models/Book.cs
index 1e1cb42b2b..66beed1072 100644
--- a/src/Examples/GettingStarted/Models/Book.cs
+++ b/src/Examples/GettingStarted/Models/Book.cs
@@ -2,19 +2,18 @@
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
-namespace GettingStarted.Models
+namespace GettingStarted.Models;
+
+[UsedImplicitly(ImplicitUseTargetFlags.Members)]
+[Resource]
+public sealed class Book : Identifiable
{
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- [Resource]
- public sealed class Book : Identifiable
- {
- [Attr]
- public string Title { get; set; } = null!;
+ [Attr]
+ public string Title { get; set; } = null!;
- [Attr]
- public int PublishYear { get; set; }
+ [Attr]
+ public int PublishYear { get; set; }
- [HasOne]
- public Person Author { get; set; } = null!;
- }
+ [HasOne]
+ public Person Author { get; set; } = null!;
}
diff --git a/src/Examples/GettingStarted/Models/Person.cs b/src/Examples/GettingStarted/Models/Person.cs
index 056d3b0522..89ca4c5a69 100644
--- a/src/Examples/GettingStarted/Models/Person.cs
+++ b/src/Examples/GettingStarted/Models/Person.cs
@@ -1,18 +1,16 @@
-using System.Collections.Generic;
using JetBrains.Annotations;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
-namespace GettingStarted.Models
+namespace GettingStarted.Models;
+
+[UsedImplicitly(ImplicitUseTargetFlags.Members)]
+[Resource]
+public sealed class Person : Identifiable
{
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- [Resource]
- public sealed class Person : Identifiable
- {
- [Attr]
- public string Name { get; set; } = null!;
+ [Attr]
+ public string Name { get; set; } = null!;
- [HasMany]
- public ICollection Books { get; set; } = new List();
- }
+ [HasMany]
+ public ICollection Books { get; set; } = new List();
}
diff --git a/src/Examples/GettingStarted/Program.cs b/src/Examples/GettingStarted/Program.cs
index 68bca0ae86..cf19380c6c 100644
--- a/src/Examples/GettingStarted/Program.cs
+++ b/src/Examples/GettingStarted/Program.cs
@@ -1,21 +1,73 @@
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Hosting;
+using GettingStarted.Data;
+using GettingStarted.Models;
+using JsonApiDotNetCore.Configuration;
-namespace GettingStarted
+WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+builder.Services.AddSqlite("Data Source=sample.db;Pooling=False");
+
+builder.Services.AddJsonApi(options =>
+{
+ options.Namespace = "api";
+ options.UseRelativeLinks = true;
+ options.IncludeTotalResourceCount = true;
+ options.SerializerOptions.WriteIndented = true;
+});
+
+WebApplication app = builder.Build();
+
+// Configure the HTTP request pipeline.
+
+app.UseRouting();
+app.UseJsonApi();
+app.MapControllers();
+
+await CreateDatabaseAsync(app.Services);
+
+app.Run();
+
+static async Task CreateDatabaseAsync(IServiceProvider serviceProvider)
+{
+ await using AsyncServiceScope scope = serviceProvider.CreateAsyncScope();
+
+ var dbContext = scope.ServiceProvider.GetRequiredService();
+ await dbContext.Database.EnsureDeletedAsync();
+ await dbContext.Database.EnsureCreatedAsync();
+
+ await CreateSampleDataAsync(dbContext);
+}
+
+static async Task CreateSampleDataAsync(SampleDbContext dbContext)
{
- internal static class Program
+ // Note: The generate-examples.ps1 script (to create example requests in documentation) depends on these.
+
+ dbContext.Books.AddRange(new Book
{
- public static void Main(string[] args)
+ Title = "Frankenstein",
+ PublishYear = 1818,
+ Author = new Person
{
- CreateHostBuilder(args).Build().Run();
+ Name = "Mary Shelley"
}
-
- private static IHostBuilder CreateHostBuilder(string[] args)
+ }, new Book
+ {
+ Title = "Robinson Crusoe",
+ PublishYear = 1719,
+ Author = new Person
{
- return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>
- {
- webBuilder.UseStartup();
- });
+ Name = "Daniel Defoe"
}
- }
+ }, new Book
+ {
+ Title = "Gulliver's Travels",
+ PublishYear = 1726,
+ Author = new Person
+ {
+ Name = "Jonathan Swift"
+ }
+ });
+
+ await dbContext.SaveChangesAsync();
}
diff --git a/src/Examples/GettingStarted/Startup.cs b/src/Examples/GettingStarted/Startup.cs
deleted file mode 100644
index 13beab63fe..0000000000
--- a/src/Examples/GettingStarted/Startup.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using GettingStarted.Data;
-using GettingStarted.Models;
-using JetBrains.Annotations;
-using JsonApiDotNetCore.Configuration;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace GettingStarted
-{
- public sealed class Startup
- {
- // This method gets called by the runtime. Use this method to add services to the container.
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddDbContext(options => options.UseSqlite("Data Source=sample.db"));
-
- services.AddJsonApi(options =>
- {
- options.Namespace = "api";
- options.UseRelativeLinks = true;
- options.IncludeTotalResourceCount = true;
- options.SerializerOptions.WriteIndented = true;
- });
- }
-
- // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- [UsedImplicitly]
- public void Configure(IApplicationBuilder app, SampleDbContext dbContext)
- {
- dbContext.Database.EnsureDeleted();
- dbContext.Database.EnsureCreated();
- CreateSampleData(dbContext);
-
- app.UseRouting();
- app.UseJsonApi();
- app.UseEndpoints(endpoints => endpoints.MapControllers());
- }
-
- private static void CreateSampleData(SampleDbContext dbContext)
- {
- // Note: The generate-examples.ps1 script (to create example requests in documentation) depends on these.
-
- dbContext.Books.AddRange(new Book
- {
- Title = "Frankenstein",
- PublishYear = 1818,
- Author = new Person
- {
- Name = "Mary Shelley"
- }
- }, new Book
- {
- Title = "Robinson Crusoe",
- PublishYear = 1719,
- Author = new Person
- {
- Name = "Daniel Defoe"
- }
- }, new Book
- {
- Title = "Gulliver's Travels",
- PublishYear = 1726,
- Author = new Person
- {
- Name = "Jonathan Swift"
- }
- });
-
- dbContext.SaveChanges();
- }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/NonJsonApiController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/NonJsonApiController.cs
index b26b82d27d..be5e01b7a9 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Controllers/NonJsonApiController.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/NonJsonApiController.cs
@@ -1,55 +1,52 @@
-using System.IO;
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
-namespace JsonApiDotNetCoreExample.Controllers
+namespace JsonApiDotNetCoreExample.Controllers;
+
+[Route("[controller]")]
+public sealed class NonJsonApiController : ControllerBase
{
- [Route("[controller]")]
- public sealed class NonJsonApiController : ControllerBase
+ [HttpGet]
+ public IActionResult Get()
{
- [HttpGet]
- public IActionResult Get()
- {
- string[] result =
- {
- "Welcome!"
- };
-
- return Ok(result);
- }
-
- [HttpPost]
- public async Task PostAsync()
+ string[] result =
{
- string name = await new StreamReader(Request.Body).ReadToEndAsync();
+ "Welcome!"
+ };
- if (string.IsNullOrEmpty(name))
- {
- return BadRequest("Please send your name.");
- }
+ return Ok(result);
+ }
- string result = $"Hello, {name}";
- return Ok(result);
- }
+ [HttpPost]
+ public async Task PostAsync()
+ {
+ string name = await new StreamReader(Request.Body).ReadToEndAsync();
- [HttpPut]
- public IActionResult Put([FromBody] string name)
+ if (string.IsNullOrEmpty(name))
{
- string result = $"Hi, {name}";
- return Ok(result);
+ return BadRequest("Please send your name.");
}
- [HttpPatch]
- public IActionResult Patch(string name)
- {
- string result = $"Good day, {name}";
- return Ok(result);
- }
+ string result = $"Hello, {name}";
+ return Ok(result);
+ }
- [HttpDelete]
- public IActionResult Delete()
- {
- return Ok("Bye.");
- }
+ [HttpPut]
+ public IActionResult Put([FromBody] string name)
+ {
+ string result = $"Hi, {name}";
+ return Ok(result);
+ }
+
+ [HttpPatch]
+ public IActionResult Patch(string name)
+ {
+ string result = $"Good day, {name}";
+ return Ok(result);
+ }
+
+ [HttpDelete]
+ public IActionResult Delete()
+ {
+ return Ok("Bye.");
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs
index 3d29f72af1..6dd2bcb9ba 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs
@@ -3,16 +3,14 @@
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Middleware;
using JsonApiDotNetCore.Resources;
-using Microsoft.Extensions.Logging;
-namespace JsonApiDotNetCoreExample.Controllers
+namespace JsonApiDotNetCoreExample.Controllers;
+
+public sealed class OperationsController : JsonApiOperationsController
{
- 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 OperationsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor,
- IJsonApiRequest request, ITargetedFields targetedFields)
- : base(options, resourceGraph, loggerFactory, processor, request, targetedFields)
- {
- }
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
index f9f6752990..24378e3182 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
@@ -4,29 +4,28 @@
// @formatter:wrap_chained_method_calls chop_always
-namespace JsonApiDotNetCoreExample.Data
+namespace JsonApiDotNetCoreExample.Data;
+
+[UsedImplicitly(ImplicitUseTargetFlags.Members)]
+public sealed class AppDbContext : DbContext
{
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public sealed class AppDbContext : DbContext
- {
- public DbSet TodoItems => Set();
+ public DbSet TodoItems => Set();
- public AppDbContext(DbContextOptions options)
- : base(options)
- {
- }
+ public AppDbContext(DbContextOptions options)
+ : base(options)
+ {
+ }
- protected override void OnModelCreating(ModelBuilder builder)
- {
- // When deleting a person, un-assign him/her from existing todo items.
- builder.Entity()
- .HasMany(person => person.AssignedTodoItems)
- .WithOne(todoItem => todoItem.Assignee!);
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ // When deleting a person, un-assign him/her from existing todo items.
+ builder.Entity()
+ .HasMany(person => person.AssignedTodoItems)
+ .WithOne(todoItem => todoItem.Assignee!);
- // When deleting a person, the todo items he/she owns are deleted too.
- builder.Entity()
- .HasOne(todoItem => todoItem.Owner)
- .WithMany();
- }
+ // When deleting a person, the todo items he/she owns are deleted too.
+ builder.Entity()
+ .HasOne(todoItem => todoItem.Owner)
+ .WithMany();
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs
index 306315d05f..ee7b874fc4 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs
@@ -1,6 +1,4 @@
using System.ComponentModel;
-using System.Threading;
-using System.Threading.Tasks;
using JetBrains.Annotations;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Middleware;
@@ -9,45 +7,44 @@
using JsonApiDotNetCoreExample.Models;
using Microsoft.AspNetCore.Authentication;
-namespace JsonApiDotNetCoreExample.Definitions
+namespace JsonApiDotNetCoreExample.Definitions;
+
+[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
+public sealed class TodoItemDefinition : JsonApiResourceDefinition
{
- [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
- public sealed class TodoItemDefinition : JsonApiResourceDefinition
+ private readonly ISystemClock _systemClock;
+
+ public TodoItemDefinition(IResourceGraph resourceGraph, ISystemClock systemClock)
+ : base(resourceGraph)
{
- private readonly ISystemClock _systemClock;
+ _systemClock = systemClock;
+ }
- public TodoItemDefinition(IResourceGraph resourceGraph, ISystemClock systemClock)
- : base(resourceGraph)
- {
- _systemClock = systemClock;
- }
+ public override SortExpression OnApplySort(SortExpression? existingSort)
+ {
+ return existingSort ?? GetDefaultSortOrder();
+ }
- public override SortExpression OnApplySort(SortExpression? existingSort)
+ private SortExpression GetDefaultSortOrder()
+ {
+ return CreateSortExpressionFromLambda(new PropertySortOrder
{
- return existingSort ?? GetDefaultSortOrder();
- }
+ (todoItem => todoItem.Priority, ListSortDirection.Descending),
+ (todoItem => todoItem.LastModifiedAt, ListSortDirection.Descending)
+ });
+ }
- private SortExpression GetDefaultSortOrder()
+ public override Task OnWritingAsync(TodoItem resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
+ {
+ if (writeOperation == WriteOperationKind.CreateResource)
{
- return CreateSortExpressionFromLambda(new PropertySortOrder
- {
- (todoItem => todoItem.Priority, ListSortDirection.Descending),
- (todoItem => todoItem.LastModifiedAt, ListSortDirection.Descending)
- });
+ resource.CreatedAt = _systemClock.UtcNow;
}
-
- public override Task OnWritingAsync(TodoItem resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
+ else if (writeOperation == WriteOperationKind.UpdateResource)
{
- if (writeOperation == WriteOperationKind.CreateResource)
- {
- resource.CreatedAt = _systemClock.UtcNow;
- }
- else if (writeOperation == WriteOperationKind.UpdateResource)
- {
- resource.LastModifiedAt = _systemClock.UtcNow;
- }
-
- return Task.CompletedTask;
+ resource.LastModifiedAt = _systemClock.UtcNow;
}
+
+ return Task.CompletedTask;
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj
index 8e2baedf85..b243e99ec2 100644
--- a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj
+++ b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj
@@ -1,6 +1,6 @@
- $(NetCoreAppVersion)
+ $(TargetFrameworkName)
@@ -11,6 +11,6 @@
-
+
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs
index 6814db7f1c..5415d37bb3 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs
@@ -1,21 +1,19 @@
-using System.Collections.Generic;
using JetBrains.Annotations;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
-namespace JsonApiDotNetCoreExample.Models
+namespace JsonApiDotNetCoreExample.Models;
+
+[UsedImplicitly(ImplicitUseTargetFlags.Members)]
+[Resource]
+public sealed class Person : Identifiable
{
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- [Resource]
- public sealed class Person : Identifiable
- {
- [Attr]
- public string? FirstName { get; set; }
+ [Attr]
+ public string? FirstName { get; set; }
- [Attr]
- public string LastName { get; set; } = null!;
+ [Attr]
+ public string LastName { get; set; } = null!;
- [HasMany]
- public ISet AssignedTodoItems { get; set; } = new HashSet();
- }
+ [HasMany]
+ public ISet AssignedTodoItems { get; set; } = new HashSet();
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Tag.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Tag.cs
index a620c31759..9095b0af80 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Models/Tag.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/Tag.cs
@@ -1,20 +1,18 @@
-using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
-namespace JsonApiDotNetCoreExample.Models
+namespace JsonApiDotNetCoreExample.Models;
+
+[UsedImplicitly(ImplicitUseTargetFlags.Members)]
+[Resource]
+public sealed class Tag : Identifiable
{
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- [Resource]
- public sealed class Tag : Identifiable
- {
- [Attr]
- [MinLength(1)]
- public string Name { get; set; } = null!;
+ [Attr]
+ [MinLength(1)]
+ public string Name { get; set; } = null!;
- [HasMany]
- public ISet TodoItems { get; set; } = new HashSet();
- }
+ [HasMany]
+ public ISet TodoItems { get; set; } = new HashSet();
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs
index 872322b0b3..9be7e6e64e 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs
@@ -1,36 +1,33 @@
-using System;
-using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
-namespace JsonApiDotNetCoreExample.Models
+namespace JsonApiDotNetCoreExample.Models;
+
+[UsedImplicitly(ImplicitUseTargetFlags.Members)]
+[Resource]
+public sealed class TodoItem : Identifiable
{
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- [Resource]
- public sealed class TodoItem : Identifiable
- {
- [Attr]
- public string Description { get; set; } = null!;
+ [Attr]
+ public string Description { get; set; } = null!;
- [Attr]
- [Required]
- public TodoItemPriority? Priority { get; set; }
+ [Attr]
+ [Required]
+ public TodoItemPriority? Priority { get; set; }
- [Attr(Capabilities = AttrCapabilities.AllowFilter | AttrCapabilities.AllowSort | AttrCapabilities.AllowView)]
- public DateTimeOffset CreatedAt { get; set; }
+ [Attr(Capabilities = AttrCapabilities.AllowFilter | AttrCapabilities.AllowSort | AttrCapabilities.AllowView)]
+ public DateTimeOffset CreatedAt { get; set; }
- [Attr(PublicName = "modifiedAt", Capabilities = AttrCapabilities.AllowFilter | AttrCapabilities.AllowSort | AttrCapabilities.AllowView)]
- public DateTimeOffset? LastModifiedAt { get; set; }
+ [Attr(PublicName = "modifiedAt", Capabilities = AttrCapabilities.AllowFilter | AttrCapabilities.AllowSort | AttrCapabilities.AllowView)]
+ public DateTimeOffset? LastModifiedAt { get; set; }
- [HasOne]
- public Person Owner { get; set; } = null!;
+ [HasOne]
+ public Person Owner { get; set; } = null!;
- [HasOne]
- public Person? Assignee { get; set; }
+ [HasOne]
+ public Person? Assignee { get; set; }
- [HasMany]
- public ISet Tags { get; set; } = new HashSet();
- }
+ [HasMany]
+ public ISet