Skip to content

Commit 879646a

Browse files
authored
Fix losing UTC DateTimeKind on ISO8601 UTC values (#2317)
1 parent 96bbe70 commit 879646a

File tree

3 files changed

+76
-3
lines changed

3 files changed

+76
-3
lines changed

src/Common/JsonUtils.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,29 @@ public static bool CheckRead(JsonTextReader reader)
153153
return true;
154154
}
155155

156+
public static bool ReadForType(JsonTextReader reader, Type type)
157+
{
158+
// Explicity read values as dates from JSON with reader.
159+
// We do this because otherwise dates are read as strings
160+
// and the JsonSerializer will use a conversion method that won't
161+
// preserve UTC in DateTime.Kind for UTC ISO8601 dates
162+
if (type == typeof(DateTime) || type == typeof(DateTime?))
163+
{
164+
reader.ReadAsDateTime();
165+
}
166+
else if (type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?))
167+
{
168+
reader.ReadAsDateTimeOffset();
169+
}
170+
else
171+
{
172+
reader.Read();
173+
}
174+
175+
// TokenType will be None if there is no more content
176+
return reader.TokenType != JsonToken.None;
177+
}
178+
156179
private class JsonArrayPool<T> : IArrayPool<T>
157180
{
158181
private readonly ArrayPool<T> _inner;

src/Microsoft.AspNetCore.SignalR.Protocols.Json/Protocol/JsonHubProtocol.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,19 +168,25 @@ private HubMessage ParseMessage(Utf8BufferTextReader textReader, IInvocationBind
168168
error = JsonUtils.ReadAsString(reader, ErrorPropertyName);
169169
break;
170170
case ResultPropertyName:
171-
JsonUtils.CheckRead(reader);
172-
173171
hasResult = true;
174172

175173
if (string.IsNullOrEmpty(invocationId))
176174
{
175+
JsonUtils.CheckRead(reader);
176+
177177
// If we don't have an invocation id then we need to store it as a JToken so we can parse it later
178178
resultToken = JToken.Load(reader);
179179
}
180180
else
181181
{
182182
// If we have an invocation id already we can parse the end result
183183
var returnType = binder.GetReturnType(invocationId);
184+
185+
if (!JsonUtils.ReadForType(reader, returnType))
186+
{
187+
throw new JsonReaderException("Unexpected end when reading JSON");
188+
}
189+
184190
result = PayloadSerializer.Deserialize(reader, returnType);
185191
}
186192
break;
@@ -599,14 +605,26 @@ private HubMessage BindInvocationMessage(string invocationId, string target, obj
599605
return new InvocationMessage(invocationId, target, arguments);
600606
}
601607

608+
private bool ReadArgumentAsType(JsonTextReader reader, IReadOnlyList<Type> paramTypes, int paramIndex)
609+
{
610+
if (paramIndex < paramTypes.Count)
611+
{
612+
var paramType = paramTypes[paramIndex];
613+
614+
return JsonUtils.ReadForType(reader, paramType);
615+
}
616+
617+
return reader.Read();
618+
}
619+
602620
private object[] BindArguments(JsonTextReader reader, IReadOnlyList<Type> paramTypes)
603621
{
604622
object[] arguments = null;
605623
var paramIndex = 0;
606624
var argumentsCount = 0;
607625
var paramCount = paramTypes.Count;
608626

609-
while (reader.Read())
627+
while (ReadArgumentAsType(reader, paramTypes, paramIndex))
610628
{
611629
if (reader.TokenType == JsonToken.EndArray)
612630
{

test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ public void ExtraItemsInMessageAreIgnored(string input)
250250
[InlineData("{'type':4,'invocationId':'42','target':'foo','arguments':[ 'abc', 'xyz']}", "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.")]
251251
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':[1,'',{'1':1,'2':2}]}", "Invocation provides 3 argument(s) but target expects 2.")]
252252
[InlineData("{'type':1,'arguments':[1,'',{'1':1,'2':2}]},'invocationId':'42','target':'foo'", "Invocation provides 3 argument(s) but target expects 2.")]
253+
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':[1,[]]}", "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.")]
253254
public void ArgumentBindingErrors(string input, string expectedMessage)
254255
{
255256
input = Frame(input);
@@ -262,6 +263,37 @@ public void ArgumentBindingErrors(string input, string expectedMessage)
262263
Assert.Equal(expectedMessage, bindingFailure.BindingFailure.SourceException.Message);
263264
}
264265

266+
[Theory]
267+
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':['2007-03-01T13:00:00Z']}")]
268+
[InlineData("{'type':1,'invocationId':'42','arguments':['2007-03-01T13:00:00Z'],'target':'foo'}")]
269+
public void DateTimeArgumentPreservesUtcKind(string input)
270+
{
271+
var binder = new TestBinder(new[] { typeof(DateTime) });
272+
var protocol = new JsonHubProtocol();
273+
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(Frame(input)));
274+
protocol.TryParseMessage(ref data, binder, out var message);
275+
var invocationMessage = Assert.IsType<InvocationMessage>(message);
276+
277+
Assert.Single(invocationMessage.Arguments);
278+
var dt = Assert.IsType<DateTime>(invocationMessage.Arguments[0]);
279+
Assert.Equal(DateTimeKind.Utc, dt.Kind);
280+
}
281+
282+
[Theory]
283+
[InlineData("{'type':3,'invocationId':'42','target':'foo','arguments':[],'result':'2007-03-01T13:00:00Z'}")]
284+
[InlineData("{'type':3,'target':'foo','arguments':[],'result':'2007-03-01T13:00:00Z','invocationId':'42'}")]
285+
public void DateTimeReturnValuePreservesUtcKind(string input)
286+
{
287+
var binder = new TestBinder(typeof(DateTime));
288+
var protocol = new JsonHubProtocol();
289+
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(Frame(input)));
290+
protocol.TryParseMessage(ref data, binder, out var message);
291+
var invocationMessage = Assert.IsType<CompletionMessage>(message);
292+
293+
var dt = Assert.IsType<DateTime>(invocationMessage.Result);
294+
Assert.Equal(DateTimeKind.Utc, dt.Kind);
295+
}
296+
265297
private static string Frame(string input)
266298
{
267299
var data = Encoding.UTF8.GetBytes(input);

0 commit comments

Comments
 (0)