diff --git a/Build.ps1 b/Build.ps1
index e614f84c29..9798b5ee6e 100644
--- a/Build.ps1
+++ b/Build.ps1
@@ -19,7 +19,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" --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal
+ dotnet jb inspectcode JsonApiDotNetCore.sln --output="$outputPath" --profile=JsonApiDotNetCore-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
new file mode 100644
index 0000000000..acd0856299
--- /dev/null
+++ b/CSharpGuidelinesAnalyzer.config
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/CodingGuidelines.ruleset b/CodingGuidelines.ruleset
new file mode 100644
index 0000000000..9447b105b1
--- /dev/null
+++ b/CodingGuidelines.ruleset
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index 80b7734180..3e7fe88452 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -4,8 +4,15 @@
3.1.*
3.1.*
3.1.*
+ $(SolutionDir)CodingGuidelines.ruleset
+
+
+ CSharpGuidelinesAnalyzer.config
+
+
+
$(NoWarn);1591
true
diff --git a/JsonApiDotNetCore-WarningSeverities.DotSettings b/JsonApiDotNetCore-WarningSeverities.DotSettings
new file mode 100644
index 0000000000..f4a9ae32e8
--- /dev/null
+++ b/JsonApiDotNetCore-WarningSeverities.DotSettings
@@ -0,0 +1,254 @@
+
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+
\ No newline at end of file
diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings
index 0404af6c5b..a32ac146aa 100644
--- a/JsonApiDotNetCore.sln.DotSettings
+++ b/JsonApiDotNetCore.sln.DotSettings
@@ -72,6 +72,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
WARNING
WARNING
WARNING
+ WARNING
SUGGESTION
WARNING
HINT
@@ -554,7 +555,6 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
UseExplicitType
UseVarWhenEvident
UseVarWhenEvident
- True
False
False
False
@@ -590,7 +590,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$);
JsonApiDotNetCore.ArgumentGuard.NotNull($argument$, nameof($argument$));
$left$ = $right$;
$left$ = $right$ ?? throw new ArgumentNullException(nameof($argument$));
- SUGGESTION
+ WARNING
True
Replace classic argument null check with Guard clause
True
@@ -604,7 +604,7 @@ $left$ = $right$;
Replace argument null check with Guard clause
JsonApiDotNetCore.ArgumentGuard.NotNull($argument$, nameof($argument$));
if ($argument$ == null) throw new ArgumentNullException(nameof($argument$));
- SUGGESTION
+ WARNING
True
Replace collection null/empty check with extension method
True
@@ -617,7 +617,7 @@ $left$ = $right$;
Replace collection null/empty check with extension method
$collection$.IsNullOrEmpty()
$collection$ == null || !$collection$.Any()
- SUGGESTION
+ WARNING
True
True
True
diff --git a/benchmarks/BenchmarkResourcePublicNames.cs b/benchmarks/BenchmarkResourcePublicNames.cs
index b8d6fdae12..84b63e7668 100644
--- a/benchmarks/BenchmarkResourcePublicNames.cs
+++ b/benchmarks/BenchmarkResourcePublicNames.cs
@@ -1,3 +1,5 @@
+#pragma warning disable AV1008 // Class should not be static
+
namespace Benchmarks
{
internal static class BenchmarkResourcePublicNames
diff --git a/benchmarks/DependencyFactory.cs b/benchmarks/DependencyFactory.cs
index d5ca4af6b6..7ecbafffbc 100644
--- a/benchmarks/DependencyFactory.cs
+++ b/benchmarks/DependencyFactory.cs
@@ -3,9 +3,9 @@
namespace Benchmarks
{
- internal static class DependencyFactory
+ internal sealed class DependencyFactory
{
- public static IResourceGraph CreateResourceGraph(IJsonApiOptions options)
+ public IResourceGraph CreateResourceGraph(IJsonApiOptions options)
{
var builder = new ResourceGraphBuilder(options, NullLoggerFactory.Instance);
builder.Add(BenchmarkResourcePublicNames.Type);
diff --git a/benchmarks/Query/QueryParserBenchmarks.cs b/benchmarks/Query/QueryParserBenchmarks.cs
index 8ba23d56f0..553d9fa7ba 100644
--- a/benchmarks/Query/QueryParserBenchmarks.cs
+++ b/benchmarks/Query/QueryParserBenchmarks.cs
@@ -20,6 +20,7 @@ namespace Benchmarks.Query
[MemoryDiagnoser]
public class QueryParserBenchmarks
{
+ private readonly DependencyFactory _dependencyFactory = new DependencyFactory();
private readonly FakeRequestQueryStringAccessor _queryStringAccessor = new FakeRequestQueryStringAccessor();
private readonly QueryStringReader _queryStringReaderForSort;
private readonly QueryStringReader _queryStringReaderForAll;
@@ -31,7 +32,7 @@ public QueryParserBenchmarks()
EnableLegacyFilterNotation = true
};
- IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);
+ IResourceGraph resourceGraph = _dependencyFactory.CreateResourceGraph(options);
var request = new JsonApiRequest
{
@@ -107,7 +108,7 @@ public void ComplexQuery()
private void Run(int iterations, Action action)
{
- for (int i = 0; i < iterations; i++)
+ for (int index = 0; index < iterations; index++)
{
action();
}
diff --git a/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs b/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs
index 9ecc308e7b..963c59cbcc 100644
--- a/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs
+++ b/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs
@@ -29,12 +29,13 @@ public class JsonApiDeserializerBenchmarks
}
});
+ private readonly DependencyFactory _dependencyFactory = new DependencyFactory();
private readonly IJsonApiDeserializer _jsonApiDeserializer;
public JsonApiDeserializerBenchmarks()
{
var options = new JsonApiOptions();
- IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);
+ IResourceGraph resourceGraph = _dependencyFactory.CreateResourceGraph(options);
var targetedFields = new TargetedFields();
var request = new JsonApiRequest();
var resourceFactory = new ResourceFactory(new ServiceContainer());
diff --git a/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs b/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs
index 088be638c4..7c42e26685 100644
--- a/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs
+++ b/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs
@@ -21,12 +21,13 @@ public class JsonApiSerializerBenchmarks
Name = Guid.NewGuid().ToString()
};
+ private readonly DependencyFactory _dependencyFactory = new DependencyFactory();
private readonly IJsonApiSerializer _jsonApiSerializer;
public JsonApiSerializerBenchmarks()
{
var options = new JsonApiOptions();
- IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);
+ IResourceGraph resourceGraph = _dependencyFactory.CreateResourceGraph(options);
IFieldsToSerialize fieldsToSerialize = CreateFieldsToSerialize(resourceGraph);
IMetaBuilder metaBuilder = new Mock().Object;
diff --git a/inspectcode.ps1 b/inspectcode.ps1
index 83987d5e69..355b8f20dd 100644
--- a/inspectcode.ps1
+++ b/inspectcode.ps1
@@ -16,7 +16,7 @@ if ($LASTEXITCODE -ne 0) {
$outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml')
$resultPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.html')
-dotnet jb inspectcode JsonApiDotNetCore.sln --output="$outputPath" --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal
+dotnet jb inspectcode JsonApiDotNetCore.sln --output="$outputPath" --profile=JsonApiDotNetCore-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/JsonApiDotNetCoreExample/Data/AppDbContext.cs b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
index 9d13b17493..ae3d13d553 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
@@ -23,12 +23,12 @@ public AppDbContext(DbContextOptions options)
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity()
- .HasOne(t => t.Assignee)
- .WithMany(p => p.AssignedTodoItems);
+ .HasOne(todoItem => todoItem.Assignee)
+ .WithMany(person => person.AssignedTodoItems);
builder.Entity()
- .HasOne(t => t.Owner)
- .WithMany(p => p.TodoItems);
+ .HasOne(todoItem => todoItem.Owner)
+ .WithMany(person => person.TodoItems);
builder.Entity()
.HasKey(bc => new
@@ -45,23 +45,23 @@ protected override void OnModelCreating(ModelBuilder builder)
});
builder.Entity()
- .HasOne(t => t.StakeHolderTodoItem)
- .WithMany(t => t.StakeHolders)
+ .HasOne(person => person.StakeHolderTodoItem)
+ .WithMany(todoItem => todoItem.StakeHolders)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity()
- .HasMany(t => t.ChildTodoItems)
- .WithOne(t => t.ParentTodo);
+ .HasMany(todoItem => todoItem.ChildTodoItems)
+ .WithOne(todoItem => todoItem.ParentTodo);
builder.Entity()
- .HasOne(p => p.Person)
- .WithOne(p => p.Passport)
+ .HasOne(passport => passport.Person)
+ .WithOne(person => person.Passport)
.HasForeignKey("PassportKey")
.OnDelete(DeleteBehavior.SetNull);
builder.Entity()
- .HasOne(p => p.OneToOnePerson)
- .WithOne(p => p.OneToOneTodoItem)
+ .HasOne(todoItem => todoItem.OneToOnePerson)
+ .WithOne(person => person.OneToOneTodoItem)
.HasForeignKey("OneToOnePersonKey");
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs
index 5ea4353ecd..cef9af0a74 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs
@@ -21,7 +21,7 @@ public ArticleHooksDefinition(IResourceGraph resourceGraph)
public override IEnumerable OnReturn(HashSet resources, ResourcePipeline pipeline)
{
- if (pipeline == ResourcePipeline.GetSingle && resources.Any(r => r.Caption == "Classified"))
+ if (pipeline == ResourcePipeline.GetSingle && resources.Any(article => article.Caption == "Classified"))
{
throw new JsonApiException(new Error(HttpStatusCode.Forbidden)
{
@@ -29,7 +29,7 @@ public override IEnumerable OnReturn(HashSet resources, Resour
});
}
- return resources.Where(article => article.Caption != "This should not be included");
+ return resources.Where(article => article.Caption != "This should not be included").ToArray();
}
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs
index a7a025bede..b2a5eb7775 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs
@@ -31,12 +31,12 @@ public override void BeforeRead(ResourcePipeline pipeline, bool isIncluded = fal
public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline)
{
- resourcesByRelationship.GetByRelationship().ToList().ForEach(kvp => DisallowLocked(kvp.Value));
+ resourcesByRelationship.GetByRelationship().ToList().ForEach(pair => DisallowLocked(pair.Value));
}
public override IEnumerable OnReturn(HashSet resources, ResourcePipeline pipeline)
{
- return resources.Where(passport => !passport.IsLocked);
+ return resources.Where(passport => !passport.IsLocked).ToArray();
}
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs
index 1d70f5892b..473817b0e8 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs
@@ -24,7 +24,7 @@ public override IEnumerable BeforeUpdateRelationship(HashSet ids
public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline)
{
- resourcesByRelationship.GetByRelationship().ToList().ForEach(kvp => DisallowLocked(kvp.Value));
+ resourcesByRelationship.GetByRelationship().ToList().ForEach(pair => DisallowLocked(pair.Value));
}
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs
index b170715e95..891959e00c 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs
@@ -18,7 +18,7 @@ public TagHooksDefinition(IResourceGraph resourceGraph)
public override IEnumerable OnReturn(HashSet resources, ResourcePipeline pipeline)
{
- return resources.Where(tag => tag.Name != "This should not be included");
+ return resources.Where(tag => tag.Name != "This should not be included").ToArray();
}
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooksDefinition.cs
index 9e59aa6569..bb912e3a82 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooksDefinition.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooksDefinition.cs
@@ -31,13 +31,13 @@ public override void BeforeRead(ResourcePipeline pipeline, bool isIncluded = fal
public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline)
{
- List todoItems = resourcesByRelationship.GetByRelationship().SelectMany(kvp => kvp.Value).ToList();
+ List todoItems = resourcesByRelationship.GetByRelationship().SelectMany(pair => pair.Value).ToList();
DisallowLocked(todoItems);
}
public override IEnumerable OnReturn(HashSet resources, ResourcePipeline pipeline)
{
- return resources.Where(todoItem => todoItem.Description != "This should not be included");
+ return resources.Where(todoItem => todoItem.Description != "This should not be included").ToArray();
}
}
}
diff --git a/src/JsonApiDotNetCore/ArgumentGuard.cs b/src/JsonApiDotNetCore/ArgumentGuard.cs
index 28640848a2..c9f9e2d6a7 100644
--- a/src/JsonApiDotNetCore/ArgumentGuard.cs
+++ b/src/JsonApiDotNetCore/ArgumentGuard.cs
@@ -3,6 +3,8 @@
using System.Linq;
using JetBrains.Annotations;
+#pragma warning disable AV1008 // Class should not be static
+
namespace JsonApiDotNetCore
{
internal static class ArgumentGuard
@@ -20,13 +22,26 @@ public static void NotNull([CanBeNull] [NoEnumeration] T value, [NotNull] [In
[AssertionMethod]
[ContractAnnotation("value: null => halt")]
- public static void NotNullNorEmpty([CanBeNull] IEnumerable value, [NotNull] [InvokerParameterName] string name)
+ public static void NotNullNorEmpty([CanBeNull] IEnumerable value, [NotNull] [InvokerParameterName] string name,
+ [CanBeNull] string collectionName = null)
{
NotNull(value, name);
if (!value.Any())
{
- throw new ArgumentException("Collection cannot be empty.", name);
+ throw new ArgumentException($"Must have one or more {collectionName ?? name}.", name);
+ }
+ }
+
+ [AssertionMethod]
+ [ContractAnnotation("value: null => halt")]
+ public static void NotNullNorEmpty([CanBeNull] string value, [NotNull] [InvokerParameterName] string name)
+ {
+ NotNull(value, name);
+
+ if (value == string.Empty)
+ {
+ throw new ArgumentException("String cannot be null or empty.", name);
}
}
}
diff --git a/src/JsonApiDotNetCore/ArrayFactory.cs b/src/JsonApiDotNetCore/ArrayFactory.cs
index 44eb3ba3f6..a33102cbdd 100644
--- a/src/JsonApiDotNetCore/ArrayFactory.cs
+++ b/src/JsonApiDotNetCore/ArrayFactory.cs
@@ -1,3 +1,6 @@
+#pragma warning disable AV1008 // Class should not be static
+#pragma warning disable AV1130 // Return type in method signature should be a collection interface instead of a concrete type
+
namespace JsonApiDotNetCore
{
internal static class ArrayFactory
diff --git a/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs b/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs
index 56faba2b66..453267d828 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs
@@ -20,8 +20,8 @@ public void Reset()
///
public void Declare(string localId, string resourceType)
{
- ArgumentGuard.NotNull(localId, nameof(localId));
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
+ ArgumentGuard.NotNullNorEmpty(localId, nameof(localId));
+ ArgumentGuard.NotNullNorEmpty(resourceType, nameof(resourceType));
AssertIsNotDeclared(localId);
@@ -43,9 +43,9 @@ private void AssertIsNotDeclared(string localId)
///
public void Assign(string localId, string resourceType, string stringId)
{
- ArgumentGuard.NotNull(localId, nameof(localId));
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
- ArgumentGuard.NotNull(stringId, nameof(stringId));
+ ArgumentGuard.NotNullNorEmpty(localId, nameof(localId));
+ ArgumentGuard.NotNullNorEmpty(resourceType, nameof(resourceType));
+ ArgumentGuard.NotNullNorEmpty(stringId, nameof(stringId));
AssertIsDeclared(localId);
@@ -64,8 +64,8 @@ public void Assign(string localId, string resourceType, string stringId)
///
public string GetValue(string localId, string resourceType)
{
- ArgumentGuard.NotNull(localId, nameof(localId));
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
+ ArgumentGuard.NotNullNorEmpty(localId, nameof(localId));
+ ArgumentGuard.NotNullNorEmpty(resourceType, nameof(resourceType));
AssertIsDeclared(localId);
diff --git a/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs b/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs
index 47ab571cb3..79ea1bf6ba 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs
@@ -91,7 +91,7 @@ private void AssignLocalId(OperationContainer operation)
{
ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(operation.Resource.GetType());
- _localIdTracker.Assign(operation.Resource.LocalId, resourceContext.PublicName, string.Empty);
+ _localIdTracker.Assign(operation.Resource.LocalId, resourceContext.PublicName, "placeholder");
}
}
diff --git a/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs
index e522dabe51..1fb60d985e 100644
--- a/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs
+++ b/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs
@@ -84,7 +84,9 @@ public virtual async Task> ProcessAsync(IList : ISetRelationshipProcessor
where TResource : class, IIdentifiable
{
+ private readonly CollectionConverter _collectionConverter = new CollectionConverter();
private readonly ISetRelationshipService _service;
public SetRelationshipProcessor(ISetRelationshipService service)
@@ -36,14 +37,14 @@ public virtual async Task ProcessAsync(OperationContainer op
return null;
}
- private static object GetRelationshipRightValue(OperationContainer operation)
+ private object GetRelationshipRightValue(OperationContainer operation)
{
RelationshipAttribute relationship = operation.Request.Relationship;
object rightValue = relationship.GetValue(operation.Resource);
if (relationship is HasManyAttribute)
{
- ICollection rightResources = TypeHelper.ExtractResources(rightValue);
+ ICollection rightResources = _collectionConverter.ExtractResources(rightValue);
return rightResources.ToHashSet(IdentifiableComparer.Instance);
}
diff --git a/src/JsonApiDotNetCore/CollectionConverter.cs b/src/JsonApiDotNetCore/CollectionConverter.cs
new file mode 100644
index 0000000000..a79757ceac
--- /dev/null
+++ b/src/JsonApiDotNetCore/CollectionConverter.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using JsonApiDotNetCore.Resources;
+
+namespace JsonApiDotNetCore
+{
+ internal class CollectionConverter
+ {
+ private static readonly Type[] HashSetCompatibleCollectionTypes =
+ {
+ typeof(HashSet<>),
+ typeof(ICollection<>),
+ typeof(ISet<>),
+ typeof(IEnumerable<>),
+ typeof(IReadOnlyCollection<>)
+ };
+
+ ///
+ /// Creates a collection instance based on the specified collection type and copies the specified elements into it.
+ ///
+ ///
+ /// Source to copy from.
+ ///
+ ///
+ /// Target collection type, for example: typeof(List{Article}) or typeof(ISet{Person}).
+ ///
+ public IEnumerable CopyToTypedCollection(IEnumerable source, Type collectionType)
+ {
+ ArgumentGuard.NotNull(source, nameof(source));
+ ArgumentGuard.NotNull(collectionType, nameof(collectionType));
+
+ Type concreteCollectionType = ToConcreteCollectionType(collectionType);
+ dynamic concreteCollectionInstance = Activator.CreateInstance(concreteCollectionType);
+
+ foreach (object item in source)
+ {
+ concreteCollectionInstance!.Add((dynamic)item);
+ }
+
+ return concreteCollectionInstance;
+ }
+
+ ///
+ /// Returns a compatible collection type that can be instantiated, for example IList{Article} -> List{Article} or ISet{Article} -> HashSet{Article}
+ ///
+ public Type ToConcreteCollectionType(Type collectionType)
+ {
+ if (collectionType.IsInterface && collectionType.IsGenericType)
+ {
+ Type genericTypeDefinition = collectionType.GetGenericTypeDefinition();
+
+ if (genericTypeDefinition == typeof(ICollection<>) || genericTypeDefinition == typeof(ISet<>) ||
+ genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(IReadOnlyCollection<>))
+ {
+ return typeof(HashSet<>).MakeGenericType(collectionType.GenericTypeArguments[0]);
+ }
+
+ if (genericTypeDefinition == typeof(IList<>) || genericTypeDefinition == typeof(IReadOnlyList<>))
+ {
+ return typeof(List<>).MakeGenericType(collectionType.GenericTypeArguments[0]);
+ }
+ }
+
+ return collectionType;
+ }
+
+ ///
+ /// Returns a collection that contains zero, one or multiple resources, depending on the specified value.
+ ///
+ public ICollection ExtractResources(object value)
+ {
+ if (value is ICollection resourceCollection)
+ {
+ return resourceCollection;
+ }
+
+ if (value is IEnumerable resources)
+ {
+ return resources.ToList();
+ }
+
+ if (value is IIdentifiable resource)
+ {
+ return resource.AsArray();
+ }
+
+ return Array.Empty();
+ }
+
+ ///
+ /// Returns the element type if the specified type is a generic collection, for example: IList{string} -> string or IList -> null.
+ ///
+ public Type TryGetCollectionElementType(Type type)
+ {
+ if (type != null)
+ {
+ if (type.IsGenericType && type.GenericTypeArguments.Length == 1)
+ {
+ if (type.IsOrImplementsInterface(typeof(IEnumerable)))
+ {
+ return type.GenericTypeArguments[0];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Indicates whether a instance can be assigned to the specified type, for example IList{Article} -> false or ISet{Article} ->
+ /// true.
+ ///
+ public bool TypeCanContainHashSet(Type collectionType)
+ {
+ if (collectionType.IsGenericType)
+ {
+ Type openCollectionType = collectionType.GetGenericTypeDefinition();
+ return HashSetCompatibleCollectionTypes.Contains(openCollectionType);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore/CollectionExtensions.cs b/src/JsonApiDotNetCore/CollectionExtensions.cs
index be9962dc2d..68997f60b3 100644
--- a/src/JsonApiDotNetCore/CollectionExtensions.cs
+++ b/src/JsonApiDotNetCore/CollectionExtensions.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
@@ -17,5 +18,21 @@ public static bool IsNullOrEmpty(this IEnumerable source)
return !source.Any();
}
+
+ public static int FindIndex(this IList source, Predicate match)
+ {
+ ArgumentGuard.NotNull(source, nameof(source));
+ ArgumentGuard.NotNull(match, nameof(match));
+
+ for (int index = 0; index < source.Count; index++)
+ {
+ if (match(source[index]))
+ {
+ return index;
+ }
+ }
+
+ return -1;
+ }
}
}
diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs
index 9f04418849..b5ce25aa82 100644
--- a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs
+++ b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs
@@ -261,8 +261,8 @@ private void AddResourceHooks()
_services.AddSingleton(typeof(IHooksDiscovery<>), typeof(HooksDiscovery<>));
_services.AddScoped(typeof(IResourceHookContainer<>), typeof(ResourceHooksDefinition<>));
_services.AddTransient();
- _services.AddTransient();
- _services.AddScoped();
+ _services.AddTransient();
+ _services.AddScoped();
_services.AddScoped();
}
else
diff --git a/src/JsonApiDotNetCore/Configuration/ResourceDescriptorAssemblyCache.cs b/src/JsonApiDotNetCore/Configuration/ResourceDescriptorAssemblyCache.cs
index 667d6680fa..7bf545b997 100644
--- a/src/JsonApiDotNetCore/Configuration/ResourceDescriptorAssemblyCache.cs
+++ b/src/JsonApiDotNetCore/Configuration/ResourceDescriptorAssemblyCache.cs
@@ -10,6 +10,8 @@ namespace JsonApiDotNetCore.Configuration
///
internal sealed class ResourceDescriptorAssemblyCache
{
+ private readonly TypeLocator _typeLocator = new TypeLocator();
+
private readonly Dictionary> _resourceDescriptorsPerAssembly =
new Dictionary>();
@@ -25,7 +27,7 @@ public void RegisterAssembly(Assembly assembly)
{
EnsureAssembliesScanned();
- return _resourceDescriptorsPerAssembly.Select(pair => (pair.Key, pair.Value));
+ return _resourceDescriptorsPerAssembly.Select(pair => (pair.Key, pair.Value)).ToArray();
}
private void EnsureAssembliesScanned()
@@ -36,11 +38,11 @@ private void EnsureAssembliesScanned()
}
}
- private static IEnumerable ScanForResourceDescriptors(Assembly assembly)
+ private IEnumerable ScanForResourceDescriptors(Assembly assembly)
{
foreach (Type type in assembly.GetTypes())
{
- ResourceDescriptor resourceDescriptor = TypeLocator.TryGetResourceDescriptor(type);
+ ResourceDescriptor resourceDescriptor = _typeLocator.TryGetResourceDescriptor(type);
if (resourceDescriptor != null)
{
diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs
index 3f56e3fffe..1fc36ca4a2 100644
--- a/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs
+++ b/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs
@@ -32,9 +32,9 @@ public IReadOnlyCollection GetResourceContexts()
///
public ResourceContext GetResourceContext(string resourceName)
{
- ArgumentGuard.NotNull(resourceName, nameof(resourceName));
+ ArgumentGuard.NotNullNorEmpty(resourceName, nameof(resourceName));
- return _resources.SingleOrDefault(e => e.PublicName == resourceName);
+ return _resources.SingleOrDefault(resourceContext => resourceContext.PublicName == resourceName);
}
///
@@ -43,8 +43,8 @@ public ResourceContext GetResourceContext(Type resourceType)
ArgumentGuard.NotNull(resourceType, nameof(resourceType));
return IsLazyLoadingProxyForResourceType(resourceType)
- ? _resources.SingleOrDefault(e => e.ResourceType == resourceType.BaseType)
- : _resources.SingleOrDefault(e => e.ResourceType == resourceType);
+ ? _resources.SingleOrDefault(resourceContext => resourceContext.ResourceType == resourceType.BaseType)
+ : _resources.SingleOrDefault(resourceContext => resourceContext.ResourceType == resourceType);
}
///
@@ -109,7 +109,8 @@ public RelationshipAttribute GetInverseRelationship(RelationshipAttribute relati
return null;
}
- return GetResourceContext(relationship.RightType).Relationships.SingleOrDefault(r => r.Property == relationship.InverseNavigationProperty);
+ return GetResourceContext(relationship.RightType).Relationships
+ .SingleOrDefault(nextRelationship => nextRelationship.Property == relationship.InverseNavigationProperty);
}
private IReadOnlyCollection Getter(Expression> selector = null,
@@ -145,7 +146,7 @@ private IReadOnlyCollection Getter(Expression
// model => model.Field1
try
{
- targeted.Add(available.Single(f => f.Property.Name == memberExpression.Member.Name));
+ targeted.Add(available.Single(field => field.Property.Name == memberExpression.Member.Name));
return targeted;
}
catch (InvalidOperationException)
@@ -169,7 +170,7 @@ private IReadOnlyCollection Getter(Expression
foreach (MemberInfo member in newExpression.Members)
{
memberName = member.Name;
- targeted.Add(available.Single(f => f.Property.Name == memberName));
+ targeted.Add(available.Single(field => field.Property.Name == memberName));
}
return targeted;
diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs
index a6bb501ffd..0451c83643 100644
--- a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs
+++ b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs
@@ -19,6 +19,7 @@ public class ResourceGraphBuilder
private readonly IJsonApiOptions _options;
private readonly ILogger _logger;
private readonly List _resources = new List();
+ private readonly TypeLocator _typeLocator = new TypeLocator();
public ResourceGraphBuilder(IJsonApiOptions options, ILoggerFactory loggerFactory)
{
@@ -102,15 +103,15 @@ public ResourceGraphBuilder Add(Type resourceType, Type idType = null, string pu
{
ArgumentGuard.NotNull(resourceType, nameof(resourceType));
- if (_resources.Any(e => e.ResourceType == resourceType))
+ if (_resources.Any(resourceContext => resourceContext.ResourceType == resourceType))
{
return this;
}
- if (TypeHelper.IsOrImplementsInterface(resourceType, typeof(IIdentifiable)))
+ if (resourceType.IsOrImplementsInterface(typeof(IIdentifiable)))
{
string effectivePublicName = publicName ?? FormatResourceName(resourceType);
- Type effectiveIdType = idType ?? TypeLocator.TryGetIdType(resourceType);
+ Type effectiveIdType = idType ?? _typeLocator.TryGetIdType(resourceType);
ResourceContext resourceContext = CreateResourceContext(effectivePublicName, resourceType, effectiveIdType);
_resources.Add(resourceContext);
@@ -138,8 +139,6 @@ private ResourceContext CreateResourceContext(string publicName, Type resourceTy
private IReadOnlyCollection GetAttributes(Type resourceType)
{
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
-
var attributes = new List();
foreach (PropertyInfo property in resourceType.GetProperties())
@@ -183,8 +182,6 @@ private IReadOnlyCollection GetAttributes(Type resourceType)
private IReadOnlyCollection GetRelationships(Type resourceType)
{
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
-
var attributes = new List();
PropertyInfo[] properties = resourceType.GetProperties();
@@ -205,7 +202,7 @@ private IReadOnlyCollection GetRelationships(Type resourc
if (attribute is HasManyThroughAttribute hasManyThroughAttribute)
{
- PropertyInfo throughProperty = properties.SingleOrDefault(p => p.Name == hasManyThroughAttribute.ThroughPropertyName);
+ PropertyInfo throughProperty = properties.SingleOrDefault(property => property.Name == hasManyThroughAttribute.ThroughPropertyName);
if (throughProperty == null)
{
@@ -240,14 +237,15 @@ private IReadOnlyCollection GetRelationships(Type resourc
else
{
// In case of a non-self-referencing many-to-many relationship, we just pick the single compatible type.
- hasManyThroughAttribute.LeftProperty = throughProperties.SingleOrDefault(x => x.PropertyType.IsAssignableFrom(resourceType)) ??
+ hasManyThroughAttribute.LeftProperty =
+ throughProperties.SingleOrDefault(property => property.PropertyType.IsAssignableFrom(resourceType)) ??
throw new InvalidConfigurationException($"'{throughType}' does not contain a navigation property to type '{resourceType}'.");
}
// ArticleTag.ArticleId
string leftIdPropertyName = hasManyThroughAttribute.LeftIdPropertyName ?? hasManyThroughAttribute.LeftProperty.Name + "Id";
- hasManyThroughAttribute.LeftIdProperty = throughProperties.SingleOrDefault(x => x.Name == leftIdPropertyName) ??
+ hasManyThroughAttribute.LeftIdProperty = throughProperties.SingleOrDefault(property => property.Name == leftIdPropertyName) ??
throw new InvalidConfigurationException(
$"'{throughType}' does not contain a relationship ID property to type '{resourceType}' with name '{leftIdPropertyName}'.");
@@ -262,7 +260,8 @@ private IReadOnlyCollection GetRelationships(Type resourc
else
{
// In case of a non-self-referencing many-to-many relationship, we just pick the single compatible type.
- hasManyThroughAttribute.RightProperty = throughProperties.SingleOrDefault(x => x.PropertyType == hasManyThroughAttribute.RightType) ??
+ hasManyThroughAttribute.RightProperty =
+ throughProperties.SingleOrDefault(property => property.PropertyType == hasManyThroughAttribute.RightType) ??
throw new InvalidConfigurationException(
$"'{throughType}' does not contain a navigation property to type '{hasManyThroughAttribute.RightType}'.");
}
@@ -270,7 +269,7 @@ private IReadOnlyCollection GetRelationships(Type resourc
// ArticleTag.TagId
string rightIdPropertyName = hasManyThroughAttribute.RightIdPropertyName ?? hasManyThroughAttribute.RightProperty.Name + "Id";
- hasManyThroughAttribute.RightIdProperty = throughProperties.SingleOrDefault(x => x.Name == rightIdPropertyName) ??
+ hasManyThroughAttribute.RightIdProperty = throughProperties.SingleOrDefault(property => property.Name == rightIdPropertyName) ??
throw new InvalidConfigurationException(
$"'{throughType}' does not contain a relationship ID property to type '{hasManyThroughAttribute.RightType}' with name '{rightIdPropertyName}'.");
}
@@ -289,7 +288,7 @@ private Type TryGetThroughType(PropertyInfo throughProperty)
{
Type constructedThroughType = typeof(ICollection<>).MakeGenericType(typeArguments[0]);
- if (TypeHelper.IsOrImplementsInterface(throughProperty.PropertyType, constructedThroughType))
+ if (throughProperty.PropertyType.IsOrImplementsInterface(constructedThroughType))
{
return typeArguments[0];
}
@@ -307,13 +306,9 @@ private Type GetRelationshipType(RelationshipAttribute relationship, PropertyInf
return relationship is HasOneAttribute ? property.PropertyType : property.PropertyType.GetGenericArguments()[0];
}
- // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
private IReadOnlyCollection GetEagerLoads(Type resourceType, int recursionDepth = 0)
{
- if (recursionDepth >= 500)
- {
- throw new InvalidOperationException("Infinite recursion detected in eager-load chain.");
- }
+ AssertNoInfiniteRecursion(recursionDepth);
var attributes = new List();
PropertyInfo[] properties = resourceType.GetProperties();
@@ -337,9 +332,19 @@ private IReadOnlyCollection GetEagerLoads(Type resourceType,
return attributes;
}
+ [AssertionMethod]
+ private static void AssertNoInfiniteRecursion(int recursionDepth)
+ {
+ if (recursionDepth >= 500)
+ {
+ throw new InvalidOperationException("Infinite recursion detected in eager-load chain.");
+ }
+ }
+
private Type TypeOrElementType(Type type)
{
- Type[] interfaces = type.GetInterfaces().Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)).ToArray();
+ Type[] interfaces = type.GetInterfaces()
+ .Where(@interface => @interface.IsGenericType && @interface.GetGenericTypeDefinition() == typeof(IEnumerable<>)).ToArray();
return interfaces.Length == 1 ? interfaces.Single().GenericTypeArguments[0] : type;
}
diff --git a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs
index a63d734fe4..3192ddd85f 100644
--- a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs
+++ b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs
@@ -16,6 +16,8 @@ namespace JsonApiDotNetCore.Configuration
[PublicAPI]
public static class ServiceCollectionExtensions
{
+ private static readonly TypeLocator TypeLocator = new TypeLocator();
+
///
/// Configures JsonApiDotNetCore by registering resources manually.
///
diff --git a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs
index 41e3a390bc..b18c8826f6 100644
--- a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs
+++ b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs
@@ -70,6 +70,7 @@ public class ServiceDiscoveryFacade
private readonly ResourceGraphBuilder _resourceGraphBuilder;
private readonly IJsonApiOptions _options;
private readonly ResourceDescriptorAssemblyCache _assemblyCache = new ResourceDescriptorAssemblyCache();
+ private readonly TypeLocator _typeLocator = new TypeLocator();
public ServiceDiscoveryFacade(IServiceCollection services, ResourceGraphBuilder resourceGraphBuilder, IJsonApiOptions options,
ILoggerFactory loggerFactory)
@@ -140,7 +141,7 @@ private void AddInjectables(IReadOnlyCollection resourceDesc
private void AddDbContextResolvers(Assembly assembly)
{
- IEnumerable dbContextTypes = TypeLocator.GetDerivedTypes(assembly, typeof(DbContext));
+ IEnumerable dbContextTypes = _typeLocator.GetDerivedTypes(assembly, typeof(DbContext));
foreach (Type dbContextType in dbContextTypes)
{
@@ -158,7 +159,7 @@ private void AddResourceHookDefinitions(Assembly assembly, ResourceDescriptor id
{
try
{
- Type resourceDefinition = TypeLocator.GetDerivedGenericTypes(assembly, typeof(ResourceHooksDefinition<>), identifiable.ResourceType)
+ Type resourceDefinition = _typeLocator.GetDerivedGenericTypes(assembly, typeof(ResourceHooksDefinition<>), identifiable.ResourceType)
.SingleOrDefault();
if (resourceDefinition != null)
@@ -166,10 +167,10 @@ private void AddResourceHookDefinitions(Assembly assembly, ResourceDescriptor id
_services.AddScoped(typeof(ResourceHooksDefinition<>).MakeGenericType(identifiable.ResourceType), resourceDefinition);
}
}
- catch (InvalidOperationException e)
+ catch (InvalidOperationException exception)
{
throw new InvalidConfigurationException($"Cannot define multiple ResourceHooksDefinition<> implementations for '{identifiable.ResourceType}'",
- e);
+ exception);
}
}
@@ -203,8 +204,8 @@ private void RegisterImplementations(Assembly assembly, Type interfaceType, Reso
? ArrayFactory.Create(resourceDescriptor.ResourceType, resourceDescriptor.IdType)
: ArrayFactory.Create(resourceDescriptor.ResourceType);
- (Type implementation, Type registrationInterface)?
- result = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments);
+ (Type implementation, Type registrationInterface)? result =
+ _typeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments);
if (result != null)
{
diff --git a/src/JsonApiDotNetCore/Configuration/TypeLocator.cs b/src/JsonApiDotNetCore/Configuration/TypeLocator.cs
index 303cb32c43..e0b0634efe 100644
--- a/src/JsonApiDotNetCore/Configuration/TypeLocator.cs
+++ b/src/JsonApiDotNetCore/Configuration/TypeLocator.cs
@@ -9,15 +9,15 @@ namespace JsonApiDotNetCore.Configuration
///
/// Used to locate types and facilitate resource auto-discovery.
///
- internal static class TypeLocator
+ internal sealed class TypeLocator
{
///
/// Attempts to lookup the ID type of the specified resource type. Returns null if it does not implement .
///
- public static Type TryGetIdType(Type resourceType)
+ public Type TryGetIdType(Type resourceType)
{
- Type identifiableInterface = resourceType.GetInterfaces()
- .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IIdentifiable<>));
+ Type identifiableInterface = resourceType.GetInterfaces().FirstOrDefault(@interface =>
+ @interface.IsGenericType && @interface.GetGenericTypeDefinition() == typeof(IIdentifiable<>));
return identifiableInterface?.GetGenericArguments()[0];
}
@@ -25,9 +25,9 @@ public static Type TryGetIdType(Type resourceType)
///
/// Attempts to get a descriptor for the specified resource type.
///
- public static ResourceDescriptor TryGetResourceDescriptor(Type type)
+ public ResourceDescriptor TryGetResourceDescriptor(Type type)
{
- if (TypeHelper.IsOrImplementsInterface(type, typeof(IIdentifiable)))
+ if (type.IsOrImplementsInterface(typeof(IIdentifiable)))
{
Type idType = TryGetIdType(type);
@@ -57,7 +57,7 @@ public static ResourceDescriptor TryGetResourceDescriptor(Type type)
/// GetGenericInterfaceImplementation(assembly, typeof(IResourceService<,>), typeof(Article), typeof(Guid));
/// ]]>
///
- public static (Type implementation, Type registrationInterface)? GetGenericInterfaceImplementation(Assembly assembly, Type openGenericInterface,
+ public (Type implementation, Type registrationInterface)? GetGenericInterfaceImplementation(Assembly assembly, Type openGenericInterface,
params Type[] interfaceGenericTypeArguments)
{
ArgumentGuard.NotNull(assembly, nameof(assembly));
@@ -121,7 +121,7 @@ private static (Type implementation, Type registrationInterface)? FindGenericInt
/// GetDerivedGenericTypes(assembly, typeof(ResourceDefinition<>), typeof(Article))
/// ]]>
///
- public static IReadOnlyCollection GetDerivedGenericTypes(Assembly assembly, Type openGenericType, params Type[] genericArguments)
+ public IReadOnlyCollection GetDerivedGenericTypes(Assembly assembly, Type openGenericType, params Type[] genericArguments)
{
Type genericType = openGenericType.MakeGenericType(genericArguments);
return GetDerivedTypes(assembly, genericType).ToArray();
@@ -141,7 +141,7 @@ public static IReadOnlyCollection GetDerivedGenericTypes(Assembly assembly
/// GetDerivedGenericTypes(assembly, typeof(DbContext))
///
///
- public static IEnumerable GetDerivedTypes(Assembly assembly, Type inheritedType)
+ public IEnumerable GetDerivedTypes(Assembly assembly, Type inheritedType)
{
foreach (Type type in assembly.GetTypes())
{
diff --git a/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs b/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs
index e363b1aabb..4bf10ec976 100644
--- a/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs
+++ b/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs
@@ -48,7 +48,7 @@ public DisableQueryStringAttribute(StandardQueryStringParameters parameters)
///
public DisableQueryStringAttribute(string parameterNames)
{
- ArgumentGuard.NotNull(parameterNames, nameof(parameterNames));
+ ArgumentGuard.NotNullNorEmpty(parameterNames, nameof(parameterNames));
ParameterNames = parameterNames.Split(",").ToList();
}
diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs
index d625abbdec..978fe828f5 100644
--- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs
+++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs
@@ -130,7 +130,7 @@ public virtual async Task GetSecondaryAsync(TId id, string relati
relationshipName
});
- ArgumentGuard.NotNull(relationshipName, nameof(relationshipName));
+ ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName));
if (_getSecondary == null)
{
@@ -153,7 +153,7 @@ public virtual async Task GetRelationshipAsync(TId id, string rel
relationshipName
});
- ArgumentGuard.NotNull(relationshipName, nameof(relationshipName));
+ ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName));
if (_getRelationship == null)
{
@@ -232,7 +232,7 @@ public virtual async Task PostRelationshipAsync(TId id, string re
secondaryResourceIds
});
- ArgumentGuard.NotNull(relationshipName, nameof(relationshipName));
+ ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName));
ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds));
if (_addToRelationship == null)
@@ -300,7 +300,7 @@ public virtual async Task PatchRelationshipAsync(TId id, string r
secondaryResourceIds
});
- ArgumentGuard.NotNull(relationshipName, nameof(relationshipName));
+ ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName));
if (_setRelationship == null)
{
@@ -357,7 +357,7 @@ public virtual async Task DeleteRelationshipAsync(TId id, string
secondaryResourceIds
});
- ArgumentGuard.NotNull(relationshipName, nameof(relationshipName));
+ ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName));
ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds));
if (_removeFromRelationship == null)
diff --git a/src/JsonApiDotNetCore/Controllers/ModelStateViolation.cs b/src/JsonApiDotNetCore/Controllers/ModelStateViolation.cs
index 8bda54c806..2a4c8cfb84 100644
--- a/src/JsonApiDotNetCore/Controllers/ModelStateViolation.cs
+++ b/src/JsonApiDotNetCore/Controllers/ModelStateViolation.cs
@@ -17,8 +17,8 @@ public sealed class ModelStateViolation
public ModelStateViolation(string prefix, string propertyName, Type resourceType, ModelError error)
{
- ArgumentGuard.NotNull(prefix, nameof(prefix));
- ArgumentGuard.NotNull(propertyName, nameof(propertyName));
+ ArgumentGuard.NotNullNorEmpty(prefix, nameof(prefix));
+ ArgumentGuard.NotNullNorEmpty(propertyName, nameof(propertyName));
ArgumentGuard.NotNull(resourceType, nameof(resourceType));
ArgumentGuard.NotNull(error, nameof(error));
diff --git a/src/JsonApiDotNetCore/Errors/JsonApiException.cs b/src/JsonApiDotNetCore/Errors/JsonApiException.cs
index 55a88e287a..93ba6fe6cb 100644
--- a/src/JsonApiDotNetCore/Errors/JsonApiException.cs
+++ b/src/JsonApiDotNetCore/Errors/JsonApiException.cs
@@ -34,14 +34,10 @@ public JsonApiException(Error error, Exception innerException = null)
public JsonApiException(IEnumerable errors, Exception innerException = null)
: base(null, innerException)
{
- ArgumentGuard.NotNull(errors, nameof(errors));
+ List errorList = errors?.ToList();
+ ArgumentGuard.NotNullNorEmpty(errorList, nameof(errors));
- Errors = errors.ToList();
-
- if (!Errors.Any())
- {
- throw new ArgumentException("At least one error is required.", nameof(errors));
- }
+ Errors = errorList;
}
}
}
diff --git a/src/JsonApiDotNetCore/Errors/MissingResourceInRelationship.cs b/src/JsonApiDotNetCore/Errors/MissingResourceInRelationship.cs
index 95ef199932..7afe5b04cc 100644
--- a/src/JsonApiDotNetCore/Errors/MissingResourceInRelationship.cs
+++ b/src/JsonApiDotNetCore/Errors/MissingResourceInRelationship.cs
@@ -11,9 +11,9 @@ public sealed class MissingResourceInRelationship
public MissingResourceInRelationship(string relationshipName, string resourceType, string resourceId)
{
- ArgumentGuard.NotNull(relationshipName, nameof(relationshipName));
- ArgumentGuard.NotNull(resourceType, nameof(resourceType));
- ArgumentGuard.NotNull(resourceId, nameof(resourceId));
+ ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName));
+ ArgumentGuard.NotNullNorEmpty(resourceType, nameof(resourceType));
+ ArgumentGuard.NotNullNorEmpty(resourceId, nameof(resourceId));
RelationshipName = relationshipName;
ResourceType = resourceType;
diff --git a/src/JsonApiDotNetCore/Errors/NonSharedTransactionException.cs b/src/JsonApiDotNetCore/Errors/NonParticipatingTransactionException.cs
similarity index 83%
rename from src/JsonApiDotNetCore/Errors/NonSharedTransactionException.cs
rename to src/JsonApiDotNetCore/Errors/NonParticipatingTransactionException.cs
index 5245c4344f..9da29c521b 100644
--- a/src/JsonApiDotNetCore/Errors/NonSharedTransactionException.cs
+++ b/src/JsonApiDotNetCore/Errors/NonParticipatingTransactionException.cs
@@ -8,9 +8,9 @@ namespace JsonApiDotNetCore.Errors
/// The error that is thrown when a repository does not participate in the overarching transaction during an atomic:operations request.
///
[PublicAPI]
- public sealed class NonSharedTransactionException : JsonApiException
+ public sealed class NonParticipatingTransactionException : JsonApiException
{
- public NonSharedTransactionException()
+ public NonParticipatingTransactionException()
: base(new Error(HttpStatusCode.UnprocessableEntity)
{
Title = "Unsupported combination of resource types in atomic:operations request.",
diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Discovery/HooksDiscovery.cs b/src/JsonApiDotNetCore/Hooks/Internal/Discovery/HooksDiscovery.cs
index e09d327fbc..b707e2569f 100644
--- a/src/JsonApiDotNetCore/Hooks/Internal/Discovery/HooksDiscovery.cs
+++ b/src/JsonApiDotNetCore/Hooks/Internal/Discovery/HooksDiscovery.cs
@@ -35,7 +35,7 @@ public class HooksDiscovery : IHooksDiscovery
public HooksDiscovery(IServiceProvider provider)
{
- _allHooks = Enum.GetValues(typeof(ResourceHook)).Cast().Where(h => h != ResourceHook.None).ToArray();
+ _allHooks = Enum.GetValues(typeof(ResourceHook)).Cast().Where(hook => hook != ResourceHook.None).ToArray();
Type containerType;
diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/DiffableResourceHashSet.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/DiffableResourceHashSet.cs
index a61fb4b6d5..91e51a9660 100644
--- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/DiffableResourceHashSet.cs
+++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/DiffableResourceHashSet.cs
@@ -15,12 +15,15 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution
public sealed class DiffableResourceHashSet : ResourceHashSet, IDiffableResourceHashSet
where TResource : class, IIdentifiable
{
+ // ReSharper disable once StaticMemberInGenericType
+ private static readonly CollectionConverter CollectionConverter = new CollectionConverter();
+
private readonly HashSet _databaseValues;
private readonly bool _databaseValuesLoaded;
- private readonly Dictionary> _updatedAttributes;
+ private readonly IDictionary> _updatedAttributes;
public DiffableResourceHashSet(HashSet requestResources, HashSet databaseResources,
- Dictionary> relationships, Dictionary> updatedAttributes)
+ IDictionary> relationships, IDictionary> updatedAttributes)
: base(requestResources, relationships)
{
_databaseValues = databaseResources;
@@ -32,12 +35,10 @@ public DiffableResourceHashSet(HashSet requestResources, HashSet
internal DiffableResourceHashSet(IEnumerable requestResources, IEnumerable databaseResources,
- Dictionary relationships, ITargetedFields targetedFields)
+ IDictionary relationships, ITargetedFields targetedFields)
: this((HashSet)requestResources, (HashSet)databaseResources,
- TypeHelper.ConvertRelationshipDictionary(relationships),
- targetedFields.Attributes == null
- ? null
- : TypeHelper.ConvertAttributeDictionary(targetedFields.Attributes, (HashSet)requestResources))
+ relationships.ToDictionary(pair => pair.Key, pair => (HashSet)pair.Value),
+ targetedFields.Attributes?.ToDictionary(attr => attr.Property, _ => (HashSet)requestResources))
{
}
@@ -51,7 +52,7 @@ public IEnumerable> GetDiffs()
foreach (TResource resource in this)
{
- TResource currentValueInDatabase = _databaseValues.Single(e => resource.StringId == e.StringId);
+ TResource currentValueInDatabase = _databaseValues.Single(databaseResource => resource.StringId == databaseResource.StringId);
yield return new ResourceDiffPair(resource, currentValueInDatabase);
}
}
@@ -61,17 +62,17 @@ public override HashSet GetAffected(Expression
- internal sealed class HookExecutorHelper : IHookExecutorHelper
+ internal sealed class HookContainerProvider : IHookContainerProvider
{
+ private static readonly HooksCollectionConverter CollectionConverter = new HooksCollectionConverter();
+ private static readonly HooksObjectFactory ObjectFactory = new HooksObjectFactory();
+ private static readonly IncludeChainConverter IncludeChainConverter = new IncludeChainConverter();
+
private readonly IdentifiableComparer _comparer = IdentifiableComparer.Instance;
private readonly IJsonApiOptions _options;
- private readonly IGenericServiceFactory _genericProcessorFactory;
+ private readonly IGenericServiceFactory _genericServiceFactory;
private readonly IResourceContextProvider _resourceContextProvider;
private readonly Dictionary _hookContainers;
private readonly Dictionary _hookDiscoveries;
private readonly List _targetedHooksForRelatedResources;
- public HookExecutorHelper(IGenericServiceFactory genericProcessorFactory, IResourceContextProvider resourceContextProvider, IJsonApiOptions options)
+ public HookContainerProvider(IGenericServiceFactory genericServiceFactory, IResourceContextProvider resourceContextProvider, IJsonApiOptions options)
{
_options = options;
- _genericProcessorFactory = genericProcessorFactory;
+ _genericServiceFactory = genericServiceFactory;
_resourceContextProvider = resourceContextProvider;
_hookContainers = new Dictionary();
_hookDiscoveries = new Dictionary();
@@ -40,13 +44,13 @@ public HookExecutorHelper(IGenericServiceFactory genericProcessorFactory, IResou
///
public IResourceHookContainer GetResourceHookContainer(RightType targetResource, ResourceHook hook = ResourceHook.None)
{
- // checking the cache if we have a reference for the requested container,
- // regardless of the hook we will use it for. If the value is null,
- // it means there was no implementation IResourceHookContainer at all,
+ // checking the cache if we have a reference for the requested container,
+ // regardless of the hook we will use it for. If the value is null,
+ // it means there was no implementation IResourceHookContainer at all,
// so we need not even bother.
if (!_hookContainers.TryGetValue(targetResource, out IResourceHookContainer container))
{
- container = _genericProcessorFactory.Get(typeof(ResourceHooksDefinition<>), targetResource);
+ container = _genericServiceFactory.Get(typeof(ResourceHooksDefinition<>), targetResource);
_hookContainers[targetResource] = container;
}
@@ -55,7 +59,7 @@ public IResourceHookContainer GetResourceHookContainer(RightType targetResource,
return null;
}
- // if there was a container, first check if it implements the hook we
+ // if there was a container, first check if it implements the hook we
// want to use it for.
IEnumerable targetHooks;
@@ -87,40 +91,19 @@ public IResourceHookContainer GetResourceHookContainer(Res
return (IResourceHookContainer)GetResourceHookContainer(typeof(TResource), hook);
}
- public IEnumerable LoadDbValues(LeftType resourceTypeForRepository, IEnumerable resources, ResourceHook hook,
- params RelationshipAttribute[] relationshipsToNextLayer)
+ public IEnumerable LoadDbValues(LeftType resourceTypeForRepository, IEnumerable resources, params RelationshipAttribute[] relationshipsToNextLayer)
{
- LeftType idType = TypeHelper.GetIdType(resourceTypeForRepository);
+ LeftType idType = ObjectFactory.GetIdType(resourceTypeForRepository);
MethodInfo parameterizedGetWhere =
- GetType().GetMethod(nameof(GetWhereAndInclude), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod(resourceTypeForRepository,
+ GetType().GetMethod(nameof(GetWhereWithInclude), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod(resourceTypeForRepository,
idType);
- IEnumerable cast = ((IEnumerable