Skip to content

Commit e95befd

Browse files
authoredFeb 22, 2017
Merge pull request #35 from Research-Institute/sideload-relationships
Sideload relationships
2 parents abc7de0 + 464fc41 commit e95befd

File tree

9 files changed

+254
-19
lines changed

9 files changed

+254
-19
lines changed
 

‎.travis.yml

+3
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ before_script:
77
- psql -c 'create database JsonApiDotNetCoreExample;' -U postgres
88
mono: none
99
dotnet: 1.0.0-preview2-1-003177
10+
branches:
11+
only:
12+
- master
1013
script:
1114
- ./build.sh

‎src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

+72-13
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,17 @@ public Document Build(IIdentifiable entity)
2727
Data = _getData(contextEntity, entity)
2828
};
2929

30+
document.Included = _appendIncludedObject(document.Included, contextEntity, entity);
31+
3032
return document;
3133
}
3234

3335
public Documents Build(IEnumerable<IIdentifiable> entities)
3436
{
3537
var entityType = entities
36-
.GetType()
37-
.GenericTypeArguments[0];
38-
38+
.GetType()
39+
.GenericTypeArguments[0];
40+
3941
var contextEntity = _contextGraph.GetContextEntity(entityType);
4042

4143
var documents = new Documents
@@ -44,9 +46,25 @@ public Documents Build(IEnumerable<IIdentifiable> entities)
4446
};
4547

4648
foreach (var entity in entities)
49+
{
4750
documents.Data.Add(_getData(contextEntity, entity));
51+
documents.Included = _appendIncludedObject(documents.Included, contextEntity, entity);
52+
}
4853

49-
return documents;
54+
return documents;
55+
}
56+
57+
private List<DocumentData> _appendIncludedObject(List<DocumentData> includedObject, ContextEntity contextEntity, IIdentifiable entity)
58+
{
59+
var includedEntities = _getIncludedEntities(contextEntity, entity);
60+
if (includedEntities.Count > 0)
61+
{
62+
if (includedObject == null)
63+
includedObject = new List<DocumentData>();
64+
includedObject.AddRange(includedEntities);
65+
}
66+
67+
return includedObject;
5068
}
5169

5270
private DocumentData _getData(ContextEntity contextEntity, IIdentifiable entity)
@@ -61,20 +79,21 @@ private DocumentData _getData(ContextEntity contextEntity, IIdentifiable entity)
6179
return data;
6280

6381
data.Attributes = new Dictionary<string, object>();
64-
data.Relationships = new Dictionary<string, RelationshipData>();
6582

6683
contextEntity.Attributes.ForEach(attr =>
6784
{
6885
data.Attributes.Add(attr.PublicAttributeName, attr.GetValue(entity));
6986
});
7087

71-
_addRelationships(data, contextEntity, entity);
88+
if (contextEntity.Relationships.Count > 0)
89+
_addRelationships(data, contextEntity, entity);
7290

7391
return data;
7492
}
7593

7694
private void _addRelationships(DocumentData data, ContextEntity contextEntity, IIdentifiable entity)
7795
{
96+
data.Relationships = new Dictionary<string, RelationshipData>();
7897
var linkBuilder = new LinkBuilder(_jsonApiContext);
7998

8099
contextEntity.Relationships.ForEach(r =>
@@ -88,12 +107,12 @@ private void _addRelationships(DocumentData data, ContextEntity contextEntity, I
88107
}
89108
};
90109

91-
if (_hasRelationship(r.RelationshipName))
110+
if (_relationshipIsIncluded(r.RelationshipName))
92111
{
93112
var navigationEntity = _jsonApiContext.ContextGraph
94113
.GetRelationship(entity, r.RelationshipName);
95114

96-
if(navigationEntity is IEnumerable)
115+
if (navigationEntity is IEnumerable)
97116
relationshipData.ManyData = _getRelationships((IEnumerable<object>)navigationEntity, r.RelationshipName);
98117
else
99118
relationshipData.SingleData = _getRelationship(navigationEntity, r.RelationshipName);
@@ -103,20 +122,60 @@ private void _addRelationships(DocumentData data, ContextEntity contextEntity, I
103122
});
104123
}
105124

