Skip to content

Commit a7c6009

Browse files
author
Bart Koelman
committed
Cleanup reader and writer
1 parent 055b93f commit a7c6009

10 files changed

+101
-92
lines changed

benchmarks/Serialization/OperationsSerializationBenchmarks.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ private static IEnumerable<OperationContainer> CreateResponseOperations(IJsonApi
116116
[Benchmark]
117117
public string SerializeOperationsResponse()
118118
{
119-
(Document responseDocument, _) = ResponseModelAdapter.Convert(_responseOperations);
119+
Document responseDocument = ResponseModelAdapter.Convert(_responseOperations);
120120
return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions);
121121
}
122122

benchmarks/Serialization/ResourceSerializationBenchmarks.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ private static ResourceA CreateResponseResource()
107107
[Benchmark]
108108
public string SerializeResourceResponse()
109109
{
110-
(Document responseDocument, _) = ResponseModelAdapter.Convert(ResponseResource);
110+
Document responseDocument = ResponseModelAdapter.Convert(ResponseResource);
111111
return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions);
112112
}
113113

src/JsonApiDotNetCore/Serialization/AtomicOperationsResponseSerializer.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ public sealed class AtomicOperationsResponseSerializer : BaseSerializer, IJsonAp
2727
private readonly IJsonApiRequest _request;
2828
private readonly IJsonApiOptions _options;
2929

30-
/// <inheritdoc />
31-
public string ContentType { get; } = HeaderConstants.AtomicOperationsMediaType;
32-
3330
public AtomicOperationsResponseSerializer(IResourceObjectBuilder resourceObjectBuilder, IMetaBuilder metaBuilder, ILinkBuilder linkBuilder,
3431
IFieldsToSerialize fieldsToSerialize, IResourceDefinitionAccessor resourceDefinitionAccessor, IEvaluatedIncludeCache evaluatedIncludeCache,
3532
ISparseFieldSetCache sparseFieldSetCache, IJsonApiRequest request, IJsonApiOptions options)

src/JsonApiDotNetCore/Serialization/IJsonApiSerializer.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@ namespace JsonApiDotNetCore.Serialization
55
/// </summary>
66
public interface IJsonApiSerializer
77
{
8-
/// <summary>
9-
/// Gets the Content-Type HTTP header value.
10-
/// </summary>
11-
string ContentType { get; }
12-
138
/// <summary>
149
/// Serializes a single resource or a collection of resources.
1510
/// </summary>

src/JsonApiDotNetCore/Serialization/IResponseModelAdapter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@ public interface IResponseModelAdapter
4242
/// </item>
4343
/// </list>
4444
/// </summary>
45-
(Document responseDocument, string contentType) Convert(object model);
45+
Document Convert(object model);
4646
}
4747
}

src/JsonApiDotNetCore/Serialization/JsonApiReader.cs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
2-
using System.IO;
32
using System.Net;
3+
using System.Text;
44
using System.Text.Json;
55
using System.Threading.Tasks;
66
using JetBrains.Annotations;
@@ -12,6 +12,7 @@
1212
using JsonApiDotNetCore.Serialization.RequestAdapters;
1313
using Microsoft.AspNetCore.Http;
1414
using Microsoft.AspNetCore.Http.Extensions;
15+
using Microsoft.AspNetCore.WebUtilities;
1516
using Microsoft.Extensions.Logging;
1617

1718
namespace JsonApiDotNetCore.Serialization
@@ -39,18 +40,19 @@ public async Task<object> ReadAsync(HttpRequest httpRequest)
3940
{
4041
ArgumentGuard.NotNull(httpRequest, nameof(httpRequest));
4142

42-
string requestBody = await GetRequestBodyAsync(httpRequest);
43+
string requestBody = await ReceiveRequestBodyAsync(httpRequest);
44+
45+
_traceWriter.LogMessage(() => $"Received {httpRequest.Method} request at '{httpRequest.GetEncodedUrl()}' with body: <<{requestBody}>>");
46+
4347
return GetModel(requestBody);
4448
}
4549

46-
private async Task<string> GetRequestBodyAsync(HttpRequest httpRequest)
50+
private static async Task<string> ReceiveRequestBodyAsync(HttpRequest httpRequest)
4751
{
48-
using var reader = new StreamReader(httpRequest.Body, leaveOpen: true);
49-
string requestBody = await reader.ReadToEndAsync();
50-
51-
_traceWriter.LogMessage(() => $"Received {httpRequest.Method} request at '{httpRequest.GetEncodedUrl()}' with body: <<{requestBody}>>");
52+
using IDisposable _ = CodeTimingSessionManager.Current.Measure("Receive request body");
5253

53-
return requestBody;
54+
using var reader = new HttpRequestStreamReader(httpRequest.Body, Encoding.UTF8);
55+
return await reader.ReadToEndAsync();
5456
}
5557

5658
private object GetModel(string requestBody)
@@ -59,7 +61,7 @@ private object GetModel(string requestBody)
5961

6062
using IDisposable _ = CodeTimingSessionManager.Current.Measure("Read request body");
6163

62-
Document document = DeserializeDocument(requestBody, _options.SerializerReadOptions);
64+
Document document = DeserializeDocument(requestBody);
6365
return ConvertDocumentToModel(document, requestBody);
6466
}
6567

@@ -72,14 +74,14 @@ private static void AssertHasRequestBody(string requestBody)
7274
}
7375
}
7476

75-
private Document DeserializeDocument(string requestBody, JsonSerializerOptions serializerOptions)
77+
private Document DeserializeDocument(string requestBody)
7678
{
7779
try
7880
{
7981
using IDisposable _ =
8082
CodeTimingSessionManager.Current.Measure("JsonSerializer.Deserialize", MeasurementSettings.ExcludeJsonSerializationInPercentages);
8183

82-
return JsonSerializer.Deserialize<Document>(requestBody, serializerOptions);
84+
return JsonSerializer.Deserialize<Document>(requestBody, _options.SerializerReadOptions);
8385
}
8486
catch (JsonException exception)
8587
{

src/JsonApiDotNetCore/Serialization/JsonApiWriter.cs

Lines changed: 81 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
using System.Net;
55
using System.Net.Http;
66
using System.Text;
7+
using System.Text.Json;
78
using System.Threading.Tasks;
9+
using JsonApiDotNetCore.Configuration;
810
using JsonApiDotNetCore.Diagnostics;
911
using JsonApiDotNetCore.Errors;
1012
using JsonApiDotNetCore.Middleware;
@@ -21,19 +23,26 @@ namespace JsonApiDotNetCore.Serialization
2123
/// <inheritdoc />
2224
public sealed class JsonApiWriter : IJsonApiWriter
2325
{
24-
private readonly IJsonApiSerializer _serializer;
26+
private readonly IJsonApiRequest _request;
27+
private readonly IJsonApiOptions _options;
28+
private readonly IResponseModelAdapter _responseModelAdapter;
2529
private readonly IExceptionHandler _exceptionHandler;
2630
private readonly IETagGenerator _eTagGenerator;
2731
private readonly TraceLogWriter<JsonApiWriter> _traceWriter;
2832

29-
public JsonApiWriter(IJsonApiSerializer serializer, IExceptionHandler exceptionHandler, IETagGenerator eTagGenerator, ILoggerFactory loggerFactory)
33+
public JsonApiWriter(IJsonApiRequest request, IJsonApiOptions options, IResponseModelAdapter responseModelAdapter, IExceptionHandler exceptionHandler,
34+
IETagGenerator eTagGenerator, ILoggerFactory loggerFactory)
3035
{
31-
ArgumentGuard.NotNull(serializer, nameof(serializer));
36+
ArgumentGuard.NotNull(request, nameof(request));
37+
ArgumentGuard.NotNull(responseModelAdapter, nameof(responseModelAdapter));
3238
ArgumentGuard.NotNull(exceptionHandler, nameof(exceptionHandler));
3339
ArgumentGuard.NotNull(eTagGenerator, nameof(eTagGenerator));
40+
ArgumentGuard.NotNull(options, nameof(options));
3441
ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory));
3542

36-
_serializer = serializer;
43+
_request = request;
44+
_options = options;
45+
_responseModelAdapter = responseModelAdapter;
3746
_exceptionHandler = exceptionHandler;
3847
_eTagGenerator = eTagGenerator;
3948
_traceWriter = new TraceLogWriter<JsonApiWriter>(loggerFactory);
@@ -44,83 +53,84 @@ public async Task WriteAsync(object model, HttpContext httpContext)
4453
{
4554
ArgumentGuard.NotNull(httpContext, nameof(httpContext));
4655

47-
HttpRequest request = httpContext.Request;
48-
HttpResponse response = httpContext.Response;
56+
if (model == null && !CanWriteBody((HttpStatusCode)httpContext.Response.StatusCode))
57+
{
58+
// Prevent exception from Kestrel server, caused by writing data:null json response.
59+
return;
60+
}
4961

50-
await using TextWriter writer = new HttpResponseStreamWriter(response.Body, Encoding.UTF8);
51-
string responseContent;
62+
string responseBody = GetResponseBody(model, httpContext);
63+
64+
_traceWriter.LogMessage(() =>
65+
$"Sending {httpContext.Response.StatusCode} response for {httpContext.Request.Method} request at '{httpContext.Request.GetEncodedUrl()}' with body: <<{responseBody}>>");
5266

53-
using (IDisposable _ = CodeTimingSessionManager.Current.Measure("Write response body"))
67+
await SendResponseBodyAsync(httpContext.Response, responseBody);
68+
}
69+
70+
private static bool CanWriteBody(HttpStatusCode statusCode)
71+
{
72+
return statusCode is not HttpStatusCode.NoContent and not HttpStatusCode.ResetContent and not HttpStatusCode.NotModified;
73+
}
74+
75+
private string GetResponseBody(object model, HttpContext httpContext)
76+
{
77+
using IDisposable _ = CodeTimingSessionManager.Current.Measure("Write response body");
78+
79+
try
5480
{
55-
try
81+
if (model is ProblemDetails problemDetails)
5682
{
57-
responseContent = SerializeResponse(model, (HttpStatusCode)response.StatusCode);
83+
throw new UnsuccessfulActionResultException(problemDetails);
5884
}
59-
#pragma warning disable AV1210 // Catch a specific exception instead of Exception, SystemException or ApplicationException
60-
catch (Exception exception)
61-
#pragma warning restore AV1210 // Catch a specific exception instead of Exception, SystemException or ApplicationException
85+
86+
if (model == null && !IsSuccessStatusCode((HttpStatusCode)httpContext.Response.StatusCode))
6287
{
63-
IReadOnlyList<ErrorObject> errors = _exceptionHandler.HandleException(exception);
64-
responseContent = _serializer.Serialize(errors);
65-
response.StatusCode = (int)ErrorObject.GetResponseStatusCode(errors);
88+
throw new UnsuccessfulActionResultException((HttpStatusCode)httpContext.Response.StatusCode);
6689
}
6790

68-
bool hasMatchingETag = SetETagResponseHeader(request, response, responseContent);
91+
string responseBody = RenderModel(model);
6992

70-
if (hasMatchingETag)
93+
if (SetETagResponseHeader(httpContext.Request, httpContext.Response, responseBody))
7194
{
72-
response.StatusCode = (int)HttpStatusCode.NotModified;
73-
responseContent = string.Empty;
95+
httpContext.Response.StatusCode = (int)HttpStatusCode.NotModified;
96+
return null;
7497
}
7598

76-
if (request.Method == HttpMethod.Head.Method)
99+
if (httpContext.Request.Method == HttpMethod.Head.Method)
77100
{
78-
responseContent = string.Empty;
101+
return null;
79102
}
80103

81-
if (!string.IsNullOrEmpty(responseContent))
82-
{
83-
response.ContentType = _serializer.ContentType;
84-
}
104+
return responseBody;
85105
}
86-
87-
_traceWriter.LogMessage(() =>
88-
$"Sending {response.StatusCode} response for {request.Method} request at '{request.GetEncodedUrl()}' with body: <<{responseContent}>>");
89-
90-
using (IDisposable _ = CodeTimingSessionManager.Current.Measure("Send response body"))
106+
#pragma warning disable AV1210 // Catch a specific exception instead of Exception, SystemException or ApplicationException
107+
catch (Exception exception)
108+
#pragma warning restore AV1210 // Catch a specific exception instead of Exception, SystemException or ApplicationException
91109
{
92-
await writer.WriteAsync(responseContent);
93-
await writer.FlushAsync();
110+
IReadOnlyList<ErrorObject> errors = _exceptionHandler.HandleException(exception);
111+
httpContext.Response.StatusCode = (int)ErrorObject.GetResponseStatusCode(errors);
112+
113+
return RenderModel(errors);
94114
}
95115
}
96116

97-
private string SerializeResponse(object contextObject, HttpStatusCode statusCode)
117+
private static bool IsSuccessStatusCode(HttpStatusCode statusCode)
98118
{
99-
if (contextObject is ProblemDetails problemDetails)
100-
{
101-
throw new UnsuccessfulActionResultException(problemDetails);
102-
}
103-
104-
if (contextObject == null)
105-
{
106-
if (!IsSuccessStatusCode(statusCode))
107-
{
108-
throw new UnsuccessfulActionResultException(statusCode);
109-
}
110-
111-
if (statusCode is HttpStatusCode.NoContent or HttpStatusCode.ResetContent or HttpStatusCode.NotModified)
112-
{
113-
// Prevent exception from Kestrel server, caused by writing data:null json response.
114-
return null;
115-
}
116-
}
119+
return new HttpResponseMessage(statusCode).IsSuccessStatusCode;
120+
}
117121

118-
return _serializer.Serialize(contextObject);
122+
private string RenderModel(object model)
123+
{
124+
Document document = _responseModelAdapter.Convert(model);
125+
return SerializeDocument(document);
119126
}
120127

121-
private bool IsSuccessStatusCode(HttpStatusCode statusCode)
128+
private string SerializeDocument(Document document)
122129
{
123-
return new HttpResponseMessage(statusCode).IsSuccessStatusCode;
130+
using IDisposable _ =
131+
CodeTimingSessionManager.Current.Measure("JsonSerializer.Serialize", MeasurementSettings.ExcludeJsonSerializationInPercentages);
132+
133+
return JsonSerializer.Serialize(document, _options.SerializerWriteOptions);
124134
}
125135

126136
private bool SetETagResponseHeader(HttpRequest request, HttpResponse response, string responseContent)
@@ -159,5 +169,20 @@ private static bool RequestContainsMatchingETag(IHeaderDictionary requestHeaders
159169

160170
return false;
161171
}
172+
173+
private async Task SendResponseBodyAsync(HttpResponse httpResponse, string responseBody)
174+
{
175+
if (!string.IsNullOrEmpty(responseBody))
176+
{
177+
httpResponse.ContentType =
178+
_request.Kind == EndpointKind.AtomicOperations ? HeaderConstants.AtomicOperationsMediaType : HeaderConstants.MediaType;
179+
180+
using IDisposable _ = CodeTimingSessionManager.Current.Measure("Send response body");
181+
182+
await using TextWriter writer = new HttpResponseStreamWriter(httpResponse.Body, Encoding.UTF8);
183+
await writer.WriteAsync(responseBody);
184+
await writer.FlushAsync();
185+
}
186+
}
162187
}
163188
}

src/JsonApiDotNetCore/Serialization/ResponseModelAdapter.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public ResponseModelAdapter(IJsonApiRequest request, IJsonApiOptions options, IR
5858
}
5959

6060
/// <inheritdoc />
61-
public (Document responseDocument, string contentType) Convert(object model)
61+
public Document Convert(object model)
6262
{
6363
_sparseFieldSetCache.Reset();
6464

@@ -108,8 +108,7 @@ public ResponseModelAdapter(IJsonApiRequest request, IJsonApiOptions options, IR
108108
document.Meta = _metaBuilder.Build();
109109
document.Included = GetIncluded(includedCollection);
110110

111-
string contentType = _request.Kind == EndpointKind.AtomicOperations ? HeaderConstants.AtomicOperationsMediaType : HeaderConstants.MediaType;
112-
return (document, contentType);
111+
return document;
113112
}
114113

115114
private AtomicResultObject ConvertOperation(OperationContainer operation, IImmutableList<IncludeElementExpression> includeElements,

src/JsonApiDotNetCore/Serialization/ResponseSerializer.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@ public class ResponseSerializer<TResource> : BaseSerializer, IJsonApiSerializer
3737
private readonly IJsonApiRequest _request;
3838
private readonly Type _primaryResourceType;
3939

40-
/// <inheritdoc />
41-
public string ContentType { get; } = HeaderConstants.MediaType;
42-
4340
public ResponseSerializer(IMetaBuilder metaBuilder, ILinkBuilder linkBuilder, IIncludedResourceObjectBuilder includedBuilder,
4441
IFieldsToSerialize fieldsToSerialize, IResourceObjectBuilder resourceObjectBuilder, IResourceDefinitionAccessor resourceDefinitionAccessor,
4542
ISparseFieldSetCache sparseFieldSetCache, IJsonApiOptions options, IJsonApiRequest request)

src/JsonApiDotNetCore/Serialization/TemporarySerializerBridge.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ public sealed class TemporarySerializerBridge : IJsonApiSerializer
1616
private readonly IJsonApiSerializerFactory _factory;
1717
private readonly IJsonApiOptions _options;
1818

19-
public string ContentType { get; private set; }
20-
2119
public TemporarySerializerBridge(IResponseModelAdapter responseModelAdapter, IJsonApiSerializerFactory factory, IJsonApiOptions options)
2220
{
2321
ArgumentGuard.NotNull(responseModelAdapter, nameof(responseModelAdapter));
@@ -34,14 +32,10 @@ public string Serialize(object model)
3432
if (UseLegacySerializer(model))
3533
{
3634
IJsonApiSerializer serializer = _factory.GetSerializer();
37-
string responseBody = serializer.Serialize(model);
38-
ContentType = serializer.ContentType;
39-
return responseBody;
35+
return serializer.Serialize(model);
4036
}
4137

42-
(Document document, string contentType) = _responseModelAdapter.Convert(model);
43-
ContentType = contentType;
44-
38+
Document document = _responseModelAdapter.Convert(model);
4539
return SerializeObject(document, _options.SerializerWriteOptions);
4640
}
4741

0 commit comments

Comments
 (0)