@@ -8,10 +8,26 @@ namespace JsonApiDotNetCore.Resources.Internal;
8
8
[ PublicAPI ]
9
9
public static class RuntimeTypeConverter
10
10
{
11
+ private const string ParseQueryStringsUsingCurrentCultureSwitchName = "JsonApiDotNetCore.ParseQueryStringsUsingCurrentCulture" ;
12
+
11
13
public static object ? ConvertType ( object ? value , Type type )
12
14
{
13
15
ArgumentGuard . NotNull ( type ) ;
14
16
17
+ // Earlier versions of JsonApiDotNetCore failed to pass CultureInfo.InvariantCulture in the parsing below, which resulted in the 'current'
18
+ // culture being used. Unlike parsing JSON request/response bodies, this effectively meant that query strings were parsed based on the
19
+ // OS-level regional settings of the web server.
20
+ // Because this was fixed in a non-major release, the switch below enables to revert to the old behavior.
21
+
22
+ // With the switch activated, API developers can still choose between:
23
+ // - Requiring localized date/number formats: parsing occurs using the OS-level regional settings (the default).
24
+ // - Requiring culture-invariant date/number formats: requires setting CultureInfo.DefaultThreadCurrentCulture to CultureInfo.InvariantCulture at startup.
25
+ // - Allowing clients to choose by sending an Accept-Language HTTP header: requires app.UseRequestLocalization() at startup.
26
+
27
+ CultureInfo ? cultureInfo = AppContext . TryGetSwitch ( ParseQueryStringsUsingCurrentCultureSwitchName , out bool useCurrentCulture ) && useCurrentCulture
28
+ ? null
29
+ : CultureInfo . InvariantCulture ;
30
+
15
31
if ( value == null )
16
32
{
17
33
if ( ! CanContainNull ( type ) )
@@ -50,19 +66,19 @@ public static class RuntimeTypeConverter
50
66
51
67
if ( nonNullableType == typeof ( DateTime ) )
52
68
{
53
- DateTime convertedValue = DateTime . Parse ( stringValue , null , DateTimeStyles . RoundtripKind ) ;
69
+ DateTime convertedValue = DateTime . Parse ( stringValue , cultureInfo , DateTimeStyles . RoundtripKind ) ;
54
70
return isNullableTypeRequested ? ( DateTime ? ) convertedValue : convertedValue ;
55
71
}
56
72
57
73
if ( nonNullableType == typeof ( DateTimeOffset ) )
58
74
{
59
- DateTimeOffset convertedValue = DateTimeOffset . Parse ( stringValue , null , DateTimeStyles . RoundtripKind ) ;
75
+ DateTimeOffset convertedValue = DateTimeOffset . Parse ( stringValue , cultureInfo , DateTimeStyles . RoundtripKind ) ;
60
76
return isNullableTypeRequested ? ( DateTimeOffset ? ) convertedValue : convertedValue ;
61
77
}
62
78
63
79
if ( nonNullableType == typeof ( TimeSpan ) )
64
80
{
65
- TimeSpan convertedValue = TimeSpan . Parse ( stringValue ) ;
81
+ TimeSpan convertedValue = TimeSpan . Parse ( stringValue , cultureInfo ) ;
66
82
return isNullableTypeRequested ? ( TimeSpan ? ) convertedValue : convertedValue ;
67
83
}
68
84
@@ -75,7 +91,7 @@ public static class RuntimeTypeConverter
75
91
}
76
92
77
93
// https://bradwilson.typepad.com/blog/2008/07/creating-nullab.html
78
- return Convert . ChangeType ( stringValue , nonNullableType ) ;
94
+ return Convert . ChangeType ( stringValue , nonNullableType , cultureInfo ) ;
79
95
}
80
96
catch ( Exception exception ) when ( exception is FormatException or OverflowException or InvalidCastException or ArgumentException )
81
97
{
0 commit comments