106-
private bool _hasRelationship(string relationshipName)
125+
private List<DocumentData> _getIncludedEntities(ContextEntity contextEntity, IIdentifiable entity)
126+
{
127+
var included = new List<DocumentData>();
128+
129+
contextEntity.Relationships.ForEach(r =>
130+
{
131+
if (!_relationshipIsIncluded(r.RelationshipName)) return;
132+
133+
var navigationEntity = _jsonApiContext.ContextGraph.GetRelationship(entity, r.RelationshipName);
134+
135+
if (navigationEntity is IEnumerable)
136+
foreach (var includedEntity in (IEnumerable)navigationEntity)
137+
included.Add(_getIncludedEntity((IIdentifiable)includedEntity));
138+
else
139+
included.Add(_getIncludedEntity((IIdentifiable)navigationEntity));
140+
});
141+
142+
return included;
143+
}
144+
145+
private DocumentData _getIncludedEntity(IIdentifiable entity)
107146
{
108-
return _jsonApiContext.IncludedRelationships != null &&
147+
var contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(entity.GetType());
148+
149+
var data = new DocumentData
150+
{
151+
Type = contextEntity.EntityName,
152+
Id = entity.Id.ToString()
153+
};
154+
155+
data.Attributes = new Dictionary<string, object>();
156+
157+
contextEntity.Attributes.ForEach(attr =>
158+
{
159+
data.Attributes.Add(attr.PublicAttributeName, attr.GetValue(entity));
160+
});
161+
162+
return data;
163+
}
164+
165+
private bool _relationshipIsIncluded(string relationshipName)
166+
{
167+
return _jsonApiContext.IncludedRelationships != null &&
109168
_jsonApiContext.IncludedRelationships.Contains(relationshipName.ToProperCase());
110169
}
111170

