Skip to content

Commit 418f3d8

Browse files
authored
React to new System.Text.Json features (#9572)
1 parent 19ce372 commit 418f3d8

File tree

10 files changed

+126
-156
lines changed

10 files changed

+126
-156
lines changed

src/Mvc/Mvc.Core/src/MvcOptions.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,16 @@ public int MaxModelBindingRecursionDepth
329329
/// Gets the <see cref="JsonSerializerOptions"/> used by <see cref="SystemTextJsonInputFormatter"/> and
330330
/// <see cref="SystemTextJsonOutputFormatter"/>.
331331
/// </summary>
332-
public JsonSerializerOptions SerializerOptions { get; } = new JsonSerializerOptions();
332+
public JsonSerializerOptions SerializerOptions { get; } = new JsonSerializerOptions
333+
{
334+
// Limit the object graph we'll consume to a fixed depth. This prevents stackoverflow exceptions
335+
// from deserialization errors that might occur from deeply nested objects.
336+
// This value is the same for model binding and Json.Net's serialization.
337+
MaxDepth = DefaultMaxModelBindingRecursionDepth,
338+
339+
// Use camel casing for properties
340+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
341+
};
333342

334343
IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator() => _switches.GetEnumerator();
335344

src/Mvc/Mvc.Core/test/Formatters/JsonInputFormatterTestBase.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ public async Task JsonFormatterReadsStringValue()
106106
public virtual async Task JsonFormatterReadsDateTimeValue()
107107
{
108108
// Arrange
109-
var content = "\"2012-02-01 12:45 AM\"";
109+
var expected = new DateTime(2012, 02, 01, 00, 45, 00);
110+
var content = $"\"{expected.ToString("O")}\"";
110111
var formatter = GetInputFormatter();
111112

112113
var contentBytes = Encoding.UTF8.GetBytes(content);
@@ -120,7 +121,7 @@ public virtual async Task JsonFormatterReadsDateTimeValue()
120121
// Assert
121122
Assert.False(result.HasError);
122123
var dateValue = Assert.IsType<DateTime>(result.Model);
123-
Assert.Equal(new DateTime(2012, 02, 01, 00, 45, 00), dateValue);
124+
Assert.Equal(expected, dateValue);
124125
}
125126

126127
[Fact]
@@ -129,7 +130,7 @@ public async Task JsonFormatterReadsComplexTypes()
129130
// Arrange
130131
var formatter = GetInputFormatter();
131132

132-
var content = "{\"Name\": \"Person Name\", \"Age\": 30}";
133+
var content = "{\"name\": \"Person Name\", \"age\": 30}";
133134
var contentBytes = Encoding.UTF8.GetBytes(content);
134135
var httpContext = GetHttpContext(contentBytes);
135136

@@ -194,8 +195,8 @@ protected async Task ReadAsync_ReadsValidArray_AsList(Type requestedType)
194195

195196
// Assert
196197
Assert.False(result.HasError);
197-
var integers = Assert.IsType<List<int>>(result.Model);
198-
Assert.Equal(new int[] { 0, 23, 300 }, integers);
198+
Assert.IsAssignableFrom(requestedType, result.Model);
199+
Assert.Equal(new int[] { 0, 23, 300 }, (IEnumerable<int>)result.Model);
199200
}
200201

201202
[Fact]

