Skip to content

Commit e2ec868

Browse files
committed
feat(*): add relationship links
1 parent 1268c3f commit e2ec868

File tree

10 files changed

+154
-50
lines changed

10 files changed

+154
-50
lines changed

src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
using System.Collections.Generic;
22
using JsonApiDotNetCore.Internal;
33
using JsonApiDotNetCore.Models;
4+
using JsonApiDotNetCore.Services;
45

56
namespace JsonApiDotNetCore.Builders
67
{
78
public class DocumentBuilder
89
{
9-
private IContextGraph _contextGraph;
10+
private IJsonApiContext _jsonApiContext;
11+
private IContextGraph _contextGraph;
1012

11-
public DocumentBuilder(IContextGraph contextGraph)
13+
public DocumentBuilder(IJsonApiContext jsonApiContext)
1214
{
13-
_contextGraph = contextGraph;
15+
_jsonApiContext = jsonApiContext;
16+
_contextGraph = jsonApiContext.ContextGraph;
1417
}
1518

1619
public Document Build(IIdentifiable entity)
@@ -50,15 +53,30 @@ private DocumentData _getData(ContextEntity contextEntity, IIdentifiable entity)
5053
{
5154
Type = contextEntity.EntityName,
5255
Id = entity.Id.ToString(),
53-
Attributes = new Dictionary<string, object>()
56+
Attributes = new Dictionary<string, object>(),
57+
Relationships = new Dictionary<string, Dictionary<string, object>>()
5458
};
5559

5660
contextEntity.Attributes.ForEach(attr =>
5761
{
5862
data.Attributes.Add(attr.PublicAttributeName, attr.GetValue(entity));
5963
});
6064

65+
_addRelationships(data, contextEntity, entity);
66+
6167
return data;
6268
}
69+
70+
private void _addRelationships(DocumentData data, ContextEntity contextEntity, IIdentifiable entity)
71+
{
72+
var linkBuilder = new LinkBuilder(_jsonApiContext);
73+
74+
contextEntity.Relationships.ForEach(r => {
75+
data.Relationships.Add("links", new Dictionary<string,object> {
76+
{"self", linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.Id.ToString(), r.RelationshipName)},
77+
{"related", linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.Id.ToString(), r.RelationshipName)},
78+
});
79+
});
80+
}
6381
}
6482
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
2+
using JsonApiDotNetCore.Extensions;
3+
using JsonApiDotNetCore.Services;
4+
using Microsoft.AspNetCore.Http;
5+
6+
namespace JsonApiDotNetCore.Builders
7+
{
8+
public class LinkBuilder
9+
{
10+
IJsonApiContext _context;
11+
12+
public LinkBuilder(IJsonApiContext context)
13+
{
14+
_context = context;
15+
}
16+
17+
public string GetBasePath(HttpContext context, string entityName)
18+
{
19+
var r = context.Request;
20+
return $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}";
21+
}
22+
23+
private string GetNamespaceFromPath(string path, string entityName)
24+
{
25+
var nSpace = string.Empty;
26+
var segments = path.Split('/');
27+
for(var i = 1; i < segments.Length; i++)
28+
{
29+
if(segments[i].ToLower() == entityName.ToLower())
30+
break;
31+
32+
nSpace += $"/{segments[i].Dasherize()}";
33+
}
34+
return nSpace;
35+
}
36+
37+
public string GetSelfRelationLink(string parent, string parentId, string child)
38+
{
39+
return $"{_context.BasePath}/{parent.Dasherize()}/{parentId}/relationships/{child.Dasherize()}";
40+
}
41+
42+
public string GetRelatedRelationLink(string parent, string parentId, string child)
43+
{
44+
return $"{_context.BasePath}/{parent.Dasherize()}/{parentId}/{child.Dasherize()}";
45+
}
46+
}
47+
}

src/JsonApiDotNetCore/Controllers/JsonApiController.cs

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ public class JsonApiController<T> : JsonApiController<T, int> where T : class, I
1313
{
1414
public JsonApiController(
1515
ILoggerFactory loggerFactory,
16-
DbContext context,
16+
DbContext context,
1717
IJsonApiContext jsonApiContext)
1818
: base(loggerFactory, context, jsonApiContext)
19-
{ }
19+
{ }
2020
}
2121

2222
public class JsonApiController<T, TId> : Controller where T : class, IIdentifiable<TId>
@@ -28,9 +28,9 @@ public class JsonApiController<T, TId> : Controller where T : class, IIdentifiab
2828

