Skip to content

Commit 43bd9e6

Browse files
committed
test(patch): can patch relationship links
prior to this commit there was nothing to handle updating relationships
1 parent 11424df commit 43bd9e6

File tree

13 files changed

+227
-6
lines changed

13 files changed

+227
-6
lines changed

src/JsonApiDotNetCore/Controllers/JsonApiController.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,40 @@ public virtual async Task<IActionResult> PatchAsync(TId id, [FromBody] T entity)
159159
var updatedEntity = await _entities.UpdateAsync(id, entity);
160160

161161
if(updatedEntity == null) return NotFound();
162-
162+
163163
return Ok(updatedEntity);
164164
}
165165

166+
[HttpPatch("{id}/relationships/{relationshipName}")]
167+
public virtual async Task<IActionResult> PatchRelationshipsAsync(TId id, string relationshipName, [FromBody] List<DocumentData> relationships)
168+
{
169+
relationshipName = _jsonApiContext.ContextGraph
170+
.GetRelationshipName<T>(relationshipName.ToProperCase());
171+
172+
if (relationshipName == null)
173+
{
174+
_logger?.LogInformation($"Relationship name not specified returning 422");
175+
return UnprocessableEntity();
176+
}
177+
178+
var entity = await _entities.GetAndIncludeAsync(id, relationshipName);
179+
180+
if (entity == null)
181+
return NotFound();
182+
183+
var relationship = _jsonApiContext.ContextGraph
184+
.GetContextEntity(typeof(T))
185+
.Relationships
186+
.FirstOrDefault(r => r.RelationshipName == relationshipName);
187+
188+
var relationshipIds = relationships.Select(r=>r.Id);
189+
190+
await _entities.UpdateRelationshipsAsync(entity, relationship, relationshipIds);
191+
192+
return Ok();
193+
194+
}
195+
166196
[HttpDelete("{id}")]
167197
public virtual async Task<IActionResult> DeleteAsync(TId id)
168198
{

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,13 @@ public virtual async Task<TEntity> UpdateAsync(TId id, TEntity entity)
105105

106106
await _context.SaveChangesAsync();
107107

108-
return oldEntity;
108+
return oldEntity;
109+
}
110+
111+
public async Task UpdateRelationshipsAsync(object parent, Relationship relationship, IEnumerable<string> relationshipIds)
112+
{
113+
var genericProcessor = GenericProcessorFactory.GetProcessor(relationship.BaseType, _context);
114+
await genericProcessor.UpdateRelationshipsAsync(parent, relationship, relationshipIds);
109115
}
110116

111117
public virtual async Task<bool> DeleteAsync(TId id)

src/JsonApiDotNetCore/Data/IEntityRepository.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using System.Linq;
33
using System.Threading.Tasks;
4+
using JsonApiDotNetCore.Internal;
45
using JsonApiDotNetCore.Internal.Query;
56
using JsonApiDotNetCore.Models;
67

@@ -33,6 +34,8 @@ public interface IEntityRepository<TEntity, in TId>
3334

3435
Task<TEntity> UpdateAsync(TId id, TEntity entity);
3536

37+
Task UpdateRelationshipsAsync(object parent, Relationship relationship, IEnumerable<string> relationshipIds);
38+
3639
Task<bool> DeleteAsync(TId id);
3740
}
3841
}

