Skip to content

Commit 34431fa

Browse files
committed
refactor(serialization): introduce new interfaces and composition root at JsonApiOutputFormatter
removes logic from JsonApiOutputFormatter and into JsonApiWriter. this also reduces the large method parameter requirements throughout the affected classes
1 parent 8462e75 commit 34431fa

File tree

10 files changed

+158
-129
lines changed

10 files changed

+158
-129
lines changed

.vscode/launch.json

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,11 @@
11
{
22
"version": "0.2.0",
33
"configurations": [
4-
{
5-
"name": ".NET Core Launch (web)",
6-
"type": "coreclr",
7-
"request": "launch",
8-
"preLaunchTask": "build",
9-
"program": "${workspaceRoot}/src/JsonApiDotNetCoreExample/bin/Debug/netcoreapp1.0/JsonApiDotNetCoreExample.dll",
10-
"args": [],
11-
"cwd": "${workspaceRoot}/src/JsonApiDotNetCoreExample",
12-
"stopAtEntry": false,
13-
"launchBrowser": {
14-
"enabled": false,
15-
"args": "${auto-detect-url}",
16-
"windows": {
17-
"command": "cmd.exe",
18-
"args": "/C start ${auto-detect-url}"
19-
},
20-
"osx": {
21-
"command": "open"
22-
},
23-
"linux": {
24-
"command": "xdg-open"
25-
}
26-
},
27-
"env": {
28-
"ASPNETCORE_ENVIRONMENT": "Development"
29-
},
30-
"sourceFileMap": {
31-
"/Views": "${workspaceRoot}/Views"
32-
}
33-
},
344
{
355
"name": ".NET Core Attach",
366
"type": "coreclr",
377
"request": "attach",
38-
"processId": "${command.pickProcess}"
8+
"processId": "${command:pickProcess}"
399
}
4010
]
4111
}

src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@
88

99
namespace JsonApiDotNetCore.Builders
1010
{
11-
public class DocumentBuilder
11+
public class DocumentBuilder : IDocumentBuilder
1212
{
1313
private IJsonApiContext _jsonApiContext;
1414
private IContextGraph _contextGraph;
1515
private readonly IRequestMeta _requestMeta;
1616

17+
public DocumentBuilder(IJsonApiContext jsonApiContext)
18+
{
19+
_jsonApiContext = jsonApiContext;
20+
_contextGraph = jsonApiContext.ContextGraph;
21+
}
22+
1723
public DocumentBuilder(IJsonApiContext jsonApiContext, IRequestMeta requestMeta)
1824
{
1925
_jsonApiContext = jsonApiContext;
@@ -73,9 +79,8 @@ private Dictionary<string, object> _getMeta(IIdentifiable entity)
7379
if(_jsonApiContext.Options.IncludeTotalRecordCount)
7480
builder.Add("total-records", _jsonApiContext.PageManager.TotalRecords);
7581

76-
var requestMeta = _requestMeta?.GetMeta();
77-
if(requestMeta != null)
78-
builder.Add(requestMeta);
82+
if(_requestMeta != null)
83+
builder.Add(_requestMeta.GetMeta());
7984

8085
var meta = builder.Build();
8186
if(meta.Count > 0) return meta;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Collections.Generic;
2+
using JsonApiDotNetCore.Models;
3+
4+
namespace JsonApiDotNetCore.Builders
5+
{
6+
public interface IDocumentBuilder
7+
{
8+
Document Build(IIdentifiable entity);
9+
Documents Build(IEnumerable<IIdentifiable> entities);
10+
}
11+
}

src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using JsonApiDotNetCore.Data;
55
using JsonApiDotNetCore.Formatters;
66
using JsonApiDotNetCore.Internal;
7+
using JsonApiDotNetCore.Serialization;
78
using JsonApiDotNetCore.Services;
89
using Microsoft.AspNetCore.Http;
910
using Microsoft.AspNetCore.Mvc;
@@ -57,6 +58,9 @@ public static void AddJsonApiInternals<TContext>(this IServiceCollection service
5758
services.AddScoped<JsonApiRouteHandler>();
5859

5960
services.AddScoped<IMetaBuilder, MetaBuilder>();
61+
services.AddScoped<IDocumentBuilder, DocumentBuilder>();
62+
services.AddScoped<IJsonApiSerializer, JsonApiSerializer>();
63+
services.AddScoped<IJsonApiWriter, JsonApiWriter>();
6064
}
6165

6266
public static void SerializeAsJsonApi(this MvcOptions options, JsonApiOptions jsonApiOptions)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Threading.Tasks;
2+
using Microsoft.AspNetCore.Mvc.Formatters;
3+
4+
namespace JsonApiDotNetCore.Formatters
5+
{
6+
public interface IJsonApiWriter
7+
{
8+
Task WriteAsync(OutputFormatterWriteContext context);
9+
}
10+
}
Lines changed: 2 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
using System;
2-
using System.Text;
32
using System.Threading.Tasks;
4-
using JsonApiDotNetCore.Internal;
5-
using JsonApiDotNetCore.Models;
6-
using JsonApiDotNetCore.Serialization;
7-
using JsonApiDotNetCore.Services;
83
using Microsoft.AspNetCore.Mvc.Formatters;
94
using Microsoft.Extensions.DependencyInjection;
10-
using Microsoft.Extensions.Logging;
11-
using Newtonsoft.Json;
125

136
namespace JsonApiDotNetCore.Formatters
147
{
@@ -26,81 +19,8 @@ public bool CanWriteResult(OutputFormatterCanWriteContext context)
2619

2720
public async Task WriteAsync(OutputFormatterWriteContext context)
2821
{
29-
if (context == null)
30-
throw new ArgumentNullException(nameof(context));
31-
32-
var logger = GetService<ILoggerFactory>(context)?
33-
.CreateLogger<JsonApiOutputFormatter>();
34-
35-
var requestMeta = GetService<IRequestMeta>(context);
36-
37-
logger?.LogInformation("Formatting response as JSONAPI");
38-
39-
var response = context.HttpContext.Response;
40-
using (var writer = context.WriterFactory(response.Body, Encoding.UTF8))
41-
{
42-
var jsonApiContext = GetService<IJsonApiContext>(context);
43-
44-
response.ContentType = "application/vnd.api+json";
45-
string responseContent;
46-
try
47-
{
48-
responseContent = GetResponseBody(context.Object, jsonApiContext, requestMeta, logger);
49-
}
50-
catch (Exception e)
51-
{
52-
logger?.LogError(new EventId(), e, "An error ocurred while formatting the response");
53-
var errors = new ErrorCollection();
54-
errors.Add(new Error("400", e.Message));
55-
responseContent = errors.GetJson();
56-
response.StatusCode = 400;
57-
}
58-
59-
await writer.WriteAsync(responseContent);
60-
await writer.FlushAsync();
61-
}
62-
}
63-
64-
private T GetService<T>(OutputFormatterWriteContext context)
65-
{
66-
return context.HttpContext.RequestServices.GetService<T>();
67-
}
68-
69-
private string GetResponseBody(object responseObject,
70-
IJsonApiContext jsonApiContext,
71-
IRequestMeta requestMeta,
72-
ILogger logger)
73-
{
74-
if (responseObject == null)
75-
return GetNullDataResponse();
76-
77-
if (responseObject.GetType() == typeof(Error) || jsonApiContext.RequestEntity == null)
78-
return GetErrorJson(responseObject, logger);
79-
80-
return JsonApiSerializer.Serialize(responseObject, jsonApiContext, requestMeta);
81-
}
82-
83-
private string GetNullDataResponse()
84-
{
85-
return JsonConvert.SerializeObject(new Document
86-
{
87-
Data = null
88-
});
89-
}
90-
91-
private string GetErrorJson(object responseObject, ILogger logger)
92-
{
93-
if (responseObject.GetType() == typeof(Error))
94-
{
95-
var errors = new ErrorCollection();
96-
errors.Add((Error)responseObject);
97-
return errors.GetJson();
98-
}
99-
else
100-
{
101-
logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON.");
102-
return JsonConvert.SerializeObject(responseObject);
103-
}
22+
var writer = context.HttpContext.RequestServices.GetService<IJsonApiWriter>();
23+
await writer.WriteAsync(context);
10424
}
10525
}
10626
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
using System;
2+
using System.Text;
3+
using System.Threading.Tasks;
4+
using JsonApiDotNetCore.Internal;
5+
using JsonApiDotNetCore.Models;
6+
using JsonApiDotNetCore.Serialization;
7+
using JsonApiDotNetCore.Services;
8+
using Microsoft.AspNetCore.Mvc.Formatters;
9+
using Microsoft.Extensions.Logging;
10+
using Newtonsoft.Json;
11+
12+
namespace JsonApiDotNetCore.Formatters
13+
{
14+
public class JsonApiWriter : IJsonApiWriter
15+
{
16+
private readonly ILogger<JsonApiWriter> _logger;
17+
private readonly IJsonApiContext _jsonApiContext;
18+
private readonly IJsonApiSerializer _serializer;
19+
20+
public JsonApiWriter(IJsonApiContext jsonApiContext,
21+
IJsonApiSerializer serializer,
22+
ILoggerFactory loggerFactory)
23+
{
24+
_jsonApiContext = jsonApiContext;
25+
_serializer = serializer;
26+
_logger = loggerFactory.CreateLogger<JsonApiWriter>();
27+
}
28+
29+
public async Task WriteAsync(OutputFormatterWriteContext context)
30+
{
31+
if (context == null)
32+
throw new ArgumentNullException(nameof(context));
33+
34+
_logger?.LogInformation("Formatting response as JSONAPI");
35+
36+
var response = context.HttpContext.Response;
37+
using (var writer = context.WriterFactory(response.Body, Encoding.UTF8))
38+
{
39+
response.ContentType = "application/vnd.api+json";
40+
string responseContent;
41+
try
42+
{
43+
responseContent = GetResponseBody(context.Object);
44+
}
45+
catch (Exception e)
46+
{
47+
_logger?.LogError(new EventId(), e, "An error ocurred while formatting the response");
48+
var errors = new ErrorCollection();
49+
errors.Add(new Error("400", e.Message));
50+
responseContent = errors.GetJson();
51+
response.StatusCode = 400;
52+
}
53+
54+
await writer.WriteAsync(responseContent);
55+
await writer.FlushAsync();
56+
}
57+
}
58+
59+
private string GetResponseBody(object responseObject)
60+
{
61+
if (responseObject == null)
62+
return GetNullDataResponse();
63+
64+
if (responseObject.GetType() == typeof(Error) || _jsonApiContext.RequestEntity == null)
65+
return GetErrorJson(responseObject, _logger);
66+
67+
return _serializer.Serialize(responseObject);
68+
}
69+
70+
private string GetNullDataResponse()
71+
{
72+
return JsonConvert.SerializeObject(new Document
73+
{
74+
Data = null
75+
});
76+
}
77+
78+
private string GetErrorJson(object responseObject, ILogger logger)
79+
{
80+
if (responseObject.GetType() == typeof(Error))
81+
{
82+
var errors = new ErrorCollection();
83+
errors.Add((Error)responseObject);
84+
return errors.GetJson();
85+
}
86+
else
87+
{
88+
logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON.");
89+
return JsonConvert.SerializeObject(responseObject);
90+
}
91+
}
92+
}
93+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace JsonApiDotNetCore.Serialization
2+
{
3+
public interface IJsonApiSerializer
4+
{
5+
string Serialize(object entity);
6+
}
7+
}

src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,42 @@
11
using System.Collections.Generic;
22
using JsonApiDotNetCore.Builders;
33
using JsonApiDotNetCore.Models;
4-
using JsonApiDotNetCore.Services;
54
using Newtonsoft.Json;
65

76
namespace JsonApiDotNetCore.Serialization
87
{
9-
public static class JsonApiSerializer
8+
public class JsonApiSerializer : IJsonApiSerializer
109
{
11-
public static string Serialize(object entity, IJsonApiContext jsonApiContext, IRequestMeta requestMeta)
10+
private readonly IDocumentBuilder _documentBuilder;
11+
12+
public JsonApiSerializer(IDocumentBuilder documentBuilder)
13+
{
14+
_documentBuilder = documentBuilder;
15+
}
16+
17+
public string Serialize(object entity)
1218
{
1319
if (entity is IEnumerable<IIdentifiable>)
14-
return _serializeDocuments(entity, jsonApiContext, requestMeta);
20+
return _serializeDocuments(entity);
1521

16-
return _serializeDocument(entity, jsonApiContext, requestMeta);
22+
return _serializeDocument(entity);
1723
}
1824

19-
private static string _serializeDocuments(object entity, IJsonApiContext jsonApiContext, IRequestMeta requestMeta)
25+
private string _serializeDocuments(object entity)
2026
{
21-
var documentBuilder = new DocumentBuilder(jsonApiContext, requestMeta);
2227
var entities = entity as IEnumerable<IIdentifiable>;
23-
var documents = documentBuilder.Build(entities);
28+
var documents = _documentBuilder.Build(entities);
2429
return _serialize(documents);
2530
}
2631

27-
private static string _serializeDocument(object entity, IJsonApiContext jsonApiContext, IRequestMeta requestMeta)
32+
private string _serializeDocument(object entity)
2833
{
29-
var documentBuilder = new DocumentBuilder(jsonApiContext, requestMeta);
3034
var identifiableEntity = entity as IIdentifiable;
31-
var document = documentBuilder.Build(identifiableEntity);
35+
var document = _documentBuilder.Build(identifiableEntity);
3236
return _serialize(document);
3337
}
3438

35-
private static string _serialize(object obj)
39+
private string _serialize(object obj)
3640
{
3741
return JsonConvert.SerializeObject(obj, new JsonSerializerSettings {
3842
NullValueHandling = NullValueHandling.Ignore

test/JsonApiDotNetCoreExampleTests/Unit/Extensions/IServiceCollectionExtensionsTests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
using JsonApiDotNetCoreExample.Data;
1212
using Microsoft.Extensions.Caching.Memory;
1313
using JsonApiDotNetCoreExample.Models;
14+
using JsonApiDotNetCore.Serialization;
15+
using JsonApiDotNetCore.Formatters;
1416

1517
namespace JsonApiDotNetCoreExampleTests.Unit.Extensions
1618
{
@@ -40,6 +42,9 @@ public void AddJsonApiInternals_Adds_All_Required_Services()
4042
Assert.NotNull(provider.GetService<IJsonApiContext>());
4143
Assert.NotNull(provider.GetService<IHttpContextAccessor>());
4244
Assert.NotNull(provider.GetService<IMetaBuilder>());
45+
Assert.NotNull(provider.GetService<IDocumentBuilder>());
46+
Assert.NotNull(provider.GetService<IJsonApiSerializer>());
47+
Assert.NotNull(provider.GetService<IJsonApiWriter>());
4348
}
4449
}
4550
}

0 commit comments

Comments
 (0)