src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonInputFormatterTest.cs

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
88
{
99
public class SystemTextJsonInputFormatterTest : JsonInputFormatterTestBase
1010
{
11-
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8489")]
12-
public override Task JsonFormatterReadsDateTimeValue()
13-
{
14-
return base.JsonFormatterReadsDateTimeValue();
15-
}
16-
1711
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8474")]
1812
public override Task ReadAsync_AddsModelValidationErrorsToModelState()
1913
{
@@ -38,24 +32,6 @@ public override Task ReadAsync_UsesTryAddModelValidationErrorsToModelState()
3832
return base.ReadAsync_UsesTryAddModelValidationErrorsToModelState();
3933
}
4034

41-
[Fact(Skip = "https://github.com/dotnet/corefx/issues/36026")]
42-
public override Task ReadAsync_ReadsValidArray_AsCollectionOfT()
43-
{
44-
return base.ReadAsync_ReadsValidArray_AsCollectionOfT();
45-
}
46-
47-
[Fact(Skip = "https://github.com/dotnet/corefx/issues/36026")]
48-
public override Task ReadAsync_ReadsValidArray_AsEnumerableOfT()
49-
{
50-
return base.ReadAsync_ReadsValidArray_AsEnumerableOfT();
51-
}
52-
53-
[Fact(Skip = "https://github.com/dotnet/corefx/issues/36026")]
54-
public override Task ReadAsync_ReadsValidArray_AsIListOfT()
55-
{
56-
return base.ReadAsync_ReadsValidArray_AsIListOfT();
57-
}
58-
5935
protected override TextInputFormatter GetInputFormatter()
6036
{
6137
return new SystemTextJsonInputFormatter(new MvcOptions());

src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,28 @@ public void EnsureObjectCanBeSerialized_DoesNotThrow_OnValidType(object value)
213213
testProvider.EnsureObjectCanBeSerialized(value);
214214
}
215215

216+
[Fact]
217+
public override void RoundTripTest_GuidToString()
218+
{
219+
// Documents the behavior of round-tripping a Guid value as a string
220+
// Arrange
221+
var key = "test-key";
222+
var testProvider = GetTempDataSerializer();
223+
var value = Guid.NewGuid();
224+
var input = new Dictionary<string, object>
225+
{
226+
{ key, value.ToString() }
227+
};
228+
229+
// Act
230+
var bytes = testProvider.Serialize(input);
231+
var values = testProvider.Deserialize(bytes);
232+
233+
// Assert
234+
var roundTripValue = Assert.IsType<string>(values[key]);
235+
Assert.Equal(value.ToString(), roundTripValue);
236+
}
237+
216238
private class TestItem
217239
{
218240
public int DummyInt { get; set; }

src/Mvc/Mvc.ViewFeatures/src/Infrastructure/DefaultTempDataSerializer.cs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Globalization;
76
using System.Text.Json;
87

98
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure
@@ -46,19 +45,17 @@ private IDictionary<string, object> DeserializeDictionary(JsonElement rootElemen
4645
break;
4746

4847
case JsonValueType.String:
49-
var stringValue = item.Value.GetString();
50-
// BsonTempDataSerializer will parse certain types of string values. We'll attempt to imitiate it.
51-
if (DateTime.TryParseExact(stringValue, "r", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dateTime))
48+
if (item.Value.TryGetGuid(out var guid))
5249
{
53-
deserializedValue = dateTime;
50+
deserializedValue = guid;
5451
}
55-
else if (Guid.TryParseExact(stringValue, "B", out var guid))
52+
else if (item.Value.TryGetDateTime(out var dateTime))
5653
{
57-
deserializedValue = guid;
54+
deserializedValue = dateTime;
5855
}
5956
else
6057
{
61-
deserializedValue = stringValue;
58+
deserializedValue = item.Value.GetString();
6259
}
6360
break;
6461

@@ -123,11 +120,11 @@ public override byte[] Serialize(IDictionary<string, object> values)
123120
break;
124121

125122
case DateTime dateTime:
126-
writer.WriteString(key, dateTime.ToString("r", CultureInfo.InvariantCulture));
123+
writer.WriteString(key, dateTime);
127124
break;
128125

129126
case Guid guid:
130-
writer.WriteString(key, guid.ToString("B", CultureInfo.InvariantCulture));
127+
writer.WriteString(key, guid);
131128
break;
132129
}
133130
}

src/Mvc/Mvc.ViewFeatures/test/Infrastructure/DefaultTempDataSerializerTest.cs

Lines changed: 5 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,66 +12,18 @@ public class DefaultTempDataSerializerTest : TempDataSerializerTestBase
1212
protected override TempDataSerializer GetTempDataSerializer() => new DefaultTempDataSerializer();
1313

1414
[Fact]
15-
public void RoundTripTest_StringThatLooksLikeCompliantDateTime()
15+
public void RoundTripTest_NonStandardDateTimeStringFormat_RoundTripsAsString()
1616
{
17-
// This is an unintentional side-effect of trying to support a compat with JSON.NET.
18-
// Any string that looks like a compliant DateTime object will be parsed as a DateTime.
19-
// This test documents this behavior.
20-
// Arrange
21-
var key = "test-key";
22-
var testProvider = GetTempDataSerializer();
23-
var value = new DateTime(2009, 1, 1, 12, 37, 43);
24-
var input = new Dictionary<string, object>
25-
{
26-
{ key, value.ToString("r") }
27-
};
28-
29-
// Act
30-
var bytes = testProvider.Serialize(input);
31-
var values = testProvider.Deserialize(bytes);
17+
// DateTime that do not match the format that System.Text.Json uses for round-tripping
18+
// should round-trip as strings.
3219

33-
// Assert
34-
var roundTripValue = Assert.IsType<DateTime>(values[key]);
35-
Assert.Equal(value, roundTripValue);
36-
}
37-
38-
[Fact]
39-
public void RoundTripTest_StringThatIsNotCompliantDateTime()
40-
{
41-
// This is an unintentional side-effect of trying to support a compat with JSON.NET.
42-
// Any string that looks like a compliant DateTime object will be parsed as a DateTime.
43-
// This test documents this behavior.
4420
// Arrange
4521
var key = "test-key";
4622
var testProvider = GetTempDataSerializer();
4723
var value = new DateTime(2009, 1, 1, 12, 37, 43);
4824
var input = new Dictionary<string, object>
4925
{
50-
{ key, value.ToString() }
51-
};
52-
53-
// Act
54-
var bytes = testProvider.Serialize(input);
55-
var values = testProvider.Deserialize(bytes);
56-
57-
// Assert
58-
var roundTripValue = Assert.IsType<string>(values[key]);
59-
Assert.Equal(value.ToString(), roundTripValue);
60-
}
61-
62-
[Fact]
63-
public void RoundTripTest_StringThatIsNotCompliantGuid()
64-
{
65-
// This is an unintentional side-effect of trying to support a compat with JSON.NET.
66-
// Any string that looks like a compliant DateTime object will be parsed as a DateTime.
67-
// This test documents this behavior.
68-
// Arrange
69-
var key = "test-key";
70-
var testProvider = GetTempDataSerializer();
71-
var value = Guid.NewGuid();
72-
var input = new Dictionary<string, object>
73-
{
74-
{ key, value.ToString() }
26+
{ key, value.ToString("r") }
7527
};
7628

7729
// Act
@@ -80,7 +32,7 @@ public void RoundTripTest_StringThatIsNotCompliantGuid()
8032

8133
// Assert
8234
var roundTripValue = Assert.IsType<string>(values[key]);
83-
Assert.Equal(value.ToString(), roundTripValue);
35+
Assert.Equal(value.ToString("r"), roundTripValue);
8436
}
8537
}
8638
}