src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
4141
{
4242
var body = GetRequestBody(context.HttpContext.Request.Body);
4343
var jsonApiContext = GetService<IJsonApiContext>(context);
44-
var model = JsonApiDeSerializer.Deserialize(body, jsonApiContext);
44+
var model = jsonApiContext.IsRelationshipPath ?
45+
JsonApiDeSerializer.DeserializeRelationship(body, jsonApiContext) :
46+
JsonApiDeSerializer.Deserialize(body, jsonApiContext);
4547

4648
if(model == null)
4749
logger?.LogError("An error occurred while de-serializing the payload");
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using JsonApiDotNetCore.Extensions;
5+
using JsonApiDotNetCore.Models;
6+
using Microsoft.EntityFrameworkCore;
7+
8+
namespace JsonApiDotNetCore.Internal
9+
{
10+
public class GenericProcessor<T> : IGenericProcessor where T : class, IIdentifiable
11+
{
12+
private readonly DbContext _context;
13+
public GenericProcessor(DbContext context)
14+
{
15+
_context = context;
16+
}
17+
18+
public async Task UpdateRelationshipsAsync(object parent, Relationship relationship, IEnumerable<string> relationshipIds)
19+
{
20+
var relationshipType = relationship.BaseType;
21+
22+
var entities = _context.GetDbSet<T>().Where(x => relationshipIds.Contains(x.Id.ToString())).ToList();
23+
relationship.SetValue(parent, entities);
24+
25+
await _context.SaveChangesAsync();
26+
}
27+
}
28+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using Microsoft.EntityFrameworkCore;
3+
4+
namespace JsonApiDotNetCore.Internal
5+
{
6+
/// <summary>
7+
/// Used to generate a generic operations processor when the types
8+
/// are not know until runtime. The typical use case would be for
9+
/// accessing relationship data.
10+
/// </summary>
11+
public static class GenericProcessorFactory
12+
{
13+
public static IGenericProcessor GetProcessor(Type type, DbContext dbContext)
14+
{
15+
var repositoryType = typeof(GenericProcessor<>).MakeGenericType(type);
16+
return (IGenericProcessor)Activator.CreateInstance(repositoryType, dbContext);
17+
}
18+
}
19+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Collections.Generic;
2+
using System.Threading.Tasks;
3+
4+
namespace JsonApiDotNetCore.Internal
5+
{
6+
public interface IGenericProcessor
7+
{
8+
Task UpdateRelationshipsAsync(object parent, Relationship relationship, IEnumerable<string> relationshipIds);
9+
}
10+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
11
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Reflection;
6+
using JsonApiDotNetCore.Extensions;
27

38
namespace JsonApiDotNetCore.Internal
49
{
510
public class Relationship
611
{
712
public Type Type { get; set; }
13+
public Type BaseType { get {
14+
return (Type.GetInterfaces().Contains(typeof(IEnumerable))) ?
15+
Type.GenericTypeArguments[0] :
16+
Type;
17+
} }
18+
819
public string RelationshipName { get; set; }
20+
21+
public void SetValue(object entity, object newValue)
22+
{
23+
var propertyInfo = entity
24+
.GetType()
25+
.GetProperty(RelationshipName);
26+
27+
propertyInfo.SetValue(entity, newValue);
28+
}
929
}
1030
}

src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using JsonApiDotNetCore.Models;
88
using JsonApiDotNetCore.Services;
99
using Newtonsoft.Json;
10+
using Newtonsoft.Json.Linq;
1011

1112
namespace JsonApiDotNetCore.Serialization
1213
{
@@ -15,12 +16,21 @@ public static class JsonApiDeSerializer
1516
public static object Deserialize(string requestBody, IJsonApiContext context)
1617
{
1718
var document = JsonConvert.DeserializeObject<Document>(requestBody);
18-
1919
var entity = DataToObject(document.Data, context);
20-
2120
return entity;
2221
}
2322

23+
public static object DeserializeRelationship(string requestBody, IJsonApiContext context)
24+
{
25+
var data = JToken.Parse(requestBody)["data"];
26+
27+
if(data is JArray)
28+
return data.ToObject<List<DocumentData>>();
29+
30+
return new List<DocumentData> { data.ToObject<DocumentData>() };
31+
}
32+
33+
2434
public static List<TEntity> DeserializeList<TEntity>(string requestBody, IJsonApiContext context)
2535
{
2636
var documents = JsonConvert.DeserializeObject<Documents>(requestBody);

src/JsonApiDotNetCore/Services/IJsonApiContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public interface IJsonApiContext
1515
QuerySet QuerySet { get; set; }
1616
bool IsRelationshipData { get; set; }
1717
List<string> IncludedRelationships { get; set; }
18+
bool IsRelationshipPath { get; }
1819
PageManager PageManager { get; set; }
20+
1921
}
2022
}

0 commit comments

Comments
 (0)