Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent DateTime UTC conversion between Response and Parameters #2770

Open
deanmccrae opened this issue Apr 3, 2020 · 3 comments
Open

Comments

@deanmccrae
Copy link

deanmccrae commented Apr 3, 2020

I am using NSwag to generate a controller.

I have a DateTime field in my response, which is generated as follows:

/// <summary>The UTC timestamp.</summary> [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public System.DateTime Timestamp { get; set; }

The yaml definition is:

timestamp: description: The UTC timestamp. type: string format: date-time

The DateTime value in my c# is always DateTimeKind.Utc and in the response it undergoes no conversion to local time, and has a value such as: "timestamp": "2020-04-03T09:33:52.784818Z" in the json.

However I have now added a DateTime query parameter, defined as follows in the yaml file:

name: fromdate in: query type: string format: date-time required: false

The parameter in my c# code generated method is as follows:

[Microsoft.AspNetCore.Mvc.FromQuery] System.DateTime? fromdate = null

I now pass in a value for this parameter in the API call, e.g. the one returned above as follows, via Postman:

requests?fromDate=2020-04-03T09:33:52.784818Z

The problem here is that the value in my fromDate DateTime parameter in the method call has undergone a utc/local time conversion (in my case it is 11:33 not 09:33), which does not occur when a DateTime value is returned in the response.

I would expect the value to have had no conversion and be the exact same value as that passed in.

I tried setting the configuration parameter as follows "parameterDateTimeFormat": "u" in the hope that specifying the UTC format instead of the default "s" format would influence this, but it had no effect.

I have attached my nswag configuration file.

Can anyone explain the behavior - do I need to configure something, is it a bug, etc.?

Thank you!

api.txt

@deanmccrae deanmccrae changed the title Inconsistent UTC conversion between Response and Parameters Inconsistent DateTime UTC conversion between Response and Parameters Apr 3, 2020
@deanmccrae
Copy link
Author

It seems this is a bug in ASP.Net Core, which NSwag has no influence over. More details can be found here: dotnet/aspnetcore#11584.

@augustoproiete
Copy link
Sponsor

augustoproiete commented Nov 2, 2020

I can confirm there is a bug within NSwag from the point of view of the request - not related to the ASP .NET Core bug dotnet/aspnetcore#11584 referenced above (which causes issues on the response).

Reproduced with the latest version of NSwag.CodeGeneration.CSharp as of this writing (v13.8.2).

Given a Controller/Action that looks like this:

[HttpGet("test/{value:datetime}")]
[ProducesResponseType(typeof(DateTime), StatusCodes.Status200OK)]
public IActionResult GetDateAndReturnDate(DateTime value)
{
    return Ok(value);
}

Generating a client with NSwag results in the following client code being generated:

// ...
var urlBuilder_ = new System.Text.StringBuilder();
urlBuilder_.Append("test/{value}");
urlBuilder_.Replace("{value}", System.Uri.EscapeDataString(value.ToString("s",
    System.Globalization.CultureInfo.InvariantCulture)));
// ...

As can be seen, the generated code is assuming a Sortable format rather than expected ISO 8601-1:2019 format that you would get with the JSON serializer, and results in this request:

GET https://augustoproiete.net/test/2020-11-02T17%3A00%3A02

Expected:

GET https://augustoproiete.net/test/2020-11-02T17%3A00%3A02.1097278Z

Swagger definition from the Controller above

{
   "/test/{value}": {
      "get": {
         "tags": [
            "Test"
         ],
         "operationId": "Test_GetDateAndReturnDate",
         "produces": [
            "text/plain",
            "application/json",
            "text/json"
         ],
         "parameters": [
            {
               "type": "string",
               "name": "value",
               "in": "path",
               "required": true,
               "format": "date-time",
               "x-nullable": false
            }
         ],
         "responses": {
            "200": {
               "x-nullable": false,
               "description": "",
               "schema": {
                  "type": "string",
                  "format": "date-time"
               }
            }
         }
      }
   }
}

@augustoproiete
Copy link
Sponsor

A workaround for now is to set the CSharpClientGeneratorSettings.ParameterDateTimeFormat to a format that you want across all APIs. e.g. yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'FFFFFFFZ.

This should work for most cases when all APIs handle DateTime the same way.

Of course, this is not ideal given that each API client can have a different JsonSerializerSettings.DateTimeZoneHandling (by implementing UpdateJsonSerializerSettings) and the serialization of DateTime should honor the JsonSerializerSettings.DateTimeZoneHandling of each individual API.

This is what I ended up with:

var clientSettings = new CSharpClientGeneratorSettings
{
    ClientBaseClass = "ApiClientBase",
    ClientClassAccessModifier = "internal",
    GenerateUpdateJsonSerializerSettingsMethod = false,

    // Inconsistent DateTime UTC conversion between Response and Parameters #2770
    // https://github.com/RicoSuter/NSwag/issues/2770
    ParameterDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'FFFFFFFZ",

    CSharpGeneratorSettings =
    {
        DateType = "System.DateTime",
        DateTimeType = "System.DateTime",

        // ...
    },

    // ...
};

internal abstract class ApiClientBase
{
    protected void UpdateJsonSerializerSettings(JsonSerializerSettings settings)
    {
        // Json.NET interprets and modifies ISO dates when deserializing to JObject #862
        // https://github.com/JamesNK/Newtonsoft.Json/issues/862
        settings.DateParseHandling = DateParseHandling.None;
        
        // Inconsistent DateTime UTC conversion between Response and Parameters #2770
        // https://github.com/RicoSuter/NSwag/issues/2770
        settings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants