Skip to content

Commit 4473e98

Browse files
committed
formatter and executor implementation changes and unit tests
1 parent 03c537f commit 4473e98

6 files changed

Lines changed: 322 additions & 84 deletions

File tree

src/PartialResponse.AspNetCore.Mvc.Formatters.Json/HttpRequestExtensions.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Arjen Post. See License.txt and Notice.txt in the project root for license information.
22

33
using System;
4+
using PartialResponse.Core;
45

56
namespace Microsoft.AspNetCore.Http
67
{
@@ -44,5 +45,28 @@ public static bool GetBypassPartialResponse(this HttpRequest request)
4445

4546
return false;
4647
}
48+
49+
internal static bool TryGetFields(this HttpRequest request, out Fields? result)
50+
{
51+
if (!request.Query.ContainsKey("fields"))
52+
{
53+
result = null;
54+
55+
return true;
56+
}
57+
58+
Fields fields;
59+
60+
if (!Fields.TryParse(request.Query["fields"][0], out fields))
61+
{
62+
result = null;
63+
64+
return false;
65+
}
66+
67+
result = fields;
68+
69+
return true;
70+
}
4771
}
4872
}

src/PartialResponse.AspNetCore.Mvc.Formatters.Json/Internal/PartialJsonResultExecutor.cs

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
using System;
44
using System.Buffers;
5-
using System.Linq;
65
using System.Text;
76
using System.Threading.Tasks;
87
using Microsoft.AspNetCore.Http;
@@ -100,8 +99,18 @@ public Task ExecuteAsync(ActionContext context, PartialJsonResult result)
10099
throw new ArgumentNullException(nameof(result));
101100
}
102101

102+
var request = context.HttpContext.Request;
103103
var response = context.HttpContext.Response;
104104

105+
Fields? fields;
106+
107+
if (!request.TryGetFields(out fields))
108+
{
109+
response.StatusCode = 400;
110+
111+
return TaskCache.CompletedTask;
112+
}
113+
105114
string resolvedContentType = null;
106115
Encoding resolvedContentTypeEncoding = null;
107116
ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
@@ -129,20 +138,10 @@ public Task ExecuteAsync(ActionContext context, PartialJsonResult result)
129138
jsonWriter.CloseOutput = false;
130139

131140
var jsonSerializer = JsonSerializer.Create(serializerSettings);
132-
var request = context.HttpContext.Request;
133141

134-
if (request.Query.ContainsKey("fields"))
142+
if (fields.HasValue)
135143
{
136-
Fields fields;
137-
138-
if (!this.TryGetFields(request, out fields))
139-
{
140-
response.StatusCode = 400;
141-
142-
return TaskCache.CompletedTask;
143-
}
144-
145-
PartialJsonUtilities.RemovePropertiesAndArrayElements(result.Value, jsonWriter, jsonSerializer, value => fields.Matches(value));
144+
jsonSerializer.Serialize(jsonWriter, result.Value, path => fields.Value.Matches(path));
146145
}
147146
else
148147
{
@@ -153,17 +152,5 @@ public Task ExecuteAsync(ActionContext context, PartialJsonResult result)
153152

154153
return TaskCache.CompletedTask;
155154
}
156-
157-
private bool TryGetFields(HttpRequest request, out Fields fields)
158-
{
159-
var queryOption = request.Query["fields"].First();
160-
161-
if (!Fields.TryParse(queryOption, out fields))
162-
{
163-
return false;
164-
}
165-
166-
return true;
167-
}
168155
}
169156
}

src/PartialResponse.AspNetCore.Mvc.Formatters.Json/PartialJsonOutputFormatter.cs

Lines changed: 26 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
using System;
44
using System.Buffers;
55
using System.IO;
6-
using System.Linq;
76
using System.Text;
87
using System.Threading.Tasks;
9-
using Microsoft.AspNetCore.Http;
108
using Microsoft.AspNetCore.Mvc.Formatters;
11-
using Newtonsoft.Json;
129
using PartialResponse.AspNetCore.Mvc.Formatters.Json.Internal;
10+
using Newtonsoft.Json;
11+
using Microsoft.AspNetCore.Http;
1312
using PartialResponse.Core;
1413

