将DateTimeOffset作为WebAPI查询字符串传递

我有一个WebAPI动作,如下所示:

[Route("api/values/{id}")] public async Task Delete(string id, DateTimeOffset date) { //do stuff } 

但是当我从HttpClient实例调用它时,创建一个类似于的URL:

 string.Format("http://localhost:1234/api/values/1?date={0}", System.Net.WebUtility.UrlEncode(DateTimeOffset.Now.ToString())); // -> "http://localhost:1234/api/values/1?date=17%2F02%2F2015+7%3A18%3A39+AM+%2B11%3A00" 

我收到400响应,说不可存在的参数date不存在。

我也尝试将[FromUri]属性添加到参数中,但它仍然没有映射。 如果我将其更改为DateTimeOffset? 我可以看到它被保留为null并查看Request.RequestUri.Query值是否存在,只是没有映射。

最后我尝试不做WebUtility.UrlEncode ,它没有任何不同。

400响应消息正在准确地描述该问题,尽管它可能更清楚。 由属性定义的路由仅需要参数id ,但Delete方法需要另一个名为date的参数。

如果要使用查询字符串提供此值,则需要使用“DateTimeOffset?”将该参数设为可为空,这也会将其转换为可选参数。 如果日期是必填字段,请考虑将其添加到路线中,例如:

 [Route("api/values/{id}/{date}")] 

好的,忽略我上面输入的内容,这只是一个格式化问题。 Web API无法确定解析给定值所需的文化,但如果您尝试在查询字符串中使用JSON格式传递DateTimeOffset,例如2014-05-06T22:24:55Z,那应该可行。

回答

要将DateTimeOffset发送到API,请将其格式化为:

2017-04-17T05:04:18.070Z

完整的API URL如下所示:

http://localhost:1234/api/values/1?date=2017-04-17T05:45:18.070Z

通过以这种方式对其进行格式化,我可以将DateTimeOffset参数传递给我使用Azure Mobile Services ApiController

您可以使用ToString(yyy-MM-ddTHH:mm:ss.fffZ)来解析DateTimeOffset。

 var dateTimeOffsetAsAPIParameter = DateTimeOffset.UtcNow.ToString("yyy-MM-ddTHH:mm:ss.fffZ"); string.Format("http://localhost:1234/api/values/1?date={0}", dateTimeOffsetAsAPIParameter); 

要实现这一点,我正在使用

 internal static class DateTimeOffsetExtensions { private const string Iso8601UtcDateTimeFormat = "yyyy-MM-ddTHH:mm:ssZ"; public static string ToIso8601DateTimeOffset(this DateTimeOffset dateTimeOffset) { return dateTimeOffset.ToUniversalTime().ToString(Iso8601UtcDateTimeFormat); } } 

创建自定义类型转换器,如下所示:

 public class DateTimeOffsetConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) return true; return base.CanConvertFrom(context, sourceType); } public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(DateTimeOffset)) return true; return base.CanConvertTo(context, destinationType); } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { var s = value as string; if (s != null) { if (s.EndsWith("Z", StringComparison.OrdinalIgnoreCase)) { s = s.Substring(0, s.Length - 1) + "+0000"; } DateTimeOffset result; if (DateTimeOffset.TryParseExact(s, "yyyyMMdd'T'HHmmss.FFFFFFFzzz", CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) { return result; } } return base.ConvertFrom(context, culture, value); } 

在启动序列中,例如WebApiConfig.Register ,将此类型转换器动态添加到DateTimeOffset结构中:

 TypeDescriptor.AddAttributes(typeof(DateTimeOffset), new TypeConverterAttribute(typeof(DateTimeOffsetConverter))); 

您现在可以在ISO8601的紧凑forms中传递DateTimeOffset值,这会忽略干扰URL的连字符和冒号:

 api/values/20171231T012345-0530 api/values/20171231T012345+0000 api/values/20171231T012345Z 

请注意,如果您有小数秒,则可能需要在url中包含尾部斜杠 。

 api/values/20171231T012345.1234567-0530/ 

如果你愿意,你也可以把它放在查询字符串中:

 api/values?foo=20171231T012345-0530 

使用ISO 8601 日期时间格式说明符:

 $"http://localhost:1234/api/values/1?date={DateTime.UtcNow.ToString("o")}" 

要么

 $"http://localhost:1234/api/values/1?date={DateTime.UtcNow:o}" 

找出答案的最佳方法是要求WebAPI自己生成预期的URL格式:

 public class OffsetController : ApiController { [Route("offset", Name = "Offset")] public IHttpActionResult Get(System.DateTimeOffset date) { return this.Ok("Received: " + date); } [Route("offset", Name = "Default")] public IHttpActionResult Get() { var routeValues = new { date = System.DateTimeOffset.Now }; return this.RedirectToRoute("Offset", routeValues); } } 

当调用/ offset时,响应将返回302到包含查询字符串中’date’参数的url。 它看起来像这样:

http:// localhost:54955 / offset?date = 02/17 / 2015 09:25:38 +11:00

我找不到DateTimeOffset.ToString()的重载,它会生成该格式的字符串值,除非以字符串格式显式定义格式:

 DateTimeOffset.Now.ToString("dd/MM/yyyy HH:mm:ss zzz") 

希望有所帮助。

对于那些使用datetime在客户端和服务器之间寻找某种同步的人来说,这是最简单的方法。 我为移动应用程序实现了它。 它独立于客户的文化。 因为我的移动应用支持多种文化,在这些文化之间使用格式很无聊。 谢谢.net有一个称为ToFileTimeFromFileTime的完美函数

Server Controller动作:

 [HttpGet("PullAsync")] public async Task PullSync(long? since = null, int? page = null, int? count = null) { if (since.HasValue) DateTimeOffset date = DateTimeOffset.FromFileTime(since.Value); } 

客户端

 DateTimeOffset dateTime = DateTime.Now.ToFileTime(); var url= $"/PullAsync?since={datetime}&page={pageno}&count=10";