Skip to content

Commit 202b522

Browse files
author
Chris Santero
committed
add EphemeralRelatedResourceReader
1 parent 33d5705 commit 202b522

5 files changed

Lines changed: 182 additions & 0 deletions
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
3+
namespace JSONAPI.Core
4+
{
5+
/// <summary>
6+
/// Default implementation of <see cref="IEphemeralRelatedResourceCreator"/>, using Activator
7+
/// </summary>
8+
public class DefaultEphemeralRelatedResourceCreator : IEphemeralRelatedResourceCreator
9+
{
10+
/// <inheritdoc />
11+
public object CreateEphemeralResource(IResourceTypeRegistration resourceTypeRegistration, string id)
12+
{
13+
var obj = Activator.CreateInstance(resourceTypeRegistration.Type);
14+
resourceTypeRegistration.SetIdForResource(obj, id);
15+
return obj;
16+
}
17+
}
18+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using JSONAPI.Documents;
8+
using JSONAPI.Json;
9+
10+
namespace JSONAPI.Core
11+
{
12+
/// <summary>
13+
/// Populates property values on an ephemeral resource
14+
/// </summary>
15+
/// <typeparam name="T"></typeparam>
16+
public class EphemeralRelatedResourceReader<T> : IEphemeralRelatedResourceReader<T>
17+
{
18+
private readonly IResourceTypeRegistry _resourceTypeRegistry;
19+
private readonly IEphemeralRelatedResourceCreator _ephemeralRelatedResourceCreator;
20+
private readonly Lazy<IResourceTypeRegistration> _resourceTypeRegistration;
21+
private readonly MethodInfo _openSetToManyRelationshipValueMethod;
22+
23+
/// <summary>
24+
/// Creates a new EphemeralRelatedResourceReader
25+
/// </summary>
26+
/// <param name="resourceTypeRegistry"></param>
27+
/// <param name="ephemeralRelatedResourceCreator"></param>
28+
public EphemeralRelatedResourceReader(IResourceTypeRegistry resourceTypeRegistry, IEphemeralRelatedResourceCreator ephemeralRelatedResourceCreator)
29+
{
30+
_resourceTypeRegistry = resourceTypeRegistry;
31+
_ephemeralRelatedResourceCreator = ephemeralRelatedResourceCreator;
32+
_resourceTypeRegistration = new Lazy<IResourceTypeRegistration>(() => _resourceTypeRegistry.GetRegistrationForType(typeof(T)));
33+
_openSetToManyRelationshipValueMethod = GetType()
34+
.GetMethod("SetToManyRelationshipValue", BindingFlags.NonPublic | BindingFlags.Instance);
35+
}
36+
37+
public void SetProperty(T ephemeralResource, string jsonKey, IRelationshipObject relationshipObject)
38+
{
39+
var relationship = _resourceTypeRegistration.Value.GetFieldByName(jsonKey) as ResourceTypeRelationship;
40+
if (relationship == null) return;
41+
42+
if (relationship.IsToMany)
43+
SetPropertyForToManyRelationship(ephemeralResource, relationship, relationshipObject.Linkage);
44+
else
45+
SetPropertyForToOneRelationship(ephemeralResource, relationship, relationshipObject.Linkage);
46+
}
47+
48+
protected virtual void SetPropertyForToOneRelationship(T ephemeralResource, ResourceTypeRelationship relationship, IResourceLinkage linkage)
49+
{
50+
if (linkage == null)
51+
throw new DeserializationException("Missing linkage for to-one relationship",
52+
"Expected an object for to-one linkage, but no linkage was specified.", $"/data/relationships/{relationship.JsonKey}");
53+
54+
if (linkage.IsToMany)
55+
throw new DeserializationException("Invalid linkage for to-one relationship",
56+
"Expected an object or null for to-one linkage",
57+
$"/data/relationships/{relationship.JsonKey}/data");
58+
59+
var identifier = linkage.Identifiers.FirstOrDefault();
60+
if (identifier == null)
61+
{
62+
relationship.Property.SetValue(ephemeralResource, null);
63+
}
64+
else
65+
{
66+
var relatedObjectRegistration = _resourceTypeRegistry.GetRegistrationForResourceTypeName(identifier.Type);
67+
var relatedObject = _ephemeralRelatedResourceCreator.CreateEphemeralResource(relatedObjectRegistration, identifier.Id);
68+
69+
relationship.Property.SetValue(ephemeralResource, relatedObject);
70+
}
71+
}
72+
73+
protected virtual void SetPropertyForToManyRelationship(T ephemeralResource, ResourceTypeRelationship relationship,
74+
IResourceLinkage linkage)
75+
{
76+
if (linkage == null)
77+
throw new DeserializationException("Missing linkage for to-many relationship",
78+
"Expected an array for to-many linkage, but no linkage was specified.", $"/data/relationships/{relationship.JsonKey}");
79+
80+
if (!linkage.IsToMany)
81+
throw new DeserializationException("Invalid linkage for to-many relationship",
82+
"Expected an array for to-many linkage.",
83+
$"/data/relationships/{relationship.JsonKey}/data");
84+
85+
var newCollection = (from resourceIdentifier in linkage.Identifiers
86+
let relatedObjectRegistration = _resourceTypeRegistry.GetRegistrationForResourceTypeName(resourceIdentifier.Type)
87+
select _ephemeralRelatedResourceCreator.CreateEphemeralResource(relatedObjectRegistration, resourceIdentifier.Id)).ToList();
88+
89+
var method = _openSetToManyRelationshipValueMethod.MakeGenericMethod(relationship.RelatedType);
90+
method.Invoke(this, new object[] { ephemeralResource, newCollection, relationship });
91+
}
92+
93+
/// <summary>
94+
/// Sets the value of a to-many relationship
95+
/// </summary>
96+
protected void SetToManyRelationshipValue<TRelated>(object material, IEnumerable<object> relatedObjects, ResourceTypeRelationship relationship)
97+
{
98+
var currentValue = relationship.Property.GetValue(material);
99+
var typedArray = relatedObjects.Select(o => (TRelated)o).ToArray();
100+
if (relationship.Property.PropertyType.IsAssignableFrom(typeof(List<TRelated>)))
101+
{
102+
if (currentValue == null)
103+
{
104+
relationship.Property.SetValue(material, typedArray.ToList());
105+
}
106+
else
107+
{
108+
var listCurrentValue = (ICollection<TRelated>)currentValue;
109+
var itemsToAdd = typedArray.Except(listCurrentValue);
110+
var itemsToRemove = listCurrentValue.Except(typedArray).ToList();
111+
112+
foreach (var related in itemsToAdd)
113+
listCurrentValue.Add(related);
114+
115+
foreach (var related in itemsToRemove)
116+
listCurrentValue.Remove(related);
117+
}
118+
}
119+
else
120+
{
121+
relationship.Property.SetValue(material, typedArray);
122+
}
123+
}
124+
}
125+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace JSONAPI.Core
2+
{
3+
/// <summary>
4+
/// Service for creating an instance of a registered resource type
5+
/// </summary>
6+
public interface IEphemeralRelatedResourceCreator
7+
{
8+
/// <summary>
9+
/// Creates an instance of the specified resource type, with the given ID
10+
/// </summary>
11+
/// <param name="resourceTypeRegistration">The type to create the instance for</param>
12+
/// <param name="id">The ID for the resource</param>
13+
/// <returns>A new instance of the specified resource type</returns>
14+
object CreateEphemeralResource(IResourceTypeRegistration resourceTypeRegistration, string id);
15+
}
16+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using JSONAPI.Documents;
2+
3+
namespace JSONAPI.Core
4+
{
5+
/// <summary>
6+
/// Populates property values on an ephemeral resource from a relationship object
7+
/// </summary>
8+
/// <typeparam name="T"></typeparam>
9+
public interface IEphemeralRelatedResourceReader<T>
10+
{
11+
/// <summary>
12+
/// Sets the property on the ephemeral resource that corresponds to the given property
13+
/// </summary>
14+
/// <param name="ephemeralResource"></param>
15+
/// <param name="jsonKey"></param>
16+
/// <param name="relationshipObject"></param>
17+
void SetProperty(T ephemeralResource, string jsonKey, IRelationshipObject relationshipObject);
18+
}
19+
}

JSONAPI/JSONAPI.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@
7474
<Compile Include="Configuration\JsonApiConfiguration.cs" />
7575
<Compile Include="Configuration\ResourceTypeConfiguration.cs" />
7676
<Compile Include="Configuration\ResourceTypeRelationshipConfiguration.cs" />
77+
<Compile Include="Core\DefaultEphemeralRelatedResourceCreator.cs" />
7778
<Compile Include="Core\DefaultNamingConventions.cs" />
79+
<Compile Include="Core\EphemeralRelatedResourceReader.cs" />
80+
<Compile Include="Core\IEphemeralRelatedResourceCreator.cs" />
81+
<Compile Include="Core\IEphemeralRelatedResourceReader.cs" />
7882
<Compile Include="Core\INamingConventions.cs" />
7983
<Compile Include="Core\IResourceTypeRegistrar.cs" />
8084
<Compile Include="Core\PathVisitor.cs" />

0 commit comments

Comments
 (0)