diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 1c7cf727cb..571278c30c 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"jetbrains.resharper.globaltools": {
- "version": "2021.1.4",
+ "version": "2021.2.2",
"commands": [
"jb"
]
diff --git a/Build.ps1 b/Build.ps1
index ee1dd68cfb..0cca69c095 100644
--- a/Build.ps1
+++ b/Build.ps1
@@ -8,7 +8,7 @@ function CheckLastExitCode {
function RunInspectCode {
$outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml')
- dotnet jb inspectcode JsonApiDotNetCore.sln --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal
+ dotnet jb inspectcode JsonApiDotNetCore.sln --no-build --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal
CheckLastExitCode
[xml]$xml = Get-Content "$outputPath"
diff --git a/CSharpGuidelinesAnalyzer.config b/CSharpGuidelinesAnalyzer.config
index acd0856299..89b568e155 100644
--- a/CSharpGuidelinesAnalyzer.config
+++ b/CSharpGuidelinesAnalyzer.config
@@ -1,5 +1,5 @@
-
+
diff --git a/Directory.Build.props b/Directory.Build.props
index bec8ba912e..895d25aaf5 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -6,10 +6,11 @@
5.0.*
$(MSBuildThisFileDirectory)CodingGuidelines.ruleset
9999
+ enable
-
+
@@ -24,9 +25,9 @@
33.1.1
3.1.0
- 6.1.0
+ 6.2.0
4.16.1
2.4.*
- 16.11.0
+ 17.0.0
diff --git a/README.md b/README.md
index 39ea16b3be..8f6c6f3eb1 100644
--- a/README.md
+++ b/README.md
@@ -87,13 +87,13 @@ 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).
-| .NET version | EF Core version | JsonApiDotNetCore version |
-| ------------ | --------------- | ------------------------- |
-| Core 2.x | 2.x | 3.x |
-| Core 3.1 | 3.1 | 4.x |
-| Core 3.1 | 5 | 4.x |
-| 5 | 5 | 4.x or 5.x |
-| 6 | 6 | 5.x |
+| .NET version | Entity Framework Core version | JsonApiDotNetCore version |
+| ------------ | ----------------------------- | ------------------------- |
+| Core 2.x | 2.x | 3.x |
+| Core 3.1 | 3.1 | 4.x |
+| Core 3.1 | 5 | 4.x |
+| 5 | 5 | 4.x or 5.x |
+| 6 | 6 | 5.x |
## Contributing
diff --git a/ROADMAP.md b/ROADMAP.md
index b3eb75daa6..d96e015953 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -21,7 +21,7 @@ The need for breaking changes has blocked several efforts in the v4.x release, s
- [x] Optimized delete to-many [#1030](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1030)
- [x] Support System.Text.Json [#664](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/664) [#999](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/999) [1077](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1077) [1078](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1078)
- [x] Optimize IIdentifiable to ResourceObject conversion [#1028](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1028) [#1024](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1024) [#233](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/233)
-- [ ] Nullable reference types [#1029](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1029)
+- [x] Nullable reference types [#1029](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1029)
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/BenchmarkResource.cs b/benchmarks/BenchmarkResource.cs
deleted file mode 100644
index ad18d999c7..0000000000
--- a/benchmarks/BenchmarkResource.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using JetBrains.Annotations;
-using JsonApiDotNetCore.Resources;
-using JsonApiDotNetCore.Resources.Annotations;
-
-namespace Benchmarks
-{
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public sealed class BenchmarkResource : Identifiable
- {
- [Attr(PublicName = BenchmarkResourcePublicNames.NameAttr)]
- public string Name { get; set; }
-
- [HasOne]
- public SubResource Child { get; set; }
- }
-}
diff --git a/benchmarks/BenchmarkResourcePublicNames.cs b/benchmarks/BenchmarkResourcePublicNames.cs
deleted file mode 100644
index 84b63e7668..0000000000
--- a/benchmarks/BenchmarkResourcePublicNames.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma warning disable AV1008 // Class should not be static
-
-namespace Benchmarks
-{
- internal static class BenchmarkResourcePublicNames
- {
- public const string NameAttr = "full-name";
- public const string Type = "simple-types";
- }
-}
diff --git a/benchmarks/Benchmarks.csproj b/benchmarks/Benchmarks.csproj
index 4b19516001..225c3a75d7 100644
--- a/benchmarks/Benchmarks.csproj
+++ b/benchmarks/Benchmarks.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/benchmarks/DependencyFactory.cs b/benchmarks/DependencyFactory.cs
deleted file mode 100644
index 184ba5a082..0000000000
--- a/benchmarks/DependencyFactory.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using JsonApiDotNetCore.Configuration;
-using Microsoft.Extensions.Logging.Abstractions;
-
-namespace Benchmarks
-{
- internal sealed class DependencyFactory
- {
- public IResourceGraph CreateResourceGraph(IJsonApiOptions options)
- {
- var builder = new ResourceGraphBuilder(options, NullLoggerFactory.Instance);
-
- builder.Add(BenchmarkResourcePublicNames.Type);
- builder.Add();
-
- return builder.Build();
- }
- }
-}
diff --git a/benchmarks/Deserialization/DeserializationBenchmarkBase.cs b/benchmarks/Deserialization/DeserializationBenchmarkBase.cs
index 5ceff85a2d..8b2deb98b9 100644
--- a/benchmarks/Deserialization/DeserializationBenchmarkBase.cs
+++ b/benchmarks/Deserialization/DeserializationBenchmarkBase.cs
@@ -21,7 +21,7 @@ public abstract class DeserializationBenchmarkBase
protected DeserializationBenchmarkBase()
{
var options = new JsonApiOptions();
- IResourceGraph resourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build();
+ IResourceGraph resourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build();
options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph));
SerializerReadOptions = ((IJsonApiOptions)options).SerializerReadOptions;
@@ -30,7 +30,9 @@ protected DeserializationBenchmarkBase()
var resourceDefinitionAccessor = new ResourceDefinitionAccessor(resourceGraph, serviceContainer);
serviceContainer.AddService(typeof(IResourceDefinitionAccessor), resourceDefinitionAccessor);
- serviceContainer.AddService(typeof(IResourceDefinition), new JsonApiResourceDefinition(resourceGraph));
+
+ serviceContainer.AddService(typeof(IResourceDefinition),
+ new JsonApiResourceDefinition(resourceGraph));
// ReSharper disable once VirtualMemberCallInConstructor
JsonApiRequest request = CreateJsonApiRequest(resourceGraph);
@@ -56,7 +58,7 @@ protected DeserializationBenchmarkBase()
protected abstract JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph);
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public sealed class ResourceA : Identifiable
+ public sealed class IncomingResource : Identifiable
{
[Attr]
public bool Attribute01 { get; set; }
@@ -74,7 +76,7 @@ public sealed class ResourceA : Identifiable
public float? Attribute05 { get; set; }
[Attr]
- public string Attribute06 { get; set; }
+ public string Attribute06 { get; set; } = null!;
[Attr]
public DateTime? Attribute07 { get; set; }
@@ -89,34 +91,34 @@ public sealed class ResourceA : Identifiable
public DayOfWeek Attribute10 { get; set; }
[HasOne]
- public ResourceA Single1 { get; set; }
+ public IncomingResource Single1 { get; set; } = null!;
[HasOne]
- public ResourceA Single2 { get; set; }
+ public IncomingResource Single2 { get; set; } = null!;
[HasOne]
- public ResourceA Single3 { get; set; }
+ public IncomingResource Single3 { get; set; } = null!;
[HasOne]
- public ResourceA Single4 { get; set; }
+ public IncomingResource Single4 { get; set; } = null!;
[HasOne]
- public ResourceA Single5 { get; set; }
+ public IncomingResource Single5 { get; set; } = null!;
[HasMany]
- public ISet Multi1 { get; set; }
+ public ISet Multi1 { get; set; } = null!;
[HasMany]
- public ISet Multi2 { get; set; }
+ public ISet Multi2 { get; set; } = null!;
[HasMany]
- public ISet Multi3 { get; set; }
+ public ISet Multi3 { get; set; } = null!;
[HasMany]
- public ISet Multi4 { get; set; }
+ public ISet Multi4 { get; set; } = null!;
[HasMany]
- public ISet Multi5 { get; set; }
+ public ISet Multi5 { get; set; } = null!;
}
}
}
diff --git a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs
index c09b7c77c7..0181f4ccbc 100644
--- a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs
+++ b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs
@@ -20,7 +20,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
op = "add",
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
lid = "a-1",
attributes = new
{
@@ -41,7 +41,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "101"
}
},
@@ -49,7 +49,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "102"
}
},
@@ -57,7 +57,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "103"
}
},
@@ -65,7 +65,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "104"
}
},
@@ -73,7 +73,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "105"
}
},
@@ -83,7 +83,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "201"
}
}
@@ -94,7 +94,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "202"
}
}
@@ -105,7 +105,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "203"
}
}
@@ -116,7 +116,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "204"
}
}
@@ -127,7 +127,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "205"
}
}
@@ -140,7 +140,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
op = "update",
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "1",
attributes = new
{
@@ -161,7 +161,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "101"
}
},
@@ -169,7 +169,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "102"
}
},
@@ -177,7 +177,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "103"
}
},
@@ -185,7 +185,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "104"
}
},
@@ -193,7 +193,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "105"
}
},
@@ -203,7 +203,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "201"
}
}
@@ -214,7 +214,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "202"
}
}
@@ -225,7 +225,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "203"
}
}
@@ -236,7 +236,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "204"
}
}
@@ -247,7 +247,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "205"
}
}
@@ -260,7 +260,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
op = "remove",
@ref = new
{
- type = "resourceAs",
+ type = "incomingResources",
lid = "a-1"
}
}
@@ -268,15 +268,15 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
}).Replace("atomic__operations", "atomic:operations");
[Benchmark]
- public object DeserializeOperationsRequest()
+ public object? DeserializeOperationsRequest()
{
- var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions);
+ var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions)!;
return DocumentAdapter.Convert(document);
}
protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
{
- return new()
+ return new JsonApiRequest
{
Kind = EndpointKind.AtomicOperations
};
diff --git a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs
index d3fe50ffa6..e154306819 100644
--- a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs
+++ b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs
@@ -15,7 +15,7 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
attributes = new
{
attribute01 = true,
@@ -35,7 +35,7 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "101"
}
},
@@ -43,7 +43,7 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "102"
}
},
@@ -51,7 +51,7 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "103"
}
},
@@ -59,7 +59,7 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "104"
}
},
@@ -67,7 +67,7 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
{
data = new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "105"
}
},
@@ -77,7 +77,7 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "201"
}
}
@@ -88,7 +88,7 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "202"
}
}
@@ -99,7 +99,7 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "203"
}
}
@@ -110,7 +110,7 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "204"
}
}
@@ -121,7 +121,7 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
{
new
{
- type = "resourceAs",
+ type = "incomingResources",
id = "205"
}
}
@@ -131,19 +131,18 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
});
[Benchmark]
- public object DeserializeResourceRequest()
+ public object? DeserializeResourceRequest()
{
- var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions);
-
+ var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions)!;
return DocumentAdapter.Convert(document);
}
protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
{
- return new()
+ return new JsonApiRequest
{
Kind = EndpointKind.Primary,
- PrimaryResourceType = resourceGraph.GetResourceType(),
+ PrimaryResourceType = resourceGraph.GetResourceType(),
WriteOperation = WriteOperationKind.CreateResource
};
}
diff --git a/benchmarks/LinkBuilding/LinkBuilderGetNamespaceFromPathBenchmarks.cs b/benchmarks/LinkBuilding/LinkBuilderGetNamespaceFromPathBenchmarks.cs
deleted file mode 100644
index 400fc0dbcf..0000000000
--- a/benchmarks/LinkBuilding/LinkBuilderGetNamespaceFromPathBenchmarks.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-using System;
-using System.Text;
-using BenchmarkDotNet.Attributes;
-
-namespace Benchmarks.LinkBuilding
-{
- // ReSharper disable once ClassCanBeSealed.Global
- [MarkdownExporter]
- [SimpleJob(3, 10, 20)]
- [MemoryDiagnoser]
- public class LinkBuilderGetNamespaceFromPathBenchmarks
- {
- private const string RequestPath = "/api/some-really-long-namespace-path/resources/current/articles/?some";
- private const string ResourceName = "articles";
- private const char PathDelimiter = '/';
-
- [Benchmark]
- public void UsingStringSplit()
- {
- GetNamespaceFromPathUsingStringSplit(RequestPath, ResourceName);
- }
-
- [Benchmark]
- public void UsingReadOnlySpan()
- {
- GetNamespaceFromPathUsingReadOnlySpan(RequestPath, ResourceName);
- }
-
- private static void GetNamespaceFromPathUsingStringSplit(string path, string resourceName)
- {
- var namespaceBuilder = new StringBuilder(path.Length);
- string[] segments = path.Split('/');
-
- for (int index = 1; index < segments.Length; index++)
- {
- if (segments[index] == resourceName)
- {
- break;
- }
-
- namespaceBuilder.Append(PathDelimiter);
- namespaceBuilder.Append(segments[index]);
- }
-
- _ = namespaceBuilder.ToString();
- }
-
- private static void GetNamespaceFromPathUsingReadOnlySpan(string path, string resourceName)
- {
- ReadOnlySpan resourceNameSpan = resourceName.AsSpan();
- ReadOnlySpan pathSpan = path.AsSpan();
-
- for (int index = 0; index < pathSpan.Length; index++)
- {
- if (pathSpan[index].Equals(PathDelimiter))
- {
- if (pathSpan.Length > index + resourceNameSpan.Length)
- {
- ReadOnlySpan possiblePathSegment = pathSpan.Slice(index + 1, resourceNameSpan.Length);
-
- if (resourceNameSpan.SequenceEqual(possiblePathSegment))
- {
- int lastCharacterIndex = index + 1 + resourceNameSpan.Length;
-
- bool isAtEnd = lastCharacterIndex == pathSpan.Length;
- bool hasDelimiterAfterSegment = pathSpan.Length >= lastCharacterIndex + 1 && pathSpan[lastCharacterIndex].Equals(PathDelimiter);
-
- if (isAtEnd || hasDelimiterAfterSegment)
- {
- _ = pathSpan[..index].ToString();
- }
- }
- }
- }
- }
- }
- }
-}
diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs
index 995538eb76..45406133dd 100644
--- a/benchmarks/Program.cs
+++ b/benchmarks/Program.cs
@@ -1,7 +1,6 @@
using BenchmarkDotNet.Running;
using Benchmarks.Deserialization;
-using Benchmarks.LinkBuilding;
-using Benchmarks.Query;
+using Benchmarks.QueryString;
using Benchmarks.Serialization;
namespace Benchmarks
@@ -16,8 +15,7 @@ private static void Main(string[] args)
typeof(OperationsDeserializationBenchmarks),
typeof(ResourceSerializationBenchmarks),
typeof(OperationsSerializationBenchmarks),
- typeof(QueryParserBenchmarks),
- typeof(LinkBuilderGetNamespaceFromPathBenchmarks)
+ typeof(QueryStringParserBenchmarks)
});
switcher.Run(args);
diff --git a/benchmarks/Query/QueryParserBenchmarks.cs b/benchmarks/QueryString/QueryStringParserBenchmarks.cs
similarity index 54%
rename from benchmarks/Query/QueryParserBenchmarks.cs
rename to benchmarks/QueryString/QueryStringParserBenchmarks.cs
index bb6cec20e8..6e576bc4a7 100644
--- a/benchmarks/Query/QueryParserBenchmarks.cs
+++ b/benchmarks/QueryString/QueryStringParserBenchmarks.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.ComponentModel.Design;
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore;
@@ -12,51 +11,32 @@
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging.Abstractions;
-namespace Benchmarks.Query
+namespace Benchmarks.QueryString
{
// ReSharper disable once ClassCanBeSealed.Global
[MarkdownExporter]
[SimpleJob(3, 10, 20)]
[MemoryDiagnoser]
- public class QueryParserBenchmarks
+ public class QueryStringParserBenchmarks
{
- private readonly DependencyFactory _dependencyFactory = new();
private readonly FakeRequestQueryStringAccessor _queryStringAccessor = new();
- private readonly QueryStringReader _queryStringReaderForSort;
- private readonly QueryStringReader _queryStringReaderForAll;
+ private readonly QueryStringReader _queryStringReader;
- public QueryParserBenchmarks()
+ public QueryStringParserBenchmarks()
{
IJsonApiOptions options = new JsonApiOptions
{
EnableLegacyFilterNotation = true
};
- IResourceGraph resourceGraph = _dependencyFactory.CreateResourceGraph(options);
+ IResourceGraph resourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add("alt-resource-name").Build();
var request = new JsonApiRequest
{
- PrimaryResourceType = resourceGraph.GetResourceType(typeof(BenchmarkResource)),
+ PrimaryResourceType = resourceGraph.GetResourceType(typeof(QueryableResource)),
IsCollection = true
};
- _queryStringReaderForSort = CreateQueryParameterDiscoveryForSort(resourceGraph, request, options, _queryStringAccessor);
- _queryStringReaderForAll = CreateQueryParameterDiscoveryForAll(resourceGraph, request, options, _queryStringAccessor);
- }
-
- private static QueryStringReader CreateQueryParameterDiscoveryForSort(IResourceGraph resourceGraph, JsonApiRequest request, IJsonApiOptions options,
- FakeRequestQueryStringAccessor queryStringAccessor)
- {
- var sortReader = new SortQueryStringParameterReader(request, resourceGraph);
-
- IEnumerable readers = sortReader.AsEnumerable();
-
- return new QueryStringReader(options, queryStringAccessor, readers, NullLoggerFactory.Instance);
- }
-
- private static QueryStringReader CreateQueryParameterDiscoveryForAll(IResourceGraph resourceGraph, JsonApiRequest request, IJsonApiOptions options,
- FakeRequestQueryStringAccessor queryStringAccessor)
- {
var resourceFactory = new ResourceFactory(new ServiceContainer());
var includeReader = new IncludeQueryStringParameterReader(request, resourceGraph, options);
@@ -68,25 +48,25 @@ private static QueryStringReader CreateQueryParameterDiscoveryForAll(IResourceGr
IQueryStringParameterReader[] readers = ArrayFactory.Create(includeReader, filterReader, sortReader,
sparseFieldSetReader, paginationReader);
- return new QueryStringReader(options, queryStringAccessor, readers, NullLoggerFactory.Instance);
+ _queryStringReader = new QueryStringReader(options, _queryStringAccessor, readers, NullLoggerFactory.Instance);
}
[Benchmark]
public void AscendingSort()
{
- string queryString = $"?sort={BenchmarkResourcePublicNames.NameAttr}";
+ const string queryString = "?sort=alt-attr-name";
_queryStringAccessor.SetQueryString(queryString);
- _queryStringReaderForSort.ReadAll(null);
+ _queryStringReader.ReadAll(null);
}
[Benchmark]
public void DescendingSort()
{
- string queryString = $"?sort=-{BenchmarkResourcePublicNames.NameAttr}";
+ const string queryString = "?sort=-alt-attr-name";
_queryStringAccessor.SetQueryString(queryString);
- _queryStringReaderForSort.ReadAll(null);
+ _queryStringReader.ReadAll(null);
}
[Benchmark]
@@ -94,13 +74,11 @@ public void ComplexQuery()
{
Run(100, () =>
{
- const string resourceName = BenchmarkResourcePublicNames.Type;
- const string attrName = BenchmarkResourcePublicNames.NameAttr;
-
- string queryString = $"?filter[{attrName}]=abc,eq:abc&sort=-{attrName}&include=child&page[size]=1&fields[{resourceName}]={attrName}";
+ 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);
- _queryStringReaderForAll.ReadAll(null);
+ _queryStringReader.ReadAll(null);
});
}
@@ -114,7 +92,7 @@ private void Run(int iterations, Action action)
private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor
{
- public IQueryCollection Query { get; private set; }
+ public IQueryCollection Query { get; private set; } = new QueryCollection();
public void SetQueryString(string queryString)
{
diff --git a/benchmarks/QueryString/QueryableResource.cs b/benchmarks/QueryString/QueryableResource.cs
new file mode 100644
index 0000000000..bcf0a5075a
--- /dev/null
+++ b/benchmarks/QueryString/QueryableResource.cs
@@ -0,0 +1,16 @@
+using JetBrains.Annotations;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Resources.Annotations;
+
+namespace Benchmarks.QueryString
+{
+ [UsedImplicitly(ImplicitUseTargetFlags.Members)]
+ public sealed class QueryableResource : Identifiable
+ {
+ [Attr(PublicName = "alt-attr-name")]
+ public string? Name { get; set; }
+
+ [HasOne]
+ public QueryableResource? Child { get; set; }
+ }
+}
diff --git a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs
index fbcdf0b0a9..fef0d67a12 100644
--- a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs
+++ b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs
@@ -26,7 +26,7 @@ public OperationsSerializationBenchmarks()
private static IEnumerable CreateResponseOperations(IJsonApiRequest request)
{
- var resource1 = new ResourceA
+ var resource1 = new OutgoingResource
{
Id = 1,
Attribute01 = true,
@@ -41,7 +41,7 @@ private static IEnumerable CreateResponseOperations(IJsonApi
Attribute10 = DayOfWeek.Sunday
};
- var resource2 = new ResourceA
+ var resource2 = new OutgoingResource
{
Id = 2,
Attribute01 = false,
@@ -56,7 +56,7 @@ private static IEnumerable CreateResponseOperations(IJsonApi
Attribute10 = DayOfWeek.Monday
};
- var resource3 = new ResourceA
+ var resource3 = new OutgoingResource
{
Id = 3,
Attribute01 = true,
@@ -71,7 +71,7 @@ private static IEnumerable CreateResponseOperations(IJsonApi
Attribute10 = DayOfWeek.Tuesday
};
- var resource4 = new ResourceA
+ var resource4 = new OutgoingResource
{
Id = 4,
Attribute01 = false,
@@ -86,7 +86,7 @@ private static IEnumerable CreateResponseOperations(IJsonApi
Attribute10 = DayOfWeek.Wednesday
};
- var resource5 = new ResourceA
+ var resource5 = new OutgoingResource
{
Id = 5,
Attribute01 = true,
@@ -122,10 +122,10 @@ public string SerializeOperationsResponse()
protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
{
- return new()
+ return new JsonApiRequest
{
Kind = EndpointKind.AtomicOperations,
- PrimaryResourceType = resourceGraph.GetResourceType()
+ PrimaryResourceType = resourceGraph.GetResourceType()
};
}
diff --git a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs
index 8f538cc9a2..2877d96713 100644
--- a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs
+++ b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs
@@ -17,11 +17,11 @@ namespace Benchmarks.Serialization
// ReSharper disable once ClassCanBeSealed.Global
public class ResourceSerializationBenchmarks : SerializationBenchmarkBase
{
- private static readonly ResourceA ResponseResource = CreateResponseResource();
+ private static readonly OutgoingResource ResponseResource = CreateResponseResource();
- private static ResourceA CreateResponseResource()
+ private static OutgoingResource CreateResponseResource()
{
- var resource1 = new ResourceA
+ var resource1 = new OutgoingResource
{
Id = 1,
Attribute01 = true,
@@ -36,7 +36,7 @@ private static ResourceA CreateResponseResource()
Attribute10 = DayOfWeek.Sunday
};
- var resource2 = new ResourceA
+ var resource2 = new OutgoingResource
{
Id = 2,
Attribute01 = false,
@@ -51,7 +51,7 @@ private static ResourceA CreateResponseResource()
Attribute10 = DayOfWeek.Monday
};
- var resource3 = new ResourceA
+ var resource3 = new OutgoingResource
{
Id = 3,
Attribute01 = true,
@@ -66,7 +66,7 @@ private static ResourceA CreateResponseResource()
Attribute10 = DayOfWeek.Tuesday
};
- var resource4 = new ResourceA
+ var resource4 = new OutgoingResource
{
Id = 4,
Attribute01 = false,
@@ -81,7 +81,7 @@ private static ResourceA CreateResponseResource()
Attribute10 = DayOfWeek.Wednesday
};
- var resource5 = new ResourceA
+ var resource5 = new OutgoingResource
{
Id = 5,
Attribute01 = true,
@@ -113,21 +113,21 @@ public string SerializeResourceResponse()
protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)
{
- return new()
+ return new JsonApiRequest
{
Kind = EndpointKind.Primary,
- PrimaryResourceType = resourceGraph.GetResourceType()
+ PrimaryResourceType = resourceGraph.GetResourceType()
};
}
protected override IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceGraph resourceGraph)
{
- ResourceType resourceAType = resourceGraph.GetResourceType();
+ ResourceType resourceAType = resourceGraph.GetResourceType();
- RelationshipAttribute single2 = resourceAType.GetRelationshipByPropertyName(nameof(ResourceA.Single2));
- RelationshipAttribute single3 = resourceAType.GetRelationshipByPropertyName(nameof(ResourceA.Single3));
- RelationshipAttribute multi4 = resourceAType.GetRelationshipByPropertyName(nameof(ResourceA.Multi4));
- RelationshipAttribute multi5 = resourceAType.GetRelationshipByPropertyName(nameof(ResourceA.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 = ArrayFactory.Create(single2, single3, multi4, multi5).ToImmutableArray();
IEnumerable chains = new ResourceFieldChainExpression(chain).AsEnumerable();
diff --git a/benchmarks/Serialization/SerializationBenchmarkBase.cs b/benchmarks/Serialization/SerializationBenchmarkBase.cs
index 41fa9776b6..2abde17e42 100644
--- a/benchmarks/Serialization/SerializationBenchmarkBase.cs
+++ b/benchmarks/Serialization/SerializationBenchmarkBase.cs
@@ -40,7 +40,7 @@ protected SerializationBenchmarkBase()
}
};
- ResourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build();
+ ResourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build();
SerializerWriteOptions = ((IJsonApiOptions)options).SerializerWriteOptions;
// ReSharper disable VirtualMemberCallInConstructor
@@ -64,7 +64,7 @@ protected SerializationBenchmarkBase()
protected abstract IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceGraph resourceGraph);
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public sealed class ResourceA : Identifiable
+ public sealed class OutgoingResource : Identifiable
{
[Attr]
public bool Attribute01 { get; set; }
@@ -82,7 +82,7 @@ public sealed class ResourceA : Identifiable
public float? Attribute05 { get; set; }
[Attr]
- public string Attribute06 { get; set; }
+ public string Attribute06 { get; set; } = null!;
[Attr]
public DateTime? Attribute07 { get; set; }
@@ -97,34 +97,34 @@ public sealed class ResourceA : Identifiable
public DayOfWeek Attribute10 { get; set; }
[HasOne]
- public ResourceA Single1 { get; set; }
+ public OutgoingResource Single1 { get; set; } = null!;
[HasOne]
- public ResourceA Single2 { get; set; }
+ public OutgoingResource Single2 { get; set; } = null!;
[HasOne]
- public ResourceA Single3 { get; set; }
+ public OutgoingResource Single3 { get; set; } = null!;
[HasOne]
- public ResourceA Single4 { get; set; }
+ public OutgoingResource Single4 { get; set; } = null!;
[HasOne]
- public ResourceA Single5 { get; set; }
+ public OutgoingResource Single5 { get; set; } = null!;
[HasMany]
- public ISet Multi1 { get; set; }
+ public ISet Multi1 { get; set; } = null!;
[HasMany]
- public ISet Multi2 { get; set; }
+ public ISet Multi2 { get; set; } = null!;
[HasMany]
- public ISet Multi3 { get; set; }
+ public ISet Multi3 { get; set; } = null!;
[HasMany]
- public ISet Multi4 { get; set; }
+ public ISet Multi4 { get; set; } = null!;
[HasMany]
- public ISet Multi5 { get; set; }
+ public ISet Multi5 { get; set; } = null!;
}
private sealed class FakeResourceDefinitionAccessor : IResourceDefinitionAccessor
@@ -134,32 +134,32 @@ public IImmutableSet OnApplyIncludes(ResourceType reso
return existingIncludes;
}
- public FilterExpression OnApplyFilter(ResourceType resourceType, FilterExpression existingFilter)
+ public FilterExpression? OnApplyFilter(ResourceType resourceType, FilterExpression? existingFilter)
{
return existingFilter;
}
- public SortExpression OnApplySort(ResourceType resourceType, SortExpression existingSort)
+ public SortExpression? OnApplySort(ResourceType resourceType, SortExpression? existingSort)
{
return existingSort;
}
- public PaginationExpression OnApplyPagination(ResourceType resourceType, PaginationExpression existingPagination)
+ public PaginationExpression? OnApplyPagination(ResourceType resourceType, PaginationExpression? existingPagination)
{
return existingPagination;
}
- public SparseFieldSetExpression OnApplySparseFieldSet(ResourceType resourceType, SparseFieldSetExpression existingSparseFieldSet)
+ public SparseFieldSetExpression? OnApplySparseFieldSet(ResourceType resourceType, SparseFieldSetExpression? existingSparseFieldSet)
{
return existingSparseFieldSet;
}
- public object GetQueryableHandlerForQueryStringParameter(Type resourceClrType, string parameterName)
+ public object? GetQueryableHandlerForQueryStringParameter(Type resourceClrType, string parameterName)
{
return null;
}
- public IDictionary GetMeta(ResourceType resourceType, IIdentifiable resourceInstance)
+ public IDictionary? GetMeta(ResourceType resourceType, IIdentifiable resourceInstance)
{
return null;
}
@@ -170,8 +170,8 @@ public Task OnPrepareWriteAsync(TResource resource, WriteOperationKin
return Task.CompletedTask;
}
- public Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship,
- IIdentifiable rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken)
+ public Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship,
+ IIdentifiable? rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken)
where TResource : class, IIdentifiable
{
return Task.FromResult(rightResourceId);
@@ -223,7 +223,7 @@ private sealed class FakeLinkBuilder : ILinkBuilder
{
public TopLevelLinks GetTopLevelLinks()
{
- return new()
+ return new TopLevelLinks
{
Self = "TopLevel:Self"
};
@@ -231,15 +231,15 @@ public TopLevelLinks GetTopLevelLinks()
public ResourceLinks GetResourceLinks(ResourceType resourceType, string id)
{
- return new()
+ return new ResourceLinks
{
Self = "Resource:Self"
};
}
- public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable leftResource)
+ public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, string leftId)
{
- return new()
+ return new RelationshipLinks
{
Self = "Relationship:Self",
Related = "Relationship:Related"
@@ -249,11 +249,11 @@ public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship
private sealed class FakeMetaBuilder : IMetaBuilder
{
- public void Add(IReadOnlyDictionary values)
+ public void Add(IReadOnlyDictionary values)
{
}
- public IDictionary Build()
+ public IDictionary? Build()
{
return null;
}
diff --git a/benchmarks/SubResource.cs b/benchmarks/SubResource.cs
deleted file mode 100644
index 9d0f95bbcf..0000000000
--- a/benchmarks/SubResource.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using JetBrains.Annotations;
-using JsonApiDotNetCore.Resources;
-using JsonApiDotNetCore.Resources.Annotations;
-
-namespace Benchmarks
-{
- [UsedImplicitly(ImplicitUseTargetFlags.Members)]
- public sealed class SubResource : Identifiable
- {
- [Attr]
- public string Value { get; set; }
- }
-}
diff --git a/cleanupcode.ps1 b/cleanupcode.ps1
index 605ebff705..dccf70ebc1 100644
--- a/cleanupcode.ps1
+++ b/cleanupcode.ps1
@@ -8,10 +8,10 @@ if ($LASTEXITCODE -ne 0) {
throw "Tool restore failed with exit code $LASTEXITCODE"
}
-dotnet build -c Release
+dotnet restore
if ($LASTEXITCODE -ne 0) {
- throw "Build failed with exit code $LASTEXITCODE"
+ throw "Package restore failed with exit code $LASTEXITCODE"
}
dotnet regitlint -s JsonApiDotNetCore.sln --print-command --jb --profile --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN
diff --git a/docs/getting-started/step-by-step.md b/docs/getting-started/step-by-step.md
index 75661780cc..adc9bdf8e4 100644
--- a/docs/getting-started/step-by-step.md
+++ b/docs/getting-started/step-by-step.md
@@ -69,9 +69,9 @@ where `TResource` is the model that inherits from `Identifiable`.
```c#
public class PeopleController : JsonApiController
{
- public PeopleController(IJsonApiOptions options, ILoggerFactory loggerFactory,
- IResourceService resourceService)
- : base(options, loggerFactory, resourceService)
+ public PeopleController(IJsonApiOptions options, IResourceGraph resourceGraph,
+ ILoggerFactory loggerFactory, IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
{
}
}
diff --git a/docs/internals/queries.md b/docs/internals/queries.md
index b5e5c2cf19..46005f489c 100644
--- a/docs/internals/queries.md
+++ b/docs/internals/queries.md
@@ -5,7 +5,7 @@ _since v4.0_
The query pipeline roughly looks like this:
```
-HTTP --[ASP.NET Core]--> QueryString --[JADNC:QueryStringParameterReader]--> QueryExpression[] --[JADNC:ResourceService]--> QueryLayer --[JADNC:Repository]--> IQueryable --[EF Core]--> SQL
+HTTP --[ASP.NET]--> QueryString --[JADNC:QueryStringParameterReader]--> QueryExpression[] --[JADNC:ResourceService]--> QueryLayer --[JADNC:Repository]--> IQueryable --[Entity Framework Core]--> SQL
```
Processing a request involves the following steps:
@@ -22,7 +22,7 @@ Processing a request involves the following steps:
- `JsonApiResourceService` contains no more usage of `IQueryable`.
- `EntityFrameworkCoreRepository` delegates to `QueryableBuilder` to transform the `QueryLayer` tree into `IQueryable` expression trees.
`QueryBuilder` depends on `QueryClauseBuilder` implementations that visit the tree nodes, transforming them to `System.Linq.Expression` equivalents.
- The `IQueryable` expression trees are executed by EF Core, which produces SQL statements out of them.
+ The `IQueryable` expression trees are executed by Entity Framework Core, which produces SQL statements out of them.
- `JsonApiWriter` transforms resource objects into json response.
# Example
diff --git a/docs/usage/extensibility/controllers.md b/docs/usage/extensibility/controllers.md
index 2d5d24cec9..9fc9d380f1 100644
--- a/docs/usage/extensibility/controllers.md
+++ b/docs/usage/extensibility/controllers.md
@@ -5,9 +5,9 @@ You need to create controllers that inherit from `JsonApiController
{
- public ArticlesController(IJsonApiOptions options, ILoggerFactory loggerFactory,
- IResourceService resourceService)
- : base(options, loggerFactory, resourceService)
+ public ArticlesController(IJsonApiOptions options, IResourceGraph resourceGraph,
+ ILoggerFactory loggerFactory, IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
{
}
}
@@ -24,9 +24,9 @@ This approach is ok, but introduces some boilerplate that can easily be avoided.
```c#
public class ArticlesController : BaseJsonApiController
{
- public ArticlesController(IJsonApiOptions options, ILoggerFactory loggerFactory,
- IResourceService resourceService)
- : base(options, loggerFactory, resourceService)
+ public ArticlesController(IJsonApiOptions options, IResourceGraph resourceGraph,
+ ILoggerFactory loggerFactory, IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
{
}
@@ -61,9 +61,9 @@ An attempt to use one of the blacklisted methods will result in a HTTP 405 Metho
[HttpReadOnly]
public class ArticlesController : BaseJsonApiController
{
- public ArticlesController(IJsonApiOptions options, ILoggerFactory loggerFactory,
- IResourceService resourceService)
- : base(options, loggerFactory, resourceService)
+ public ArticlesController(IJsonApiOptions options, IResourceGraph resourceGraph,
+ ILoggerFactory loggerFactory, IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
{
}
}
@@ -80,9 +80,9 @@ For more information about resource service injection, see [Replacing injected s
```c#
public class ReportsController : BaseJsonApiController
{
- public ReportsController(IJsonApiOptions options, ILoggerFactory loggerFactory,
- IGetAllService getAllService)
- : base(options, loggerFactory, getAllService)
+ public ReportsController(IJsonApiOptions options, IResourceGraph resourceGraph,
+ ILoggerFactory loggerFactory, IGetAllService getAllService)
+ : base(options, resourceGraph, loggerFactory, getAllService)
{
}
diff --git a/docs/usage/extensibility/resource-definitions.md b/docs/usage/extensibility/resource-definitions.md
index f56442fcac..4c9eeeb8a6 100644
--- a/docs/usage/extensibility/resource-definitions.md
+++ b/docs/usage/extensibility/resource-definitions.md
@@ -223,7 +223,7 @@ _since v3_
You can define additional query string parameters with the LINQ expression that should be used.
If the key is present in a query string, the supplied LINQ expression will be added to the database query.
-Note this directly influences the Entity Framework Core `IQueryable`. As opposed to using `OnApplyFilter`, this enables the full range of EF Core operators.
+Note this directly influences the Entity Framework Core `IQueryable`. As opposed to using `OnApplyFilter`, this enables the full range of Entity Framework Core operators.
But it only works on primary resource endpoints (for example: /articles, but not on /blogs/1/articles or /blogs?include=articles).
```c#
diff --git a/docs/usage/extensibility/services.md b/docs/usage/extensibility/services.md
index 83bb7e5ffa..1842a44606 100644
--- a/docs/usage/extensibility/services.md
+++ b/docs/usage/extensibility/services.md
@@ -156,9 +156,10 @@ Then in the controller, you should inherit from the base controller and pass the
```c#
public class ArticlesController : BaseJsonApiController
{
- public ArticlesController(IJsonApiOptions options, ILoggerFactory loggerFactory,
- ICreateService create, IDeleteService delete)
- : base(options, loggerFactory, create: create, delete: delete)
+ public ArticlesController(IJsonApiOptions options, IResourceGraph resourceGraph,
+ ILoggerFactory loggerFactory, ICreateService create,
+ IDeleteService delete)
+ : base(options, resourceGraph, loggerFactory, create: create, delete: delete)
{
}
diff --git a/docs/usage/options.md b/docs/usage/options.md
index 54eb7f0d3b..8b1ee5bae8 100644
--- a/docs/usage/options.md
+++ b/docs/usage/options.md
@@ -100,9 +100,12 @@ options.SerializerOptions.DictionaryKeyPolicy = null;
Because we copy resource properties into an intermediate object before serialization, JSON annotations such as `[JsonPropertyName]` and `[JsonIgnore]` on `[Attr]` properties are ignored.
-## Enable ModelState Validation
+## ModelState Validation
-If you would like to use ASP.NET Core ModelState validation into your controllers when creating / updating resources, set `ValidateModelState` to `true`. By default, no model validation is performed.
+[ASP.NET ModelState validation](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation) can be used to validate incoming request bodies when creating and updating resources. Since v5.0, this is enabled by default.
+When `ValidateModelState` is set to `false`, no model validation is performed.
+
+How nullability affects ModelState validation is described [here](~/usage/resources/nullability.md).
```c#
options.ValidateModelState = true;
@@ -115,5 +118,8 @@ public class Person : Identifiable
[Required]
[MinLength(3)]
public string FirstName { get; set; }
+
+ [Required]
+ public LoginAccount Account : get; set; }
}
```
diff --git a/docs/usage/resources/nullability.md b/docs/usage/resources/nullability.md
new file mode 100644
index 0000000000..c3ec59eab6
--- /dev/null
+++ b/docs/usage/resources/nullability.md
@@ -0,0 +1,85 @@
+# Nullability in resources
+
+Properties on a resource class can be declared as nullable or non-nullable. This affects both ASP.NET ModelState validation and the way Entity Framework Core generates database columns.
+
+ModelState validation is enabled by default since v5.0. In earlier versions, it can be enabled in [options](~/usage/options.md#enable-modelstate-validation).
+
+# Value types
+
+When ModelState validation is enabled, non-nullable value types will **not** trigger a validation error when omitted in the request body.
+To make JsonApiDotNetCore return an error when such a property is missing on resource creation, declare it as nullable and annotate it with `[Required]`.
+
+Example:
+
+```c#
+public sealed class User : Identifiable
+{
+ [Attr]
+ [Required]
+ public bool? IsAdministrator { get; set; }
+}
+```
+
+This makes Entity Framework Core generate non-nullable columns. And model errors are returned when nullable fields are omitted.
+
+# Reference types
+
+When the [nullable reference types](https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references) (NRT) compiler feature is enabled, it affects both ASP.NET ModelState validation and Entity Framework Core.
+
+## NRT turned off
+
+When NRT is turned off, use `[Required]` on required attributes and relationships. This makes Entity Framework Core generate non-nullable columns. And model errors are returned when required fields are omitted.
+
+Example:
+
+```c#
+public sealed class Label : Identifiable
+{
+ [Attr]
+ [Required]
+ public string Name { get; set; }
+
+ [Attr]
+ public string RgbColor { get; set; }
+
+ [HasOne]
+ [Required]
+ public Person Creator { get; set; }
+
+ [HasOne]
+ public Label Parent { get; set; }
+
+ [HasMany]
+ public ISet TodoItems { get; set; }
+}
+```
+
+## NRT turned on
+
+When NRT is turned on, use nullability annotations (?) on attributes and relationships. This makes Entity Framework Core generate non-nullable columns. And model errors are returned when non-nullable fields are omitted.
+
+The [Entity Framework Core guide on NRT](https://docs.microsoft.com/en-us/ef/core/miscellaneous/nullable-reference-types) recommends to use constructor binding to initialize non-nullable properties, but JsonApiDotNetCore does not support that. For required navigation properties, it suggests to use a non-nullable property with a nullable backing field. JsonApiDotNetCore does not support that either. In both cases, just use the null-forgiving operator (!).
+
+When ModelState validation is turned on, to-many relationships must be assigned an empty collection. Otherwise an error is returned when they don't occur in the request body.
+
+Example:
+
+```c#
+public sealed class Label : Identifiable
+{
+ [Attr]
+ public string Name { get; set; } = null!;
+
+ [Attr]
+ public string? RgbColor { get; set; }
+
+ [HasOne]
+ public Person Creator { get; set; } = null!;
+
+ [HasOne]
+ public Label? Parent { get; set; }
+
+ [HasMany]
+ public ISet TodoItems { get; set; } = new HashSet();
+}
+```
diff --git a/docs/usage/resources/relationships.md b/docs/usage/resources/relationships.md
index 24d3e834ff..662e8a1a3d 100644
--- a/docs/usage/resources/relationships.md
+++ b/docs/usage/resources/relationships.md
@@ -20,6 +20,84 @@ public class TodoItem : Identifiable
The left side of this relationship is of type `TodoItem` (public name: "todoItems") and the right side is of type `Person` (public name: "persons").
+### Required one-to-one relationships in Entity Framework Core
+
+By default, Entity Framework Core generates an identifying foreign key for a required 1-to-1 relationship.
+This means no foreign key column is generated, instead the primary keys point to each other directly.
+
+The next example defines that each car requires an engine, while an engine is optionally linked to a car.
+
+```c#
+public sealed class Car : Identifiable
+{
+ [HasOne]
+ public Engine Engine { get; set; }
+}
+
+public sealed class Engine : Identifiable
+{
+ [HasOne]
+ public Car Car { get; set; }
+}
+
+public sealed class AppDbContext : DbContext
+{
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ builder.Entity()
+ .HasOne(car => car.Engine)
+ .WithOne(engine => engine.Car)
+ .HasForeignKey()
+ .IsRequired();
+ }
+}
+```
+
+Which results in Entity Framework Core generating the next database objects:
+```sql
+CREATE TABLE "Engine" (
+ "Id" integer GENERATED BY DEFAULT AS IDENTITY,
+ CONSTRAINT "PK_Engine" PRIMARY KEY ("Id")
+);
+CREATE TABLE "Cars" (
+ "Id" integer NOT NULL,
+ CONSTRAINT "PK_Cars" PRIMARY KEY ("Id"),
+ CONSTRAINT "FK_Cars_Engine_Id" FOREIGN KEY ("Id") REFERENCES "Engine" ("Id")
+ ON DELETE CASCADE
+);
+```
+
+That mechanism does not make sense for JSON:API, because patching a relationship would result in also
+changing the identity of a resource. Naming the foreign key explicitly fixes the problem by forcing to
+create a foreign key column.
+
+```c#
+protected override void OnModelCreating(ModelBuilder builder)
+{
+ builder.Entity()
+ .HasOne(car => car.Engine)
+ .WithOne(engine => engine.Car)
+ .HasForeignKey("EngineId") // Explicit foreign key name added
+ .IsRequired();
+}
+```
+
+Which generates the correct database objects:
+```sql
+CREATE TABLE "Engine" (
+ "Id" integer GENERATED BY DEFAULT AS IDENTITY,
+ CONSTRAINT "PK_Engine" PRIMARY KEY ("Id")
+);
+CREATE TABLE "Cars" (
+ "Id" integer GENERATED BY DEFAULT AS IDENTITY,
+ "EngineId" integer NOT NULL,
+ CONSTRAINT "PK_Cars" PRIMARY KEY ("Id"),
+ CONSTRAINT "FK_Cars_Engine_EngineId" FOREIGN KEY ("EngineId") REFERENCES "Engine" ("Id")
+ ON DELETE CASCADE
+);
+CREATE UNIQUE INDEX "IX_Cars_EngineId" ON "Cars" ("EngineId");
+```
+
## HasMany
This exposes a to-many relationship.
diff --git a/docs/usage/routing.md b/docs/usage/routing.md
index 79ad1a1e05..c68914a04a 100644
--- a/docs/usage/routing.md
+++ b/docs/usage/routing.md
@@ -29,9 +29,9 @@ public class OrderLine : Identifiable
public class OrderLineController : JsonApiController
{
- public OrderLineController(IJsonApiOptions options, ILoggerFactory loggerFactory,
- IResourceService resourceService)
- : base(options, loggerFactory, resourceService)
+ public OrderLineController(IJsonApiOptions options, IResourceGraph resourceGraph,
+ ILoggerFactory loggerFactory, IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
{
}
}
@@ -65,9 +65,9 @@ It is possible to bypass the default routing convention for a controller.
[Route("v1/custom/route/lines-in-order"), DisableRoutingConvention]
public class OrderLineController : JsonApiController
{
- public OrderLineController(IJsonApiOptions options, ILoggerFactory loggerFactory,
- IResourceService resourceService)
- : base(options, loggerFactory, resourceService)
+ public OrderLineController(IJsonApiOptions options, IResourceGraph resourceGraph,
+ ILoggerFactory loggerFactory, IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
{
}
}
diff --git a/docs/usage/toc.md b/docs/usage/toc.md
index a8b5473007..f6924036ea 100644
--- a/docs/usage/toc.md
+++ b/docs/usage/toc.md
@@ -1,6 +1,7 @@
# [Resources](resources/index.md)
## [Attributes](resources/attributes.md)
## [Relationships](resources/relationships.md)
+## [Nullability](resources/nullability.md)
# Reading data
## [Filtering](reading/filtering.md)
diff --git a/docs/usage/writing/bulk-batch-operations.md b/docs/usage/writing/bulk-batch-operations.md
index 549ff68025..21fe04b636 100644
--- a/docs/usage/writing/bulk-batch-operations.md
+++ b/docs/usage/writing/bulk-batch-operations.md
@@ -17,10 +17,10 @@ To enable operations, add a controller to your project that inherits from `JsonA
```c#
public sealed class OperationsController : JsonApiOperationsController
{
- public OperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory,
- IOperationsProcessor processor, IJsonApiRequest request,
- ITargetedFields targetedFields)
- : base(options, 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/inspectcode.ps1 b/inspectcode.ps1
index ab4b9c95dd..6c9d90768e 100644
--- a/inspectcode.ps1
+++ b/inspectcode.ps1
@@ -8,15 +8,9 @@ if ($LASTEXITCODE -ne 0) {
throw "Tool restore failed with exit code $LASTEXITCODE"
}
-dotnet build -c Release
-
-if ($LASTEXITCODE -ne 0) {
- throw "Build failed with exit code $LASTEXITCODE"
-}
-
$outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml')
$resultPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.html')
-dotnet jb inspectcode JsonApiDotNetCore.sln --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal
+dotnet jb inspectcode JsonApiDotNetCore.sln --build --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal
if ($LASTEXITCODE -ne 0) {
throw "Code inspection failed with exit code $LASTEXITCODE"
diff --git a/src/Examples/GettingStarted/Controllers/BooksController.cs b/src/Examples/GettingStarted/Controllers/BooksController.cs
index 928dddc82b..3f049429cd 100644
--- a/src/Examples/GettingStarted/Controllers/BooksController.cs
+++ b/src/Examples/GettingStarted/Controllers/BooksController.cs
@@ -8,8 +8,8 @@ namespace GettingStarted.Controllers
{
public sealed class BooksController : JsonApiController
{
- public BooksController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService)
- : base(options, loggerFactory, resourceService)
+ public BooksController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
{
}
}
diff --git a/src/Examples/GettingStarted/Controllers/PeopleController.cs b/src/Examples/GettingStarted/Controllers/PeopleController.cs
index cef47189ae..e7a5537f14 100644
--- a/src/Examples/GettingStarted/Controllers/PeopleController.cs
+++ b/src/Examples/GettingStarted/Controllers/PeopleController.cs
@@ -8,8 +8,9 @@ namespace GettingStarted.Controllers
{
public sealed class PeopleController : JsonApiController
{
- public PeopleController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService)
- : base(options, loggerFactory, resourceService)
+ public PeopleController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
{
}
}
diff --git a/src/Examples/GettingStarted/Data/SampleDbContext.cs b/src/Examples/GettingStarted/Data/SampleDbContext.cs
index b54011ff14..c5460db810 100644
--- a/src/Examples/GettingStarted/Data/SampleDbContext.cs
+++ b/src/Examples/GettingStarted/Data/SampleDbContext.cs
@@ -7,16 +7,11 @@ namespace GettingStarted.Data
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
public class SampleDbContext : DbContext
{
- public DbSet Books { get; set; }
+ public DbSet Books => Set();
public SampleDbContext(DbContextOptions options)
: base(options)
{
}
-
- protected override void OnModelCreating(ModelBuilder builder)
- {
- builder.Entity();
- }
}
}
diff --git a/src/Examples/GettingStarted/Models/Book.cs b/src/Examples/GettingStarted/Models/Book.cs
index f636dd54e1..0957461cd7 100644
--- a/src/Examples/GettingStarted/Models/Book.cs
+++ b/src/Examples/GettingStarted/Models/Book.cs
@@ -8,12 +8,12 @@ namespace GettingStarted.Models
public sealed class Book : Identifiable
{
[Attr]
- public string Title { get; set; }
+ public string Title { get; set; } = null!;
[Attr]
public int PublishYear { get; set; }
[HasOne]
- public Person Author { get; set; }
+ public Person Author { get; set; } = null!;
}
}
diff --git a/src/Examples/GettingStarted/Models/Person.cs b/src/Examples/GettingStarted/Models/Person.cs
index 52a95ac330..f9b8e55fff 100644
--- a/src/Examples/GettingStarted/Models/Person.cs
+++ b/src/Examples/GettingStarted/Models/Person.cs
@@ -9,9 +9,9 @@ namespace GettingStarted.Models
public sealed class Person : Identifiable
{
[Attr]
- public string Name { get; set; }
+ public string Name { get; set; } = null!;
[HasMany]
- public ICollection Books { get; set; }
+ public ICollection Books { get; set; } = new List();
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs
index 4851336a9a..3d29f72af1 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/OperationsController.cs
@@ -9,9 +9,9 @@ namespace JsonApiDotNetCoreExample.Controllers
{
public sealed class OperationsController : JsonApiOperationsController
{
- public OperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request,
- ITargetedFields targetedFields)
- : base(options, 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/Controllers/PeopleController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs
index 36c772b29b..0ebafd1767 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs
@@ -8,8 +8,9 @@ namespace JsonApiDotNetCoreExample.Controllers
{
public sealed class PeopleController : JsonApiController
{
- public PeopleController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService)
- : base(options, loggerFactory, resourceService)
+ public PeopleController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
{
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TagsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TagsController.cs
index 98b5051774..b08af4e399 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TagsController.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TagsController.cs
@@ -8,8 +8,8 @@ namespace JsonApiDotNetCoreExample.Controllers
{
public sealed class TagsController : JsonApiController
{
- public TagsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService)
- : base(options, loggerFactory, resourceService)
+ public TagsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
{
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs
index 0eaa59b1ca..c862853302 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs
@@ -8,8 +8,9 @@ namespace JsonApiDotNetCoreExample.Controllers
{
public sealed class TodoItemsController : JsonApiController
{
- public TodoItemsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService)
- : base(options, loggerFactory, resourceService)
+ public TodoItemsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
{
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
index cc59628fc6..f9f6752990 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
@@ -9,7 +9,7 @@ namespace JsonApiDotNetCoreExample.Data
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
public sealed class AppDbContext : DbContext
{
- public DbSet TodoItems { get; set; }
+ public DbSet TodoItems => Set();
public AppDbContext(DbContextOptions options)
: base(options)
@@ -21,16 +21,12 @@ 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)
- .IsRequired(false)
- .OnDelete(DeleteBehavior.SetNull);
+ .WithOne(todoItem => todoItem.Assignee!);
// When deleting a person, the todo items he/she owns are deleted too.
builder.Entity()
.HasOne(todoItem => todoItem.Owner)
- .WithMany()
- .IsRequired()
- .OnDelete(DeleteBehavior.Cascade);
+ .WithMany();
}
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs
index ddf19c5a43..306315d05f 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs
@@ -22,7 +22,7 @@ public TodoItemDefinition(IResourceGraph resourceGraph, ISystemClock systemClock
_systemClock = systemClock;
}
- public override SortExpression OnApplySort(SortExpression existingSort)
+ public override SortExpression OnApplySort(SortExpression? existingSort)
{
return existingSort ?? GetDefaultSortOrder();
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs
index 4d7d3369fa..44be2df864 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs
@@ -9,12 +9,12 @@ namespace JsonApiDotNetCoreExample.Models
public sealed class Person : Identifiable
{
[Attr]
- public string FirstName { get; set; }
+ public string? FirstName { get; set; }
[Attr]
- public string LastName { get; set; }
+ public string LastName { get; set; } = null!;
[HasMany]
- public ISet AssignedTodoItems { get; set; }
+ public ISet AssignedTodoItems { get; set; } = new HashSet();
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Tag.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Tag.cs
index 03fb527c4b..713eafe605 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Models/Tag.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/Tag.cs
@@ -9,12 +9,11 @@ namespace JsonApiDotNetCoreExample.Models
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
public sealed class Tag : Identifiable
{
- [Required]
- [MinLength(1)]
[Attr]
- public string Name { get; set; }
+ [MinLength(1)]
+ public string Name { get; set; } = null!;
[HasMany]
- public ISet TodoItems { get; set; }
+ public ISet TodoItems { get; set; } = new HashSet();
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs
index cc6341bdb2..5c4d5c6ea1 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
@@ -10,10 +11,11 @@ namespace JsonApiDotNetCoreExample.Models
public sealed class TodoItem : Identifiable
{
[Attr]
- public string Description { get; set; }
+ public string Description { get; set; } = null!;
[Attr]
- public TodoItemPriority Priority { get; set; }
+ [Required]
+ public TodoItemPriority? Priority { get; set; }
[Attr(Capabilities = AttrCapabilities.AllowFilter | AttrCapabilities.AllowSort | AttrCapabilities.AllowView)]
public DateTimeOffset CreatedAt { get; set; }
@@ -22,12 +24,12 @@ public sealed class TodoItem : Identifiable
public DateTimeOffset? LastModifiedAt { get; set; }
[HasOne]
- public Person Owner { get; set; }
+ public Person Owner { get; set; } = null!;
[HasOne]
- public Person Assignee { get; set; }
+ public Person? Assignee { get; set; }
[HasMany]
- public ISet Tags { get; set; }
+ public ISet Tags { get; set; } = new HashSet();
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs
index f14c1df8ce..84ebc30360 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs
@@ -49,7 +49,6 @@ public void ConfigureServices(IServiceCollection services)
{
options.Namespace = "api/v1";
options.UseRelativeLinks = true;
- options.ValidateModelState = true;
options.IncludeTotalResourceCount = true;
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
diff --git a/src/Examples/MultiDbContextExample/Controllers/ResourceAsController.cs b/src/Examples/MultiDbContextExample/Controllers/ResourceAsController.cs
index 6fc542cf1d..5fd3c662a4 100644
--- a/src/Examples/MultiDbContextExample/Controllers/ResourceAsController.cs
+++ b/src/Examples/MultiDbContextExample/Controllers/ResourceAsController.cs
@@ -8,8 +8,9 @@ namespace MultiDbContextExample.Controllers
{
public sealed class ResourceAsController : JsonApiController
{
- public ResourceAsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService)
- : base(options, loggerFactory, resourceService)
+ public ResourceAsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
{
}
}
diff --git a/src/Examples/MultiDbContextExample/Controllers/ResourceBsController.cs b/src/Examples/MultiDbContextExample/Controllers/ResourceBsController.cs
index 0316fbde1b..33b89aa9ec 100644
--- a/src/Examples/MultiDbContextExample/Controllers/ResourceBsController.cs
+++ b/src/Examples/MultiDbContextExample/Controllers/ResourceBsController.cs
@@ -8,8 +8,9 @@ namespace MultiDbContextExample.Controllers
{
public sealed class ResourceBsController : JsonApiController
{
- public ResourceBsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService)
- : base(options, loggerFactory, resourceService)
+ public ResourceBsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
{
}
}
diff --git a/src/Examples/MultiDbContextExample/Data/DbContextA.cs b/src/Examples/MultiDbContextExample/Data/DbContextA.cs
index cb6000e051..23b2f4a37c 100644
--- a/src/Examples/MultiDbContextExample/Data/DbContextA.cs
+++ b/src/Examples/MultiDbContextExample/Data/DbContextA.cs
@@ -7,7 +7,7 @@ namespace MultiDbContextExample.Data
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
public sealed class DbContextA : DbContext
{
- public DbSet ResourceAs { get; set; }
+ public DbSet ResourceAs => Set();
public DbContextA(DbContextOptions options)
: base(options)
diff --git a/src/Examples/MultiDbContextExample/Data/DbContextB.cs b/src/Examples/MultiDbContextExample/Data/DbContextB.cs
index b3e4e6e47f..bf9c575fa9 100644
--- a/src/Examples/MultiDbContextExample/Data/DbContextB.cs
+++ b/src/Examples/MultiDbContextExample/Data/DbContextB.cs
@@ -7,7 +7,7 @@ namespace MultiDbContextExample.Data
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
public sealed class DbContextB : DbContext
{
- public DbSet ResourceBs { get; set; }
+ public DbSet ResourceBs => Set();
public DbContextB(DbContextOptions options)
: base(options)
diff --git a/src/Examples/MultiDbContextExample/Models/ResourceA.cs b/src/Examples/MultiDbContextExample/Models/ResourceA.cs
index f536237f14..1c754be6ed 100644
--- a/src/Examples/MultiDbContextExample/Models/ResourceA.cs
+++ b/src/Examples/MultiDbContextExample/Models/ResourceA.cs
@@ -8,6 +8,6 @@ namespace MultiDbContextExample.Models
public sealed class ResourceA : Identifiable
{
[Attr]
- public string NameA { get; set; }
+ public string? NameA { get; set; }
}
}
diff --git a/src/Examples/MultiDbContextExample/Models/ResourceB.cs b/src/Examples/MultiDbContextExample/Models/ResourceB.cs
index 55a3d79a59..70941a1f4d 100644
--- a/src/Examples/MultiDbContextExample/Models/ResourceB.cs
+++ b/src/Examples/MultiDbContextExample/Models/ResourceB.cs
@@ -8,6 +8,6 @@ namespace MultiDbContextExample.Models
public sealed class ResourceB : Identifiable
{
[Attr]
- public string NameB { get; set; }
+ public string? NameB { get; set; }
}
}
diff --git a/src/Examples/MultiDbContextExample/Properties/launchSettings.json b/src/Examples/MultiDbContextExample/Properties/launchSettings.json
index 6d7e1b5cbd..e328cc07be 100644
--- a/src/Examples/MultiDbContextExample/Properties/launchSettings.json
+++ b/src/Examples/MultiDbContextExample/Properties/launchSettings.json
@@ -1,4 +1,4 @@
-{
+{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
@@ -12,7 +12,7 @@
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": false,
- "launchUrl": "/resourceBs",
+ "launchUrl": "resourceBs",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@@ -20,7 +20,7 @@
"Kestrel": {
"commandName": "Project",
"launchBrowser": false,
- "launchUrl": "/resourceBs",
+ "launchUrl": "resourceBs",
"applicationUrl": "https://localhost:44350;http://localhost:14150",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
diff --git a/src/Examples/NoEntityFrameworkExample/Controllers/WorkItemsController.cs b/src/Examples/NoEntityFrameworkExample/Controllers/WorkItemsController.cs
index d50fa1183f..055fa60ed8 100644
--- a/src/Examples/NoEntityFrameworkExample/Controllers/WorkItemsController.cs
+++ b/src/Examples/NoEntityFrameworkExample/Controllers/WorkItemsController.cs
@@ -8,8 +8,9 @@ namespace NoEntityFrameworkExample.Controllers
{
public sealed class WorkItemsController : JsonApiController
{
- public WorkItemsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService)
- : base(options, loggerFactory, resourceService)
+ public WorkItemsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory,
+ IResourceService resourceService)
+ : base(options, resourceGraph, loggerFactory, resourceService)
{
}
}
diff --git a/src/Examples/NoEntityFrameworkExample/Data/AppDbContext.cs b/src/Examples/NoEntityFrameworkExample/Data/AppDbContext.cs
index 336951eec3..bfe2115f7d 100644
--- a/src/Examples/NoEntityFrameworkExample/Data/AppDbContext.cs
+++ b/src/Examples/NoEntityFrameworkExample/Data/AppDbContext.cs
@@ -7,7 +7,7 @@ namespace NoEntityFrameworkExample.Data
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
public sealed class AppDbContext : DbContext
{
- public DbSet WorkItems { get; set; }
+ public DbSet WorkItems => Set();
public AppDbContext(DbContextOptions options)
: base(options)
diff --git a/src/Examples/NoEntityFrameworkExample/Models/WorkItem.cs b/src/Examples/NoEntityFrameworkExample/Models/WorkItem.cs
index 98aa8a9fd9..083894fd04 100644
--- a/src/Examples/NoEntityFrameworkExample/Models/WorkItem.cs
+++ b/src/Examples/NoEntityFrameworkExample/Models/WorkItem.cs
@@ -12,7 +12,7 @@ public sealed class WorkItem : Identifiable
public bool IsBlocked { get; set; }
[Attr]
- public string Title { get; set; }
+ public string Title { get; set; } = null!;
[Attr]
public long DurationInHours { get; set; }
diff --git a/src/Examples/NoEntityFrameworkExample/Properties/launchSettings.json b/src/Examples/NoEntityFrameworkExample/Properties/launchSettings.json
index 32bc82dfc2..d28c050bd8 100644
--- a/src/Examples/NoEntityFrameworkExample/Properties/launchSettings.json
+++ b/src/Examples/NoEntityFrameworkExample/Properties/launchSettings.json
@@ -12,7 +12,7 @@
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": false,
- "launchUrl": "/api/reports",
+ "launchUrl": "api/v1/workItems",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@@ -20,7 +20,7 @@
"Kestrel": {
"commandName": "Project",
"launchBrowser": false,
- "launchUrl": "/api/reports",
+ "launchUrl": "api/v1/workItems",
"applicationUrl": "https://localhost:44349;http://localhost:14149",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
diff --git a/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs b/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs
index 45c1b9a83a..5fbd062b11 100644
--- a/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs
+++ b/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs
@@ -46,17 +46,17 @@ public async Task GetAsync(int id, CancellationToken cancellationToken
return workItems.Single();
}
- public Task