Fix losing UTC DateTimeKind on ISO8601 UTC values (#2317)

This commit is contained in:
James Newton-King 2018-05-19 18:43:14 +12:00 committed by GitHub
parent 96bbe70cd8
commit 879646aba3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 76 additions and 3 deletions

View File

@ -153,6 +153,29 @@ namespace Microsoft.AspNetCore.Internal
return true;
}
public static bool ReadForType(JsonTextReader reader, Type type)
{
// Explicity read values as dates from JSON with reader.
// We do this because otherwise dates are read as strings
// and the JsonSerializer will use a conversion method that won't
// preserve UTC in DateTime.Kind for UTC ISO8601 dates
if (type == typeof(DateTime) || type == typeof(DateTime?))
{
reader.ReadAsDateTime();
}
else if (type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?))
{
reader.ReadAsDateTimeOffset();
}
else
{
reader.Read();
}
// TokenType will be None if there is no more content
return reader.TokenType != JsonToken.None;
}
private class JsonArrayPool<T> : IArrayPool<T>
{
private readonly ArrayPool<T> _inner;

View File

@ -168,12 +168,12 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
error = JsonUtils.ReadAsString(reader, ErrorPropertyName);
break;
case ResultPropertyName:
JsonUtils.CheckRead(reader);
hasResult = true;
if (string.IsNullOrEmpty(invocationId))
{
JsonUtils.CheckRead(reader);
// If we don't have an invocation id then we need to store it as a JToken so we can parse it later
resultToken = JToken.Load(reader);
}
@ -181,6 +181,12 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
{
// If we have an invocation id already we can parse the end result
var returnType = binder.GetReturnType(invocationId);
if (!JsonUtils.ReadForType(reader, returnType))
{
throw new JsonReaderException("Unexpected end when reading JSON");
}
result = PayloadSerializer.Deserialize(reader, returnType);
}
break;
@ -599,6 +605,18 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
return new InvocationMessage(invocationId, target, arguments);
}
private bool ReadArgumentAsType(JsonTextReader reader, IReadOnlyList<Type> paramTypes, int paramIndex)
{
if (paramIndex < paramTypes.Count)
{
var paramType = paramTypes[paramIndex];
return JsonUtils.ReadForType(reader, paramType);
}
return reader.Read();
}
private object[] BindArguments(JsonTextReader reader, IReadOnlyList<Type> paramTypes)
{
object[] arguments = null;
@ -606,7 +624,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
var argumentsCount = 0;
var paramCount = paramTypes.Count;
while (reader.Read())
while (ReadArgumentAsType(reader, paramTypes, paramIndex))
{
if (reader.TokenType == JsonToken.EndArray)
{

View File

@ -250,6 +250,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
[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.")]
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':[1,'',{'1':1,'2':2}]}", "Invocation provides 3 argument(s) but target expects 2.")]
[InlineData("{'type':1,'arguments':[1,'',{'1':1,'2':2}]},'invocationId':'42','target':'foo'", "Invocation provides 3 argument(s) but target expects 2.")]
[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.")]
public void ArgumentBindingErrors(string input, string expectedMessage)
{
input = Frame(input);
@ -262,6 +263,37 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
Assert.Equal(expectedMessage, bindingFailure.BindingFailure.SourceException.Message);
}
[Theory]
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':['2007-03-01T13:00:00Z']}")]
[InlineData("{'type':1,'invocationId':'42','arguments':['2007-03-01T13:00:00Z'],'target':'foo'}")]
public void DateTimeArgumentPreservesUtcKind(string input)
{
var binder = new TestBinder(new[] { typeof(DateTime) });
var protocol = new JsonHubProtocol();
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(Frame(input)));
protocol.TryParseMessage(ref data, binder, out var message);
var invocationMessage = Assert.IsType<InvocationMessage>(message);
Assert.Single(invocationMessage.Arguments);
var dt = Assert.IsType<DateTime>(invocationMessage.Arguments[0]);
Assert.Equal(DateTimeKind.Utc, dt.Kind);
}
[Theory]
[InlineData("{'type':3,'invocationId':'42','target':'foo','arguments':[],'result':'2007-03-01T13:00:00Z'}")]
[InlineData("{'type':3,'target':'foo','arguments':[],'result':'2007-03-01T13:00:00Z','invocationId':'42'}")]
public void DateTimeReturnValuePreservesUtcKind(string input)
{
var binder = new TestBinder(typeof(DateTime));
var protocol = new JsonHubProtocol();
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(Frame(input)));
protocol.TryParseMessage(ref data, binder, out var message);
var invocationMessage = Assert.IsType<CompletionMessage>(message);
var dt = Assert.IsType<DateTime>(invocationMessage.Result);
Assert.Equal(DateTimeKind.Utc, dt.Kind);
}
private static string Frame(string input)
{
var data = Encoding.UTF8.GetBytes(input);