diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs
index 7a6cbd960f..26a660775a 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs
@@ -31,6 +31,7 @@ public AttrCapabilities Capabilities
set => _capabilities = value;
}
+ ///
public override bool Equals(object? obj)
{
if (ReferenceEquals(this, obj))
@@ -48,6 +49,7 @@ public override bool Equals(object? obj)
return Capabilities == other.Capabilities && base.Equals(other);
}
+ ///
public override int GetHashCode()
{
return HashCode.Combine(Capabilities, base.GetHashCode());
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.cs
index 5792744d5c..d310028ae6 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.cs
@@ -1,3 +1,4 @@
+using System.Collections;
using JetBrains.Annotations;
// ReSharper disable NonReadonlyMemberInGetHashCode
@@ -65,6 +66,34 @@ private bool EvaluateIsManyToMany()
return false;
}
+ ///
+ public override void SetValue(object resource, object? newValue)
+ {
+ ArgumentGuard.NotNull(newValue);
+ AssertIsIdentifiableCollection(newValue);
+
+ base.SetValue(resource, newValue);
+ }
+
+ private void AssertIsIdentifiableCollection(object newValue)
+ {
+ if (newValue is not IEnumerable enumerable)
+ {
+ throw new InvalidOperationException($"Resource of type '{newValue.GetType()}' must be a collection.");
+ }
+
+ foreach (object? element in enumerable)
+ {
+ if (element == null)
+ {
+ throw new InvalidOperationException("Resource collection must not contain null values.");
+ }
+
+ AssertIsIdentifiable(element);
+ }
+ }
+
+ ///
public override bool Equals(object? obj)
{
if (ReferenceEquals(this, obj))
@@ -82,6 +111,7 @@ public override bool Equals(object? obj)
return _capabilities == other._capabilities && base.Equals(other);
}
+ ///
public override int GetHashCode()
{
return HashCode.Combine(_capabilities, base.GetHashCode());
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.cs
index c0416c92fb..72212c76f2 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.cs
@@ -64,6 +64,14 @@ private bool EvaluateIsOneToOne()
return false;
}
+ ///
+ public override void SetValue(object resource, object? newValue)
+ {
+ AssertIsIdentifiable(newValue);
+ base.SetValue(resource, newValue);
+ }
+
+ ///
public override bool Equals(object? obj)
{
if (ReferenceEquals(this, obj))
@@ -81,6 +89,7 @@ public override bool Equals(object? obj)
return _capabilities == other._capabilities && base.Equals(other);
}
+ ///
public override int GetHashCode()
{
return HashCode.Combine(_capabilities, base.GetHashCode());
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs
index dd94bab221..0b4848ada1 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs
@@ -86,6 +86,7 @@ public bool CanInclude
set => _canInclude = value;
}
+ ///
public override bool Equals(object? obj)
{
if (ReferenceEquals(this, obj))
@@ -103,6 +104,7 @@ public override bool Equals(object? obj)
return _rightType?.ClrType == other._rightType?.ClrType && Links == other.Links && base.Equals(other);
}
+ ///
public override int GetHashCode()
{
return HashCode.Combine(_rightType?.ClrType, Links, base.GetHashCode());
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs
index e8e1d17aca..3a3707442c 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs
@@ -68,6 +68,7 @@ internal set
public object? GetValue(object resource)
{
ArgumentGuard.NotNull(resource);
+ AssertIsIdentifiable(resource);
if (Property.GetMethod == null)
{
@@ -82,7 +83,7 @@ internal set
{
throw new InvalidOperationException(
$"Unable to get property value of '{Property.DeclaringType!.Name}.{Property.Name}' on instance of type '{resource.GetType().Name}'.",
- exception);
+ exception.InnerException ?? exception);
}
}
@@ -90,9 +91,10 @@ internal set
/// Sets the value of this field on the specified resource instance. Throws if the property is read-only or if the field does not belong to the specified
/// resource instance.
///
- public void SetValue(object resource, object? newValue)
+ public virtual void SetValue(object resource, object? newValue)
{
ArgumentGuard.NotNull(resource);
+ AssertIsIdentifiable(resource);
if (Property.SetMethod == null)
{
@@ -107,15 +109,25 @@ public void SetValue(object resource, object? newValue)
{
throw new InvalidOperationException(
$"Unable to set property value of '{Property.DeclaringType!.Name}.{Property.Name}' on instance of type '{resource.GetType().Name}'.",
- exception);
+ exception.InnerException ?? exception);
}
}
+ protected void AssertIsIdentifiable(object? resource)
+ {
+ if (resource != null && resource is not IIdentifiable)
+ {
+ throw new InvalidOperationException($"Resource of type '{resource.GetType()}' does not implement {nameof(IIdentifiable)}.");
+ }
+ }
+
+ ///
public override string? ToString()
{
return _publicName ?? (_property != null ? _property.Name : base.ToString());
}
+ ///
public override bool Equals(object? obj)
{
if (ReferenceEquals(this, obj))
@@ -133,6 +145,7 @@ public override bool Equals(object? obj)
return _publicName == other._publicName && _property == other._property;
}
+ ///
public override int GetHashCode()
{
return HashCode.Combine(_publicName, _property);
diff --git a/src/JsonApiDotNetCore/Queries/FieldSelectors.cs b/src/JsonApiDotNetCore/Queries/FieldSelectors.cs
index ffd95c01bc..04b32b6499 100644
--- a/src/JsonApiDotNetCore/Queries/FieldSelectors.cs
+++ b/src/JsonApiDotNetCore/Queries/FieldSelectors.cs
@@ -52,9 +52,10 @@ public void IncludeAttributes(IEnumerable attributes)
}
}
- public void IncludeRelationship(RelationshipAttribute relationship, QueryLayer? queryLayer)
+ public void IncludeRelationship(RelationshipAttribute relationship, QueryLayer queryLayer)
{
ArgumentGuard.NotNull(relationship);
+ ArgumentGuard.NotNull(queryLayer);
this[relationship] = queryLayer;
}
diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs
index bdac0d8962..541b50a220 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs
@@ -420,7 +420,7 @@ private object ConvertStringToType(string value, Type type)
private Converter GetConstantValueConverterForAttribute(AttrAttribute attribute)
{
- return stringValue => attribute.Property.Name == nameof(IIdentifiable