Skip to content

Commit b322162

Browse files
authored
Merge pull request #64 from Research-Institute/fix/patch-relationships
Fix/patch relationships
2 parents 7a95169 + f6d5c34 commit b322162

File tree

8 files changed

+106
-16
lines changed

8 files changed

+106
-16
lines changed

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,12 @@ public virtual async Task<TEntity> UpdateAsync(TId id, TEntity entity)
105105
attr.SetValue(oldEntity, attr.GetValue(entity));
106106
});
107107

108+
foreach(var relationship in _jsonApiContext.RelationshipsToUpdate)
109+
relationship.Key.SetValue(oldEntity, relationship.Value);
110+
108111
await _context.SaveChangesAsync();
109112

110-
return oldEntity;
113+
return oldEntity;
111114
}
112115

113116
public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable<string> relationshipIds)

src/JsonApiDotNetCore/Models/HasManyAttribute.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System.Reflection;
22

33
namespace JsonApiDotNetCore.Models
44
{
@@ -9,5 +9,14 @@ public HasManyAttribute(string publicName)
99
{
1010
PublicRelationshipName = publicName;
1111
}
12+
13+
public override void SetValue(object entity, object newValue)
14+
{
15+
var propertyInfo = entity
16+
.GetType()
17+
.GetProperty(InternalRelationshipName);
18+
19+
propertyInfo.SetValue(entity, newValue);
20+
}
1221
}
1322
}

src/JsonApiDotNetCore/Models/HasOneAttribute.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Reflection;
2+
13
namespace JsonApiDotNetCore.Models
24
{
35
public class HasOneAttribute : RelationshipAttribute
@@ -7,5 +9,18 @@ public HasOneAttribute(string publicName)
79
{
810
PublicRelationshipName = publicName;
911
}
12+
13+
public override void SetValue(object entity, object newValue)
14+
{
15+
var propertyName = (newValue.GetType() == Type)
16+
? InternalRelationshipName
17+
: $"{InternalRelationshipName}Id";
18+
19+
var propertyInfo = entity
20+
.GetType()
21+
.GetProperty(propertyName);
22+
23+
propertyInfo.SetValue(entity, newValue);
24+
}
1025
}
1126
}
Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
using System;
2-
using System.Reflection;
32

43
namespace JsonApiDotNetCore.Models
54
{
6-
public class RelationshipAttribute : Attribute
5+
public abstract class RelationshipAttribute : Attribute
76
{
87
protected RelationshipAttribute(string publicName)
98
{
@@ -16,13 +15,6 @@ protected RelationshipAttribute(string publicName)
1615
public bool IsHasMany { get { return this.GetType() == typeof(HasManyAttribute); } }
1716
public bool IsHasOne { get { return this.GetType() == typeof(HasOneAttribute); } }
1817

19-
public void SetValue(object entity, object newValue)
20-
{
21-
var propertyInfo = entity
22-
.GetType()
23-
.GetProperty(InternalRelationshipName);
24-
25-
propertyInfo.SetValue(entity, newValue);
26-
}
18+
public abstract void SetValue(object entity, object newValue);
2719
}
2820
}

src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,18 @@ private object _setHasOneRelationship(object entity,
141141

142142
if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData))
143143
{
144+
var relationshipAttr = _jsonApiContext.RequestEntity.Relationships
145+
.SingleOrDefault(r => r.PublicRelationshipName == relationshipName);
146+
144147
var data = (Dictionary<string, string>)relationshipData.ExposedData;
145148

146149
if (data == null) return entity;
147150

148151
var newValue = data["id"];
149152
var convertedValue = TypeHelper.ConvertType(newValue, entityProperty.PropertyType);
153+
154+
_jsonApiContext.RelationshipsToUpdate[relationshipAttr] = convertedValue;
155+
150156
entityProperty.SetValue(entity, convertedValue);
151157
}
152158

src/JsonApiDotNetCore/Services/IJsonApiContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using JsonApiDotNetCore.Configuration;
44
using JsonApiDotNetCore.Internal;
55
using JsonApiDotNetCore.Internal.Query;
6+
using JsonApiDotNetCore.Models;
67

78
namespace JsonApiDotNetCore.Services
89
{
@@ -20,5 +21,6 @@ public interface IJsonApiContext
2021
PageManager PageManager { get; set; }
2122
IMetaBuilder MetaBuilder { get; set; }
2223
IGenericProcessorFactory GenericProcessorFactory { get; set; }
24+
Dictionary<RelationshipAttribute, object> RelationshipsToUpdate { get; set; }
2325
}
2426
}

src/JsonApiDotNetCore/Services/JsonApiContext.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using JsonApiDotNetCore.Configuration;
55
using JsonApiDotNetCore.Internal;
66
using JsonApiDotNetCore.Internal.Query;
7+
using JsonApiDotNetCore.Models;
78
using Microsoft.AspNetCore.Http;
89