src/Mvc/Mvc.ViewFeatures/test/Infrastructure/TempDataSerializerTestBase.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,72 @@ public void RoundTripTest_GuidValue()
173173
Assert.Equal(value, roundTripValue);
174174
}
175175

176+
[Fact]
177+
public virtual void RoundTripTest_DateTimeToString()
178+
{
179+
// Documents the behavior of round-tripping a DateTime value as a string
180+
// Arrange
181+
var key = "test-key";
182+
var testProvider = GetTempDataSerializer();
183+
var value = new DateTime(2009, 1, 1, 12, 37, 43);
184+
var input = new Dictionary<string, object>
185+
{
186+
{ key, value.ToString() }
187+
};
188+
189+
// Act
190+
var bytes = testProvider.Serialize(input);
191+
var values = testProvider.Deserialize(bytes);
192+
193+
// Assert
194+
var roundTripValue = Assert.IsType<string>(values[key]);
195+
Assert.Equal(value.ToString(), roundTripValue);
196+
}
197+
198+
[Fact]
199+
public virtual void RoundTripTest_StringThatIsNotCompliantGuid()
200+
{
201+
// Documents the behavior of round-tripping a Guid with a non-default format specifier
202+
// Arrange
203+
var key = "test-key";
204+
var testProvider = GetTempDataSerializer();
205+
var value = Guid.NewGuid();
206+
var input = new Dictionary<string, object>
207+
{
208+
{ key, value.ToString("N") }
209+
};
210+
211+
// Act
212+
var bytes = testProvider.Serialize(input);
213+
var values = testProvider.Deserialize(bytes);
214+
215+
// Assert
216+
var roundTripValue = Assert.IsType<string>(values[key]);
217+
Assert.Equal(value.ToString("N"), roundTripValue);
218+
}
219+
220+
[Fact]
221+
public virtual void RoundTripTest_GuidToString()
222+
{
223+
// Documents the behavior of round-tripping a Guid value as a string
224+
// Arrange
225+
var key = "test-key";
226+
var testProvider = GetTempDataSerializer();
227+
var value = Guid.NewGuid();
228+
var input = new Dictionary<string, object>
229+
{
230+
{ key, value.ToString() }
231+
};
232+
233+
// Act
234+
var bytes = testProvider.Serialize(input);
235+
var values = testProvider.Deserialize(bytes);
236+
237+
// Assert
238+
var roundTripValue = Assert.IsType<Guid>(values[key]);
239+
Assert.Equal(value, roundTripValue);
240+
}
241+
176242
protected abstract TempDataSerializer GetTempDataSerializer();
177243
}
178244
}

