DateTime.Kind serialization/deserialization on JsonServiceClient

Need to clear up some confusion on a problem I though we had resolved months/years ago.
(currently using 4.0.42)

In client code using our sub-classed JsonServiceClient, it appears that deserialized DateTime properties in our DTO’s are by default DateTimeKind.Local, even though have the following JsConfig:
AssumeUtc = true;
AlwaysUseUtc = true;
DateHandler = DateHandler.ISO8601;

The values of the DateTimes that are being Deserialized are in fact correct, the problem is that the Kind property is DateTimeKind.Local instead of DateTimeKind.Utc.
So I have to call ToUniversalTime(), to make the DateTime actually a UTC dateTime.

Is this a recent change?

Do I have to apply my own Serialization/Deserialization functions to yield DateTime’s that are by default DateTimeKind.Utc?
Like this?
JsConfig.SerializeFn = time =>
{
return new DateTime(time.Ticks, DateTimeKind.Utc).ToString(“o”);
};
JsConfig.DeSerializeFn = time =>
{
return DateTime.ParseExact(time, “o”, null).ToUniversalTime();
};

The default behavior is DateTime’s are serialized and sent as UTC but deserialized back into local time.

There’s no recent change that I can recall, but can you try with the latest v4.0.46 to check if it’s still an issue.
Is the JsConfig on the client specified at the start of the Application?

Yes JsConfig is applied (albeit within a JSConfig.BeginScope) to all instances of our JsonServiceClient when it is instantiated in ctor.

OK, might be first time I am seeing this. Although that surprises the heck out of me, given we hadn’t noticed this in the last 2 years of using the client.
But knowing that all datetimes are serialized to DateTimeKind.Local is a real good gem.

(Now I am quietly cautious of what the side effects might be of the proposed change below?? :slight_smile:

OK, so lets say we decide to de-serialise DateTime(s) so that they are all DateTimeKind.Utc.

Do I do that by adding this in my JsConfig scope:

JsConfig.SerializeFn = time =>
{
return new DateTime(time.Ticks, DateTimeKind.Utc).ToString(“o”);
};
JsConfig.DeSerializeFn = time =>
{
return DateTime.ParseExact(time, “o”, null).ToUniversalTime();
};

And should we have both serialisation and deserialization handlers here? Or can we omit the serialization one?

Both should work, overriding both gives you symmetry so you can tell what serialized value you’re deserializing.

If you only specify a DeSerializeFn it will also be reliant on having specifying the DateHandler configuration, i.e:

DateHandler = DateHandler.ISO8601;

Which may not be obvious since its impl is hidden.

Let me ask a different question.

If I have this code defined in the ctor of our sub-classed JsonServiceClient class:
var scope = JsConfig.BeginScope();
scope.AssumeUtc = true;
scope.AlwaysUseUtc = true;
scope.DateHandler = DateHandler.ISO8601;

Then deserialized DateTime instances of any DTO’s are going to have Kind = DateTimeKind.Utc correct?

That’s what our tests are showing at least.

I wouldn’t create an open scope in a constructor, they should in a using{}, scoped around and limited to where it’s used like how we do it in StripeGateway.

But yeah it should be deserialized as Utc.

OK great, so given we have a sub-classed version of JsonServiceClient.
If we wanted to ensure that all outbound requests used our JSScope do I wrap the scope around the only this single method:

    public override TResponse Send<TResponse>(string httpMethod, string relativeOrAbsoluteUrl, object request)
    {
        using (var scope = JsConfig.BeginScope())
        {
            scope.AssumeUtc = true;
            scope.AlwaysUseUtc = true;
            scope.DateHandler = DateHandler.ISO8601;

            return base.Send<TResponse>(httpMethod, relativeOrAbsoluteUrl, request);
        }
    }

Or are there others I need to wrap?

Obviously that will depend on if you’re only using the API’s that use that method, there’s a number that don’t, e.g. Async API’s, SendOneWay, SendAll, PostFile, etc. Check the ServiceClientBase source code to find out which methods do use Send<T>.