2929
public JsonApiController(
3030
ILoggerFactory loggerFactory,
31-
DbContext context,
31+
DbContext context,
3232
IJsonApiContext jsonApiContext)
33-
{
33+
{
3434
_context = context;
3535
_dbSet = context.GetDbSet<T>();
3636
_jsonApiContext = jsonApiContext;
@@ -41,83 +41,94 @@ public JsonApiController(
4141
}
4242

4343
public JsonApiController(
44-
DbContext context,
44+
DbContext context,
4545
IJsonApiContext jsonApiContext)
46-
{
46+
{
4747
_context = context;
4848
_dbSet = context.GetDbSet<T>();
4949
_jsonApiContext = jsonApiContext;
5050
}
5151

5252
[HttpGet]
53-
public virtual IActionResult Get()
53+
public virtual IActionResult Get()
5454
{
55+
ApplyContext();
56+
5557
var entities = _dbSet.ToList();
5658
return Ok(entities);
5759
}
5860

5961
[HttpGet("{id}")]
60-
public virtual IActionResult Get(TId id)
62+
public virtual IActionResult Get(TId id)
6163
{
64+
ApplyContext();
65+
6266
var entity = _dbSet.FirstOrDefault(e => e.Id.Equals(id));
63-
64-
if(entity == null)
67+
68+
if (entity == null)
6569
return NotFound();
6670

6771
return Ok(entity);
6872
}
6973

7074
[HttpGet("{id}/{relationshipName}")]
71-
public virtual IActionResult GetRelationship(TId id, string relationshipName)
75+
public virtual IActionResult GetRelationship(TId id, string relationshipName)
7276
{
77+
ApplyContext();
78+
7379
relationshipName = _jsonApiContext.ContextGraph.GetRelationshipName<T>(relationshipName);
7480

75-
if(relationshipName == null)
81+
if (relationshipName == null)
7682
return NotFound();
7783

7884
var entity = _dbSet
7985
.Include(relationshipName)
8086
.FirstOrDefault(e => e.Id.Equals(id));
81-
82-
if(entity == null)
87+
88+
if (entity == null)
8389
return NotFound();
8490

8591
_logger?.LogInformation($"Looking up relationship '{relationshipName}' on {entity.GetType().Name}");
8692

8793
var relationship = _jsonApiContext.ContextGraph
8894
.GetRelationship<T>(entity, relationshipName);
8995

90-
if(relationship == null)
96+
if (relationship == null)
9197
return NotFound();
9298

9399
return Ok(relationship);
94100
}
95101

96102
[HttpPost]
97-
public virtual IActionResult Post([FromBody] T entity)
103+
public virtual IActionResult Post([FromBody] T entity)
98104
{
99-
if(entity == null)
105+
ApplyContext();
106+
107+
if (entity == null)
100108
return BadRequest();
101-
109+
102110
_dbSet.Add(entity);
103111
_context.SaveChanges();
104112

105113
return Created(HttpContext.Request.Path, entity);
106114
}
107115

108116
[HttpPatch("{id}")]
109-
public virtual IActionResult Patch(int id, [FromBody] T entity)
117+
public virtual IActionResult Patch(int id, [FromBody] T entity)
110118
{
111-
if(entity == null)
119+
ApplyContext();
120+
121+
if (entity == null)
112122
return BadRequest();
113-
123+
114124
var oldEntity = _dbSet.FirstOrDefault(e => e.Id.Equals(id));
115-
if(oldEntity == null)
125+
if (oldEntity == null)
116126
return NotFound();
117127

118128
var requestEntity = _jsonApiContext.RequestEntity;
119129

120-
requestEntity.Attributes.ForEach(attr => {
130+
requestEntity.Attributes.ForEach(attr =>
131+
{
121132
attr.SetValue(oldEntity, attr.GetValue(entity));
122133
});
123134

@@ -133,15 +144,17 @@ public virtual IActionResult Patch(int id, [FromBody] T entity)
133144
// }
134145

135146
[HttpDelete("{id}")]
136-
public virtual IActionResult Delete(TId id)
147+
public virtual IActionResult Delete(TId id)
137148
{
149+
ApplyContext();
150+
138151
var entity = _dbSet.FirstOrDefault(e => e.Id.Equals(id));
139-
if(entity == null)
152+
if (entity == null)
140153
return NotFound();
141-
154+
142155
_dbSet.Remove(entity);
143156
_context.SaveChanges();
144-
157+
145158
return Ok();
146159
}
147160

@@ -150,5 +163,11 @@ public virtual IActionResult Delete(TId id)
150163
// {
151164
// return Ok("Delete Id/relationship");
152165
// }
166+
167+
private void ApplyContext()
168+
{
169+
_jsonApiContext.RequestEntity = _jsonApiContext.ContextGraph.GetContextEntity(typeof(T));
170+
_jsonApiContext.ApplyContext(HttpContext);
171+
}
153172
}
154173
}

src/JsonApiDotNetCore/Extensions/ServiceProviderExtensions.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ public static class ServiceProviderExtensions
1212
public static void AddJsonApi<T>(this IServiceCollection services) where T : DbContext
1313
{
1414
services.AddJsonApiInternals<T>();
15-
1615
services.AddMvc()
1716
.AddMvcOptions(options => options.SerializeAsJsonApi());
1817
}
@@ -22,10 +21,8 @@ public static void AddJsonApiInternals<T>(this IServiceCollection services) wher
2221
var contextGraphBuilder = new ContextGraphBuilder<T>();
2322
var contextGraph = contextGraphBuilder.Build();
2423

25-
var jsonApiContext = new JsonApiContext();
26-
jsonApiContext.ContextGraph = contextGraph;
27-
28-
services.AddSingleton<IJsonApiContext>(jsonApiContext);
24+
services.AddSingleton<IContextGraph>(contextGraph);
25+
services.AddSingleton<IJsonApiContext, JsonApiContext>();
2926
}
3027

3128
public static void SerializeAsJsonApi(this MvcOptions options)

src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Text;
33
using System.Threading.Tasks;
4-
using JsonApiDotNetCore.Internal;
54
using JsonApiDotNetCore.Serialization;
65
using JsonApiDotNetCore.Services;
76
using Microsoft.AspNetCore.Mvc.Formatters;
@@ -27,12 +26,12 @@ public async Task WriteAsync(OutputFormatterWriteContext context)
2726
throw new ArgumentNullException(nameof(context));
2827

2928
var response = context.HttpContext.Response;
30-
29+
3130
using (var writer = context.WriterFactory(response.Body, Encoding.UTF8))
3231
{
33-
var contextGraph = context.HttpContext.RequestServices.GetService<IJsonApiContext>().ContextGraph;
32+
var jsonApiContext = context.HttpContext.RequestServices.GetService<IJsonApiContext>();
3433

35-
var responseContent = JsonApiSerializer.Serialize(context.Object, contextGraph);
34+
var responseContent = JsonApiSerializer.Serialize(context.Object, jsonApiContext);
3635

3736
await writer.WriteAsync(responseContent);
3837

src/JsonApiDotNetCore/Models/DocumentData.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,8 @@ public string Type
2020

2121
[JsonProperty("attributes")]
2222
public Dictionary<string, object> Attributes { get; set; }
23+
24+
[JsonProperty("relationships")]
25+
public Dictionary<string, Dictionary<string, object>> Relationships { get; set; }
2326
}
2427
}

src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
using System.Collections.Generic;
22
using JsonApiDotNetCore.Builders;
3-
using JsonApiDotNetCore.Internal;
43
using JsonApiDotNetCore.Models;
4+
using JsonApiDotNetCore.Services;
55
using Newtonsoft.Json;
66

77
namespace JsonApiDotNetCore.Serialization
88
{
99
public static class JsonApiSerializer
1010
{
11-
public static string Serialize(object entity, IContextGraph contextGraph)
11+
public static string Serialize(object entity, IJsonApiContext jsonApiContext)
1212
{
1313
if (entity is IEnumerable<IIdentifiable>)
14-
return _serializeDocuments(entity, contextGraph);
15-
return _serializeDocument(entity, contextGraph);
14+
return _serializeDocuments(entity, jsonApiContext);
15+
return _serializeDocument(entity, jsonApiContext);
1616
}
1717

18-
private static string _serializeDocuments(object entity, IContextGraph contextGraph)
18+
private static string _serializeDocuments(object entity, IJsonApiContext jsonApiContext)
1919
{
20-
var documentBuilder = new DocumentBuilder(contextGraph);
20+
var documentBuilder = new DocumentBuilder(jsonApiContext);
2121
var entities = entity as IEnumerable<IIdentifiable>;
2222
var documents = documentBuilder.Build(entities);
2323
return JsonConvert.SerializeObject(documents);
2424
}
2525

26-
private static string _serializeDocument(object entity, IContextGraph contextGraph)
26+
private static string _serializeDocument(object entity, IJsonApiContext jsonApiContext)
2727
{
28-
var documentBuilder = new DocumentBuilder(contextGraph);
28+
var documentBuilder = new DocumentBuilder(jsonApiContext);
2929
var identifiableEntity = entity as IIdentifiable;
3030
var document = documentBuilder.Build(identifiableEntity);
3131
return JsonConvert.SerializeObject(document);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
using JsonApiDotNetCore.Internal;
2+
using Microsoft.AspNetCore.Http;
23

34
namespace JsonApiDotNetCore.Services
45
{
56
public interface IJsonApiContext
67
{
8+
void ApplyContext(HttpContext context);
79
IContextGraph ContextGraph { get; set; }
810
ContextEntity RequestEntity { get; set; }
11+
string BasePath { get; set; }
912
}
1013
}

0 commit comments

Comments
 (0)