1514
namespace PartialResponse.AspNetCore.Mvc.Formatters
@@ -99,31 +98,6 @@ protected virtual JsonSerializer CreateJsonSerializer()
9998
return _serializer;
10099
}
101100

102-
/// <summary>
103-
/// Returns a value that indicates whether partial response should be bypassed.
104-
/// </summary>
105-
/// <param name="request">The request.</param>
106-
/// <returns>True if the partial response should be bypassed, otherwise false.</returns>
107-
protected virtual bool ShouldBypassPartialResponse(HttpRequest request)
108-
{
109-
if (request == null)
110-
{
111-
throw new ArgumentNullException("request");
112-
}
113-
114-
if (request.GetBypassPartialResponse())
115-
{
116-
return true;
117-
}
118-
119-
if (request.HttpContext != null)
120-
{
121-
return request.HttpContext.Response.StatusCode != 200;
122-
}
123-
124-
return false;
125-
}
126-
127101
/// <inheritdoc />
128102
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
129103
{
@@ -137,32 +111,21 @@ public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext co
137111
throw new ArgumentNullException(nameof(selectedEncoding));
138112
}
139113

114+
var request = context.HttpContext.Request;
140115
var response = context.HttpContext.Response;
116+
117+
Fields? fields;
118+
119+
if (!request.TryGetFields(out fields))
120+
{
121+
response.StatusCode = 400;
122+
123+
return;
124+
}
125+
141126
using (var writer = context.WriterFactory(response.Body, selectedEncoding))
142127
{
143-
using (var jsonWriter = CreateJsonWriter(writer))
144-
{
145-
var jsonSerializer = CreateJsonSerializer();
146-
var request = context.HttpContext.Request;
147-
148-
if (!ShouldBypassPartialResponse(request) && request.Query.ContainsKey("fields"))
149-
{
150-
Fields fields;
151-
152-
if (!this.TryGetFields(request, out fields))
153-
{
154-
response.StatusCode = 400;
155-
156-
await writer.FlushAsync();
157-
}
158-
159-
PartialJsonUtilities.RemovePropertiesAndArrayElements(context.Object, jsonWriter, jsonSerializer, value => fields.Matches(value));
160-
}
161-
else
162-
{
163-
jsonSerializer.Serialize(jsonWriter, context.Object);
164-
}
165-
}
128+
WriteObject(writer, context.Object, fields);
166129

167130
// Perf: call FlushAsync to call WriteAsync on the stream with any content left in the TextWriter's
168131
// buffers. This is better than just letting dispose handle it (which would result in a synchronous
@@ -171,16 +134,21 @@ public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext co
171134
}
172135
}
173136

