Fix losing UTC DateTimeKind on ISO8601 UTC values (#2317)
This commit is contained in:
parent
96bbe70cd8
commit
879646aba3
|
|
@ -153,6 +153,29 @@ namespace Microsoft.AspNetCore.Internal
|
||||||
return true;
|
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 class JsonArrayPool<T> : IArrayPool<T>
|
||||||
{
|
{
|
||||||
private readonly ArrayPool<T> _inner;
|
private readonly ArrayPool<T> _inner;
|
||||||
|
|
|
||||||
|
|
@ -168,12 +168,12 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
|
||||||
error = JsonUtils.ReadAsString(reader, ErrorPropertyName);
|
error = JsonUtils.ReadAsString(reader, ErrorPropertyName);
|
||||||
break;
|
break;
|
||||||
case ResultPropertyName:
|
case ResultPropertyName:
|
||||||
JsonUtils.CheckRead(reader);
|
|
||||||
|
|
||||||
hasResult = true;
|
hasResult = true;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(invocationId))
|
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
|
// 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);
|
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
|
// If we have an invocation id already we can parse the end result
|
||||||
var returnType = binder.GetReturnType(invocationId);
|
var returnType = binder.GetReturnType(invocationId);
|
||||||
|
|
||||||
|
if (!JsonUtils.ReadForType(reader, returnType))
|
||||||
|
{
|
||||||
|
throw new JsonReaderException("Unexpected end when reading JSON");
|
||||||
|
}
|
||||||
|
|
||||||
result = PayloadSerializer.Deserialize(reader, returnType);
|
result = PayloadSerializer.Deserialize(reader, returnType);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -599,6 +605,18 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
|
||||||
return new InvocationMessage(invocationId, target, arguments);
|
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)
|
private object[] BindArguments(JsonTextReader reader, IReadOnlyList<Type> paramTypes)
|
||||||
{
|
{
|
||||||
object[] arguments = null;
|
object[] arguments = null;
|
||||||
|
|
@ -606,7 +624,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
|
||||||
var argumentsCount = 0;
|
var argumentsCount = 0;
|
||||||
var paramCount = paramTypes.Count;
|
var paramCount = paramTypes.Count;
|
||||||
|
|
||||||
while (reader.Read())
|
while (ReadArgumentAsType(reader, paramTypes, paramIndex))
|
||||||
{
|
{
|
||||||
if (reader.TokenType == JsonToken.EndArray)
|
if (reader.TokenType == JsonToken.EndArray)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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':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,'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,'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)
|
public void ArgumentBindingErrors(string input, string expectedMessage)
|
||||||
{
|
{
|
||||||
input = Frame(input);
|
input = Frame(input);
|
||||||
|
|
@ -262,6 +263,37 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
|
||||||
Assert.Equal(expectedMessage, bindingFailure.BindingFailure.SourceException.Message);
|
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)
|
private static string Frame(string input)
|
||||||
{
|
{
|
||||||
var data = Encoding.UTF8.GetBytes(input);
|
var data = Encoding.UTF8.GetBytes(input);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue