Skip to content

Commit 44bcd96

Browse files
Use request culture to deserialize JSON body (#31331)
* Use request culture to deserialize JSON body Add new ReadJsonWithRequestCulture option to support deserializing JSON using the current HTTP request's culture when using the Newtonsoft.Json input formatter with MVC. Fixes #9994.
1 parent 53ba9b2 commit 44bcd96

File tree

4 files changed

+72
-0
lines changed

4 files changed

+72
-0
lines changed

src/Mvc/Mvc.NewtonsoftJson/src/MvcNewtonsoftJsonOptions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections;
66
using System.Collections.Generic;
7+
using System.Globalization;
78
using Microsoft.AspNetCore.Mvc.Formatters;
89
using Microsoft.AspNetCore.Mvc.Infrastructure;
910
using Microsoft.AspNetCore.Mvc.ModelBinding;
@@ -49,6 +50,15 @@ public class MvcNewtonsoftJsonOptions : IEnumerable<ICompatibilitySwitch>
4950
/// <value>Defaults to 30Kb.</value>
5051
public int InputFormatterMemoryBufferThreshold { get; set; } = 1024 * 30;
5152

53+
/// <summary>
54+
/// Gets or sets a flag to determine whether the value of <see cref="CultureInfo.CurrentCulture"/>
55+
/// for the current HTTP request is used for JSON deserialization by <see cref="NewtonsoftJsonInputFormatter"/>.
56+
/// </summary>
57+
/// <value>
58+
/// The default value is <see langword="false"/>.
59+
/// </value>
60+
public bool ReadJsonWithRequestCulture { get; set; }
61+
5262
/// <summary>
5363
/// Gets the maximum size to buffer in memory when <see cref="MvcOptions.SuppressOutputFormatterBuffering"/> is not set.
5464
/// <para>

src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs

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

44
using System;
55
using System.Buffers;
6+
using System.Globalization;
67
using System.IO;
78
using System.Runtime.ExceptionServices;
89
using System.Text;
@@ -175,6 +176,12 @@ public override async Task<InputFormatterResult> ReadRequestBodyAsync(
175176
var type = context.ModelType;
176177
var jsonSerializer = CreateJsonSerializer(context);
177178
jsonSerializer.Error += ErrorHandler;
179+
180+
if (_jsonOptions.ReadJsonWithRequestCulture)
181+
{
182+
jsonSerializer.Culture = CultureInfo.CurrentCulture;
183+
}
184+
178185
try
179186
{
180187
model = jsonSerializer.Deserialize(jsonReader, type);

src/Mvc/Mvc.NewtonsoftJson/src/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,5 @@ virtual Microsoft.AspNetCore.Mvc.Formatters.NewtonsoftJsonInputFormatter.Release
6666
virtual Microsoft.AspNetCore.Mvc.Formatters.NewtonsoftJsonOutputFormatter.CreateJsonSerializer() -> Newtonsoft.Json.JsonSerializer!
6767
virtual Microsoft.AspNetCore.Mvc.Formatters.NewtonsoftJsonOutputFormatter.CreateJsonSerializer(Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext! context) -> Newtonsoft.Json.JsonSerializer!
6868
virtual Microsoft.AspNetCore.Mvc.Formatters.NewtonsoftJsonOutputFormatter.CreateJsonWriter(System.IO.TextWriter! writer) -> Newtonsoft.Json.JsonWriter!
69+
Microsoft.AspNetCore.Mvc.MvcNewtonsoftJsonOptions.ReadJsonWithRequestCulture.get -> bool
70+
Microsoft.AspNetCore.Mvc.MvcNewtonsoftJsonOptions.ReadJsonWithRequestCulture.set -> void

src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonInputFormatterTest.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,59 @@ public async Task ReadAsync_RegistersFileStreamForDisposal()
434434
httpContext.Verify();
435435
}
436436

437+
[Theory]
438+
[InlineData("2019-05-15", "")]
439+
[InlineData("2019-05-15", "en-CA")]
440+
[InlineData("15/05/2019", "en-GB")]
441+
[InlineData("5/15/2019", "en-US")]
442+
[InlineData("15/05/2019", "es-ES")]
443+
[InlineData("15/5/2019", "es-MX")]
444+
[InlineData("2019-05-15", "fr-CA")]
445+
[InlineData("15/05/2019", "fr-FR")]
446+
[InlineData("15.05.2019", "ru-RU")]
447+
[InlineData("2019/5/15", "zh-CN")]
448+
public async Task ReadAsync_WithReadJsonWithRequestCulture_DeserializesUsingRequestCulture(
449+
string dateString,
450+
string culture)
451+
{
452+
// Arrange
453+
var formatter = new NewtonsoftJsonInputFormatter(
454+
GetLogger(),
455+
_serializerSettings,
456+
ArrayPool<char>.Shared,
457+
_objectPoolProvider,
458+
new MvcOptions(),
459+
new MvcNewtonsoftJsonOptions() { ReadJsonWithRequestCulture = true });
460+
461+
var originalCulture = CultureInfo.CurrentCulture;
462+
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(culture);
463+
464+
try
465+
{
466+
var content = $"{{'DateValue': '{dateString}'}}";
467+
var contentBytes = Encoding.UTF8.GetBytes(content);
468+
var httpContext = new DefaultHttpContext();
469+
httpContext.Features.Set<IHttpResponseFeature>(new TestResponseFeature());
470+
httpContext.Request.Body = new NonSeekableReadStream(contentBytes, allowSyncReads: false);
471+
httpContext.Request.ContentType = "application/json";
472+
473+
var formatterContext = CreateInputFormatterContext(typeof(TypeWithPrimitives), httpContext);
474+
475+
// Act
476+
var result = await formatter.ReadAsync(formatterContext);
477+
478+
// Assert
479+
Assert.False(result.HasError);
480+
481+
var userModel = Assert.IsType<TypeWithPrimitives>(result.Model);
482+
Assert.Equal(new DateTime(2019, 05, 15, 00, 00, 00, DateTimeKind.Unspecified), userModel.DateValue);
483+
}
484+
finally
485+
{
486+
CultureInfo.CurrentCulture = originalCulture;
487+
}
488+
}
489+
437490
private class TestableJsonInputFormatter : NewtonsoftJsonInputFormatter
438491
{
439492
public TestableJsonInputFormatter(JsonSerializerSettings settings, ObjectPoolProvider objectPoolProvider)

0 commit comments

Comments
 (0)