From 5ef225347ce78e5d64255da714fe481632bc1732 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Mon, 14 Sep 2020 16:31:59 +0200 Subject: [PATCH 1/3] This fixes the originally reported problem. --- .../Serialization/BaseDeserializer.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs index cbe4b793c8..c6208213b7 100644 --- a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs @@ -168,15 +168,17 @@ private void SetHasOneRelationship(IIdentifiable resource, var rio = (ResourceIdentifierObject)relationshipData.Data; var relatedId = rio?.Id; + var resourceContext = ResourceContextProvider.GetResourceContext(relationshipData.SingleData.Type); + // this does not make sense in the following case: if we're setting the dependent of a one-to-one relationship, IdentifiablePropertyName should be null. var foreignKeyProperty = resourceProperties.FirstOrDefault(p => p.Name == attr.IdentifiablePropertyName); if (foreignKeyProperty != null) // there is a FK from the current resource pointing to the related object, // i.e. we're populating the relationship from the dependent side. - SetForeignKey(resource, foreignKeyProperty, attr, relatedId); + SetForeignKey(resource, foreignKeyProperty, attr, relatedId, resourceContext.ResourceType); - SetNavigation(resource, attr, relatedId); + SetNavigation(resource, attr, relatedId, resourceContext.ResourceType); // depending on if this base parser is used client-side or server-side, // different additional processing per field needs to be executed. @@ -187,7 +189,7 @@ private void SetHasOneRelationship(IIdentifiable resource, /// Sets the dependent side of a HasOne relationship, which means that a /// foreign key also will to be populated. /// - private void SetForeignKey(IIdentifiable resource, PropertyInfo foreignKey, HasOneAttribute attr, string id) + private void SetForeignKey(IIdentifiable resource, PropertyInfo foreignKey, HasOneAttribute attr, string id, Type relationshipType) { bool foreignKeyPropertyIsNullableType = Nullable.GetUnderlyingType(foreignKey.PropertyType) != null || foreignKey.PropertyType == typeof(string); @@ -198,7 +200,7 @@ private void SetForeignKey(IIdentifiable resource, PropertyInfo foreignKey, HasO throw new FormatException($"Cannot set required relationship identifier '{attr.IdentifiablePropertyName}' to null because it is a non-nullable type."); } - var typedId = TypeHelper.ConvertStringIdToTypedId(attr.Property.PropertyType, id, ResourceFactory); + var typedId = TypeHelper.ConvertStringIdToTypedId(relationshipType /* was: attr.Property.PropertyType */, id, ResourceFactory); foreignKey.SetValue(resource, typedId); } @@ -206,7 +208,8 @@ private void SetForeignKey(IIdentifiable resource, PropertyInfo foreignKey, HasO /// Sets the principal side of a HasOne relationship, which means no /// foreign key is involved. /// - private void SetNavigation(IIdentifiable resource, HasOneAttribute attr, string relatedId) + private void SetNavigation(IIdentifiable resource, HasOneAttribute attr, string relatedId, + Type relationshipType) { if (relatedId == null) { @@ -214,7 +217,7 @@ private void SetNavigation(IIdentifiable resource, HasOneAttribute attr, string } else { - var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(attr.RightType); + var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(relationshipType /* was: attr.RightType */); relatedInstance.StringId = relatedId; attr.SetValue(resource, relatedInstance, ResourceFactory); } From 36ca5be36cbe2949d9f8faf9f9a856bfb8197b06 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 16 Sep 2020 10:27:54 +0200 Subject: [PATCH 2/3] Fixed broken tests --- src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs index c6208213b7..136a5a1d79 100644 --- a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs @@ -168,7 +168,9 @@ private void SetHasOneRelationship(IIdentifiable resource, var rio = (ResourceIdentifierObject)relationshipData.Data; var relatedId = rio?.Id; - var resourceContext = ResourceContextProvider.GetResourceContext(relationshipData.SingleData.Type); + var resourceContext = relationshipData.SingleData == null + ? ResourceContextProvider.GetResourceContext(attr.RightType) + : ResourceContextProvider.GetResourceContext(relationshipData.SingleData.Type); // this does not make sense in the following case: if we're setting the dependent of a one-to-one relationship, IdentifiablePropertyName should be null. var foreignKeyProperty = resourceProperties.FirstOrDefault(p => p.Name == attr.IdentifiablePropertyName); From aba7b913aa29918c859e976974623380ffb0de5f Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Thu, 17 Sep 2020 11:34:56 +0200 Subject: [PATCH 3/3] Added some pointer comments for further investigation. --- .../Repositories/RepositoryRelationshipUpdateHelper.cs | 1 + .../Resources/Annotations/HasManyThroughAttribute.cs | 1 + src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs | 1 + .../Serialization/Client/Internal/ResponseDeserializer.cs | 1 + 4 files changed, 4 insertions(+) diff --git a/src/JsonApiDotNetCore/Repositories/RepositoryRelationshipUpdateHelper.cs b/src/JsonApiDotNetCore/Repositories/RepositoryRelationshipUpdateHelper.cs index d349abe17c..ab90c51026 100644 --- a/src/JsonApiDotNetCore/Repositories/RepositoryRelationshipUpdateHelper.cs +++ b/src/JsonApiDotNetCore/Repositories/RepositoryRelationshipUpdateHelper.cs @@ -114,6 +114,7 @@ private async Task UpdateManyToManyAsync(IIdentifiable parent, HasManyThroughAtt var newLinks = relationshipIds.Select(x => { + // TODO: [#696] Potential location where we crash if the relationship targets an abstract base class. var link = _resourceFactory.CreateInstance(relationship.ThroughType); relationship.LeftIdProperty.SetValue(link, TypeHelper.ConvertType(parentId, relationship.LeftIdProperty.PropertyType)); relationship.RightIdProperty.SetValue(link, TypeHelper.ConvertType(x, relationship.RightIdProperty.PropertyType)); diff --git a/src/JsonApiDotNetCore/Resources/Annotations/HasManyThroughAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/HasManyThroughAttribute.cs index 16d8cd1075..ae6ad132af 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/HasManyThroughAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/HasManyThroughAttribute.cs @@ -137,6 +137,7 @@ public override void SetValue(object resource, object newValue, IResourceFactory List throughResources = new List(); foreach (IIdentifiable identifiable in (IEnumerable)newValue) { + // TODO: [#696] Potential location where we crash if the relationship targets an abstract base class. object throughResource = resourceFactory.CreateInstance(ThroughType); LeftProperty.SetValue(throughResource, resource); RightProperty.SetValue(throughResource, identifiable); diff --git a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs index 136a5a1d79..9b417466e0 100644 --- a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs @@ -237,6 +237,7 @@ private void SetHasManyRelationship( { // if the relationship is set to null, no need to set the navigation property to null: this is the default value. var relatedResources = relationshipData.ManyData.Select(rio => { + // TODO: [#696] Potential location where we crash if the relationship targets an abstract base class. var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(attr.RightType); relatedInstance.StringId = rio.Id; return relatedInstance; diff --git a/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs b/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs index 277cd0b2c0..8b6beb4651 100644 --- a/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs @@ -87,6 +87,7 @@ protected override void AfterProcessField(IIdentifiable resource, ResourceFieldA /// private IIdentifiable ParseIncludedRelationship(RelationshipAttribute relationshipAttr, ResourceIdentifierObject relatedResourceIdentifier) { + // TODO: [#696] Potential location where we crash if the relationship targets an abstract base class. var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(relationshipAttr.RightType); relatedInstance.StringId = relatedResourceIdentifier.Id;