112171
private List<Dictionary<string, string>> _getRelationships(IEnumerable<object> entities, string relationshipName)
113172
{
114173
var objType = entities.GetType().GenericTypeArguments[0];
115-
174+
116175
var typeName = _jsonApiContext.ContextGraph.GetContextEntity(objType);
117176

118177
var relationships = new List<Dictionary<string, string>>();
119-
foreach(var entity in entities)
178+
foreach (var entity in entities)
120179
{
121180
relationships.Add(new Dictionary<string, string> {
122181
{"type", typeName.EntityName.Dasherize() },
@@ -128,7 +187,7 @@ private List<Dictionary<string, string>> _getRelationships(IEnumerable<object> e
128187
private Dictionary<string, string> _getRelationship(object entity, string relationshipName)
129188
{
130189
var objType = entity.GetType();
131-
190+
132191
var typeName = _jsonApiContext.ContextGraph.GetContextEntity(objType);
133192

134193
return new Dictionary<string, string> {

‎src/JsonApiDotNetCore/Models/Document.cs

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Generic;
12
using Newtonsoft.Json;
23

34
namespace JsonApiDotNetCore.Models
@@ -6,5 +7,8 @@ public class Document
67
{
78
[JsonProperty("data")]
89
public DocumentData Data { get; set; }
10+
11+
[JsonProperty("included")]
12+
public List<DocumentData> Included { get; set; }
913
}
1014
}

‎src/JsonApiDotNetCore/Models/DocumentData.cs

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ public string Type
2020

2121
[JsonProperty("attributes")]
2222
public Dictionary<string, object> Attributes { get; set; }
23-
2423

2524
[JsonProperty("relationships")]
2625
public Dictionary<string, RelationshipData> Relationships { get; set; }

‎src/JsonApiDotNetCore/Models/Documents.cs

+3
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ public class Documents
77
{
88
[JsonProperty("data")]
99
public List<DocumentData> Data { get; set; }
10+
11+
[JsonProperty("included")]
12+
public List<DocumentData> Included { get; set; }
1013
}
1114
}

‎src/JsonApiDotNetCore/project.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "0.2.10",
2+
"version": "0.2.11",
33

44
"dependencies": {
55
"Microsoft.NETCore.App": {

‎src/JsonApiDotNetCoreExample/Models/Person.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ public class Person : Identifiable<int>
88
{
99
public override int Id { get; set; }
1010

11-
[Attr("firstName")]
11+
[Attr("first-name")]
1212
public string FirstName { get; set; }
1313

14-
[Attr("lastName")]
14+
[Attr("last-name")]
1515
public string LastName { get; set; }
1616

1717
public virtual List<TodoItem> TodoItems { get; set; }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
using System.Net;
2+
using System.Net.Http;
3+
using System.Threading.Tasks;
4+
using DotNetCoreDocs;
5+
using DotNetCoreDocs.Writers;
6+
using JsonApiDotNetCoreExample;
7+
using Microsoft.AspNetCore.Hosting;
8+
using Microsoft.AspNetCore.TestHost;
9+
using Newtonsoft.Json;
10+
using Xunit;
11+
using Person = JsonApiDotNetCoreExample.Models.Person;
12+
using JsonApiDotNetCore.Models;
13+
using JsonApiDotNetCoreExample.Data;
14+
using Bogus;
15+
using JsonApiDotNetCoreExample.Models;
16+
using System;
17+
18+
namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec.DocumentTests
19+
{
20+
[Collection("WebHostCollection")]
21+
public class Included
22+
{
23+
private DocsFixture<Startup, JsonDocWriter> _fixture;
24+
private AppDbContext _context;
25+
private Faker<Person> _personFaker;
26+
private Faker<TodoItem> _todoItemFaker;
27+
28+
public Included(DocsFixture<Startup, JsonDocWriter> fixture)
29+
{
30+
_fixture = fixture;
31+
_context = fixture.GetService<AppDbContext>();
32+
_personFaker = new Faker<Person>()
33+
.RuleFor(p => p.FirstName, f => f.Name.FirstName())
34+
.RuleFor(p => p.LastName, f => f.Name.LastName());
35+
36+
_todoItemFaker = new Faker<TodoItem>()
37+
.RuleFor(t => t.Description, f => f.Lorem.Sentence())
38+
.RuleFor(t => t.Ordinal, f => f.Random.Number());
39+
}
40+
41+
[Fact]
42+
public async Task GET_Included_Contains_SideloadedData_ForManyToOne()
43+
{
44+
// arrange
45+
var builder = new WebHostBuilder()
46+
.UseStartup<Startup>();
47+
48+
var httpMethod = new HttpMethod("GET");
49+
var route = $"/api/v1/todo-items?include=owner";
50+
51+
var server = new TestServer(builder);
52+
var client = server.CreateClient();
53+
var request = new HttpRequestMessage(httpMethod, route);
54+
55+
// act
56+
var response = await client.SendAsync(request);
57+
var documents = JsonConvert.DeserializeObject<Documents>(await response.Content.ReadAsStringAsync());
58+
var data = documents.Data[0];
59+
60+
// assert
61+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
62+
Assert.NotEmpty(documents.Included);
63+
Assert.Equal(documents.Data.Count, documents.Included.Count);
64+
}
65+
66+
[Fact]
67+
public async Task GET_ById_Included_Contains_SideloadedData_ForManyToOne()
68+
{
69+
// arrange
70+
var person = _personFaker.Generate();
71+
var todoItem = _todoItemFaker.Generate();
72+
todoItem.Owner = person;
73+
_context.TodoItems.Add(todoItem);
74+
_context.SaveChanges();
75+
76+
var builder = new WebHostBuilder()
77+
.UseStartup<Startup>();
78+
79+
var httpMethod = new HttpMethod("GET");
80+
81+
var route = $"/api/v1/todo-items/{todoItem.Id}?include=owner";
82+
83+
var server = new TestServer(builder);
84+
var client = server.CreateClient();
85+
var request = new HttpRequestMessage(httpMethod, route);
86+
87+
// act
88+
var response = await client.SendAsync(request);
89+
var responseString = await response.Content.ReadAsStringAsync();
90+
var document = JsonConvert.DeserializeObject<Document>(responseString);
91+
92+
// assert
93+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
94+
Assert.NotEmpty(document.Included);
95+
Assert.Equal(person.Id.ToString(), document.Included[0].Id);
96+
Assert.Equal(person.FirstName, document.Included[0].Attributes["first-name"]);
97+
Assert.Equal(person.LastName, document.Included[0].Attributes["last-name"]);
98+
}
99+
100+
[Fact]
101+
public async Task GET_Included_Contains_SideloadedData_OneToMany()
102+
{
103+
// arrange
104+
_context.People.RemoveRange(_context.People); // ensure all people have todo-items
105+
var person = _personFaker.Generate();
106+
var todoItem = _todoItemFaker.Generate();
107+
todoItem.Owner = person;
108+
_context.TodoItems.Add(todoItem);
109+
_context.SaveChanges();
110+
111+
var builder = new WebHostBuilder()
112+
.UseStartup<Startup>();
113+
114+
var httpMethod = new HttpMethod("GET");
115+
var route = $"/api/v1/people?include=todo-items";
116+
117+
var server = new TestServer(builder);
118+
var client = server.CreateClient();
119+
var request = new HttpRequestMessage(httpMethod, route);
120+
121+
// act
122+
var response = await client.SendAsync(request);
123+
var documents = JsonConvert.DeserializeObject<Documents>(await response.Content.ReadAsStringAsync());
124+
var data = documents.Data[0];
125+
126+
// assert
127+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
128+
Assert.NotEmpty(documents.Included);
129+
Assert.Equal(documents.Data.Count, documents.Included.Count);
130+
}
131+
132+
[Fact]
133+
public async Task GET_ById_Included_Contains_SideloadedData_ForOneToMany()
134+
{
135+
// arrange
136+
const int numberOfTodoItems = 5;
137+
var person = _personFaker.Generate();
138+
for (var i = 0; i < numberOfTodoItems; i++)
139+
{
140+
var todoItem = _todoItemFaker.Generate();
141+
todoItem.Owner = person;
142+
_context.TodoItems.Add(todoItem);
143+
_context.SaveChanges();
144+
}
145+
146+
var builder = new WebHostBuilder()
147+
.UseStartup<Startup>();
148+
149+
var httpMethod = new HttpMethod("GET");
150+
151+
var route = $"/api/v1/people/{person.Id}?include=todo-items";
152+
153+
var server = new TestServer(builder);
154+
var client = server.CreateClient();
155+
var request = new HttpRequestMessage(httpMethod, route);
156+
157+
// act
158+
var response = await client.SendAsync(request);
159+
var responseString = await response.Content.ReadAsStringAsync();
160+
var document = JsonConvert.DeserializeObject<Document>(responseString);
161+
162+
// assert
163+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
164+
Assert.NotEmpty(document.Included);
165+
Assert.Equal(numberOfTodoItems, document.Included.Count);
166+
}
167+
}
168+
}

‎test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/Relationships.cs renamed to ‎test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Relationships.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
using JsonApiDotNetCore.Models;
1212
using JsonApiDotNetCoreExample.Data;
1313
using System.Linq;
14-
using System;
1514

16-
namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec
15+
namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec.DocumentTests
1716
{
1817
[Collection("WebHostCollection")]
1918
public class Relationships

0 commit comments

Comments
 (0)
Please sign in to comment.