910
namespace JsonApiDotNetCore.Services
@@ -23,6 +24,7 @@ public JsonApiContext(
2324
Options = options;
2425
MetaBuilder = metaBuilder;
2526
GenericProcessorFactory = genericProcessorFactory;
27+
RelationshipsToUpdate = new Dictionary<RelationshipAttribute, object>();
2628
}
2729

2830
public JsonApiOptions Options { get; set; }
@@ -36,6 +38,7 @@ public JsonApiContext(
3638
public PageManager PageManager { get; set; }
3739
public IMetaBuilder MetaBuilder { get; set; }
3840
public IGenericProcessorFactory GenericProcessorFactory { get; set; }
41+
public Dictionary<RelationshipAttribute, object> RelationshipsToUpdate { get; set; }
3942

4043
public IJsonApiContext ApplyContext<T>()
4144
{

test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingDataTests.cs

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Collections.Generic;
21
using System.Linq;
32
using System.Net;
43
using System.Net.Http;
@@ -7,15 +6,15 @@
76
using Bogus;
87
using DotNetCoreDocs;
98
using DotNetCoreDocs.Writers;
10-
using JsonApiDotNetCore.Serialization;
11-
using JsonApiDotNetCore.Services;
129
using JsonApiDotNetCoreExample;
1310
using JsonApiDotNetCoreExample.Data;
1411
using JsonApiDotNetCoreExample.Models;
1512
using Microsoft.AspNetCore.Hosting;
1613
using Microsoft.AspNetCore.TestHost;
14+
using Microsoft.EntityFrameworkCore;
1715
using Newtonsoft.Json;
1816
using Xunit;
17+
using Person = JsonApiDotNetCoreExample.Models.Person;
1918

2019
namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec
2120
{
@@ -25,14 +24,18 @@ public class UpdatingDataTests
2524
private DocsFixture<Startup, JsonDocWriter> _fixture;
2625
private AppDbContext _context;
2726
private Faker<TodoItem> _todoItemFaker;
27+
private Faker<Person> _personFaker;
2828

2929
public UpdatingDataTests(DocsFixture<Startup, JsonDocWriter> fixture)
3030
{
3131
_fixture = fixture;
3232
_context = fixture.GetService<AppDbContext>();
33-
_todoItemFaker = new Faker<TodoItem>()
33+
_todoItemFaker = new Faker<TodoItem>()
3434
.RuleFor(t => t.Description, f => f.Lorem.Sentence())
3535
.RuleFor(t => t.Ordinal, f => f.Random.Number());
36+
_personFaker = new Faker<Person>()
37+
.RuleFor(p => p.FirstName, f => f.Name.FirstName())
38+
.RuleFor(p => p.LastName, f => f.Name.LastName());
3639
}
3740

3841
[Fact]
@@ -73,5 +76,62 @@ public async Task Respond_404_If_EntityDoesNotExist()
7376
// Assert
7477
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
7578
}
79+
80+
[Fact]
81+
public async Task Can_Patch_Entity_And_HasOne_Relationships()
82+
{
83+
// arrange
84+
var todoItem = _todoItemFaker.Generate();
85+
var person = _personFaker.Generate();
86+
_context.TodoItems.Add(todoItem);
87+
_context.People.Add(person);
88+
_context.SaveChanges();
89+
90+
var builder = new WebHostBuilder()
91+
.UseStartup<Startup>();
92+
var server = new TestServer(builder);
93+
var client = server.CreateClient();
94+
95+
var content = new
96+
{
97+
data = new
98+
{
99+
type = "todo-items",
100+
attributes = new
101+
{
102+
description = todoItem.Description,
103+
ordinal = todoItem.Ordinal
104+
},
105+
relationships = new
106+
{
107+
owner = new
108+
{
109+
data = new
110+
{
111+
type = "people",
112+
id = person.Id.ToString()
113+
}
114+
}
115+
}
116+
}
117+
};
118+
119+
var httpMethod = new HttpMethod("PATCH");
120+
var route = $"/api/v1/todo-items/{todoItem.Id}";
121+
var request = new HttpRequestMessage(httpMethod, route);
122+
123+
request.Content = new StringContent(JsonConvert.SerializeObject(content));
124+
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");
125+
126+
// Act
127+
var response = await client.SendAsync(request);
128+
var updatedTodoItem = _context.TodoItems.AsNoTracking()
129+
.Include(t => t.Owner)
130+
.SingleOrDefault(t => t.Id == todoItem.Id);
131+
132+
// Assert
133+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
134+
Assert.Equal(person.Id, updatedTodoItem.OwnerId);
135+
}
76136
}
77137
}

0 commit comments

Comments
 (0)