src/Mvc/test/Mvc.FunctionalTests/HtmlGenerationTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ public async Task CacheTagHelper_BubblesExpirationOfNestedTagHelpers()
507507

508508
// Act - 3
509509
// Trigger an expiration of the nested content.
510-
var content = @"[{ ""ProductName"": ""Music Systems"" },{ ""ProductName"": ""Televisions"" }]";
510+
var content = @"[{ ""productName"": ""Music Systems"" },{ ""productName"": ""Televisions"" }]";
511511
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/categories/Electronics");
512512
requestMessage.Content = new StringContent(content, Encoding.UTF8, "application/json");
513513
(await Client.SendAsync(requestMessage)).EnsureSuccessStatusCode();

src/Mvc/test/Mvc.FunctionalTests/JsonInputFormatterTestBase.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Linq;
56
using System.Net;
67
using System.Net.Http;
78
using System.Text;
9+
using System.Text.Json.Serialization;
810
using System.Threading.Tasks;
911
using Microsoft.AspNetCore.Hosting;
1012
using Xunit;
@@ -31,7 +33,7 @@ public async Task JsonInputFormatter_IsSelectedForJsonRequest(string requestCont
3133
{
3234
// Arrange
3335
var sampleInputInt = 10;
34-
var input = "{\"SampleInt\":10}";
36+
var input = "{\"sampleInt\":10}";
3537
var content = new StringContent(input, Encoding.UTF8, requestContentType);
3638

3739
// Act
@@ -43,7 +45,7 @@ public async Task JsonInputFormatter_IsSelectedForJsonRequest(string requestCont
4345
}
4446

4547
[Theory]
46-
[InlineData("application/json", "{\"SampleInt\":10}", 10)]
48+
[InlineData("application/json", "{\"sampleInt\":10}", 10)]
4749
[InlineData("application/json", "{}", 0)]
4850
public async Task JsonInputFormatter_IsModelStateValid_ForValidContentType(
4951
string requestContentType,
@@ -100,7 +102,7 @@ public async Task JsonInputFormatter_ReadsPrimitiveTypes()
100102
public async Task JsonInputFormatter_Returns415UnsupportedMediaType_ForEmptyContentType()
101103
{
102104
// Arrange
103-
var jsonInput = "{\"SampleInt\":10}";
105+
var jsonInput = "{\"sampleInt\":10}";
104106
var content = new StringContent(jsonInput, Encoding.UTF8, "application/json");
105107
content.Headers.Clear();
106108

@@ -112,7 +114,7 @@ public async Task JsonInputFormatter_Returns415UnsupportedMediaType_ForEmptyCont
112114
}
113115

114116
[Theory]
115-
[InlineData("application/json", "{\"SampleInt\":10}", 10)]
117+
[InlineData("application/json", "{\"sampleInt\":10}", 10)]
116118
[InlineData("application/json", "{}", 0)]
117119
public async Task JsonInputFormatter_IsModelStateValid_ForTransferEncodingChunk(
118120
string requestContentType,
@@ -133,6 +135,5 @@ public async Task JsonInputFormatter_IsModelStateValid_ForTransferEncodingChunk(
133135
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
134136
Assert.Equal(expectedSampleIntValue.ToString(), responseBody);
135137
}
136-
137138
}
138139
}

0 commit comments

Comments
 (0)