174-
private bool TryGetFields(HttpRequest request, out Fields fields)
137+
private void WriteObject(TextWriter writer, object value, Fields? fields)
175138
{
176-
var queryOption = request.Query["fields"].First();
177-
178-
if (!Fields.TryParse(queryOption, out fields))
139+
using (var jsonWriter = CreateJsonWriter(writer))
179140
{
180-
return false;
181-
}
141+
var jsonSerializer = CreateJsonSerializer();
182142

183-
return true;
143+
if (fields.HasValue)
144+
{
145+
jsonSerializer.Serialize(jsonWriter, value, path => fields.Value.Matches(path));
146+
}
147+
else
148+
{
149+
jsonSerializer.Serialize(jsonWriter, value);
150+
}
151+
}
184152
}
185153
}
186154
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
using System.Buffers;
2+
using System.IO;
3+
using System.Text;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Mvc.Formatters;
7+
using Moq;
8+
using Newtonsoft.Json;
9+
using Xunit;
10+
11+
namespace PartialResponse.AspNetCore.Mvc.Formatters.Json
12+
{
13+
public class PartialJsonOutputFormatterTests
14+
{
15+
private readonly PartialJsonOutputFormatter formatter;
16+
private readonly HttpContext httpContext = Mock.Of<HttpContext>();
17+
private readonly HttpRequest httpRequest = Mock.Of<HttpRequest>();
18+
private readonly HttpResponse httpResponse = Mock.Of<HttpResponse>();
19+
private readonly IQueryCollection queryCollection = Mock.Of<IQueryCollection>();
20+
private readonly StringBuilder body = new StringBuilder();
21+
22+
public PartialJsonOutputFormatterTests()
23+
{
24+
Mock.Get(this.httpRequest)
25+
.SetupGet(httpRequest => httpRequest.Query)
26+
.Returns(this.queryCollection);
27+
28+
Mock.Get(this.httpContext)
29+
.SetupGet(httpContext => httpContext.Request)
30+
.Returns(this.httpRequest);
31+
32+
Mock.Get(this.httpContext)
33+
.SetupGet(httpContext => httpContext.Response)
34+
.Returns(this.httpResponse);
35+
36+
this.formatter = new PartialJsonOutputFormatter(new JsonSerializerSettings(), Mock.Of<ArrayPool<char>>());
37+
}
38+
39+
[Fact]
40+
public async Task TheWriteResponseBodyAsyncMethodShouldReturnStatusCode400IfFieldsMalformed()
41+
{
42+
// Arrange
43+
Mock.Get(this.queryCollection)
44+
.Setup(queryCollection => queryCollection.ContainsKey("fields"))
45+
.Returns(true);
46+
47+
Mock.Get(this.queryCollection)
48+
.SetupGet(queryCollection => queryCollection["fields"])
49+
.Returns("foo/");
50+
51+
var writeContext = new OutputFormatterWriteContext(this.httpContext, (stream, encoding) => new StringWriter(this.body), typeof(object), new {});
52+
53+
// Act
54+
await this.formatter.WriteResponseBodyAsync(writeContext, Encoding.UTF8);
55+
56+
// Assert
57+
Mock.Get(this.httpResponse)
58+
.VerifySet(httpResponse => httpResponse.StatusCode = 400);
59+
}
60+
61+
[Fact]
62+
public async Task TheWriteResponseBodyAsyncMethodShouldNotWriteBodyIfFieldsMalformed()
63+
{
64+
// Arrange
65+
Mock.Get(this.queryCollection)
66+
.Setup(queryCollection => queryCollection.ContainsKey("fields"))
67+
.Returns(true);
68+
69+
Mock.Get(this.queryCollection)
70+
.SetupGet(queryCollection => queryCollection["fields"])
71+
.Returns("foo/");
72+
73+
var writeContext = new OutputFormatterWriteContext(this.httpContext, (stream, encoding) => new StringWriter(this.body), typeof(object), new {});
74+
75+
// Act
76+
await this.formatter.WriteResponseBodyAsync(writeContext, Encoding.UTF8);
77+
78+
// Assert
79+
Assert.Equal(0, this.body.Length);
80+
}
81+
82+
[Fact]
83+
public async Task TheWriteResponseBodyAsyncMethodShouldNotApplyFieldsIfNotSupplied()
84+
{
85+
// Arrange
86+
Mock.Get(this.queryCollection)
87+
.Setup(queryCollection => queryCollection.ContainsKey("fields"))
88+
.Returns(false);
89+
90+
var value = new { foo = "bar" };
91+
92+
var writeContext = new OutputFormatterWriteContext(this.httpContext, (stream, encoding) => new StringWriter(this.body), typeof(object), value);
93+
94+
// Act
95+
await this.formatter.WriteResponseBodyAsync(writeContext, Encoding.UTF8);
96+
97+
// Assert
98+
Assert.Equal("{\"foo\":\"bar\"}", this.body.ToString());
99+
}
100+
101+
[Fact]
102+
public async Task TheWriteResponseBodyAsyncMethodShouldApplyFieldsIfSupplied()
103+
{
104+
// Arrange
105+
Mock.Get(this.queryCollection)
106+
.Setup(queryCollection => queryCollection.ContainsKey("fields"))
107+
.Returns(true);
108+
109+
Mock.Get(this.queryCollection)
110+
.SetupGet(queryCollection => queryCollection["fields"])
111+
.Returns("foo");
112+
113+
var value = new { foo = "bar", baz = "qux" };
114+
115+
var writeContext = new OutputFormatterWriteContext(this.httpContext, (stream, encoding) => new StringWriter(this.body), typeof(object), value);
116+
117+
// Act
118+
await this.formatter.WriteResponseBodyAsync(writeContext, Encoding.UTF8);
119+
120+
// Assert
121+
Assert.Equal("{\"foo\":\"bar\"}", this.body.ToString());
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)