【问题标题】:Asp.net WebApi deserializes UTC time string to local timeAsp.net Web Api 将 UTC 时间字符串反序列化为本地时间
【发布时间】:2013-05-25 10:24:42
【问题描述】:

我有这个网址

http://example.com/api/record/getall?startdate=1994-11-05T17:15:30Z

还有这个 webapi 端点

[ActionName("GetAll")]
public object GetAll(DateTime startDate)
{
     ...
}

我面临的问题是 startDate 将反序列化字符串作为本地时间接收,“11/5/1994 9:15:30 AM” 而不是我想要的停留在 UTC 时间“11/5/1994 下午 5:15:30”。

我正在使用 VS2012 update2,最新的 Json.net nuget 包。但是,如果我在单独的控制台应用程序中使用 json.net 进行测试,相同的字符串“1994-11-05T17:15:30Z”能够正确反序列化为“11/5 /1994 下午 5:15:30"。

有人知道这里有什么问题吗?

【问题讨论】:

标签: asp.net-web-api json.net utc


【解决方案1】:

虽然您已经为您的问题提供了found a solution,但我想我会尝试解释一下为什么它没有按您的预期工作。

WebApi 使用内容类型协商来确定读取数据时使用的解析器。这意味着它将查看请求的Content-Type 标头以做出决定。如果Content-Type 标头设置为application/json,那么它将使用Json.Net 解析内容并将其提供给您的方法。

HTTP GET 请求(例如您在此处发出的请求)没有设置内容类型。在这种情况下,“内容”实际上只是来自 URL 的查询字符串。 WebApi 不希望在这里找到 JSON 数据,因此它不会尝试使用 JSON 解析器来理解它。即使是这样,您传递给 GetAll 方法的字符串也不是有效的 JSON。 (它需要被引用才能有效。)

现在,如果您要更改方法以接受 POST 请求,并将内容类型标头设置为 application/json 并将日期作为 JSON 字符串传递到正文中,那么 WebApi 将使用 Json.Net 解析它,它会像你期望的那样工作。

例如,假设您的方法如下所示:

[HttpPost]
public object GetAll([FromBody]DateTime startDate)
{
    try
    {
        return new
        {
            StartDate = startDate.ToString("yyyy-MM-dd HH:mm:ss"),
            StartDateKind = startDate.Kind.ToString(),
        };
    }
    catch (Exception ex)
    {
        return ex.Message;
    }
}

你提出了这样的请求(注意 POST):

POST http://localhost:57524/api/values/GetAll HTTP/1.1
Content-Type: application/json
Content-Length: 22
Host: localhost:57524

"1994-11-05T17:15:30Z"

响应如下所示:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 31 May 2013 01:25:48 GMT
Content-Length: 57

{"StartDate":"1994-11-05 17:15:30","StartDateKind":"Utc"}

如您所见,在这种情况下,它确实正确地将日期识别为 UTC。

【讨论】:

  • 非常感谢您抽出宝贵时间进行解释。 +1 并将您标记为答案。
  • 不幸的是,这不适用于将 [FromUri] 属性附加到您的请求参数的 GET 请求。不知道如何让它工作。
  • 我知道这篇文章有点老了,但有没有人在解析日期时找到 [FromUri] 属性的解决方案?
  • 是的,虽然这解释了为什么会发生这种情况,但我想知道如何解决这个问题,而无需在控制器内部进行手动日期转换。
【解决方案2】:

如果你想修改 Asp WebApi 解析你的 GET 请求的 uri 参数的方式,你可以这样写自定义IModelBinder

public class UtcDateTimeModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        var stringValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName)?.RawValue as string;
        DateTime parsedDate;
        if (!DateTime.TryParse(stringValue, null, DateTimeStyles.AdjustToUniversal, out parsedDate))
        {
            return false;
        }

        bindingContext.Model = parsedDate;
        return true;
    }
}

然后覆盖默认活页夹:

GlobalConfiguration.Configure(configuration => configuration.BindParameter(typeof(DateTime), new UtcDateTimeModelBinder()););

Parameter Binding in ASP.NET Web API

【讨论】:

  • 如果您的日期已经格式化为 ISO 格式“1994-11-05T17:15:30Z”。那么 DateTime.TryParse 应该修改为 DateTime.TryParse(stringValue, null, DateTimeStyles.RoundtripKind, out parsedDate)
【解决方案3】:

只需使用 DateTimeOffset 而不是 DateTime 作为 API 的输入类型:

[ActionName("GetAll")]
public object GetAll(DateTimeOffset startDate)
{
     ...
}

将查询字符串中的日期作为 UTC 传递,例如

startDate=2021-10-01T12:00:00.000Z

这将创建一个 C# DateTimeOffset 并且其UtcDateTime 属性将具有 UTC DateTime

DateTimeOffsetLocalDateTime 属性也将是 UTC DateTimeDateTimeOffsetOffset 属性将是 TimeSpan00:00:00。这与DateTime,而是将其 Kind 属性设置为 Utc 用于 UTC 日期时间)

使用DateTimeOffset 还具有允许将日期作为本地时间传递的优点,例如可以将以下日期传递给 API:

startDate=2021-10-01T18:00:00.000+05:00

注意,在查询字符串中传递这个时,+ 的 URL 必须编码为 %2B

startDate=2021-10-01T18:00:00.000%2B05:00

这将转换为DateTimeOffset,表示比 UTC 早 5 小时的时区中的当地时间:

startDate.DateTime = {1/10/21 6:00:00 pm}
startDate.UtcDateTime = {1/10/21 1:00:00 pm}
startDate.LocalDateTime = {1/10/21 1:00:00 pm}
startDate.Offset = {05:00:00}

【讨论】:

    猜你喜欢
    • 2014-07-18
    • 1970-01-01
    • 1970-01-01
    • 2011-06-13
    • 2020-11-22
    • 2014-12-10
    • 2010-09-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多