Passing DateTimeOffset as a WebAPI request string

I have a WebAPI action that looks like this:

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

But when I call this from an HttpClient instance, creating a URL, for example:

 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" 

I get a response from 400 saying that a parameter that doesn't have a nullable date value does not exist.

I also tried adding the [FromUri] attribute to the parameter, but it still does not appear. Should I change it to DateTimeOffset? , I see that it is left as null and looks at Request.RequestUri.Query , this value has a value that is simply not displayed.

Finally, I tried not to do WebUtility.UrlEncode , and it has not changed.

+10
c # asp.net-web-api
source share
8 answers

The problem is described exactly in response message 400, although it could have been clearer. The route, as defined by the attribute, expects only the parameter identifier , but the Delete method expects another parameter named date .

If you want to provide this value using the query string, you need to make this parameter nullable using "DateTimeOffset?", Which will also convert it to an optional parameter. If the date is a required field, consider adding it to the route, for example:

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

OK, ignore what I typed above, it's just a formatting issue. The Web API is having trouble figuring out the culture needed to parse a given value, but if you try to pass a DateTimeOffset using the JSON format in the query string, for example 2014-05-06T22: 24: 55Z, this should work.

+3
source share

Answer

To send DateTimeOffset your API, format it as follows after converting to UTC :

2017-04-17T05:04:18.070Z

The full API URL will look like this:

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

It is important to convert DateTimeOffset to UTC first, because, as @OffHeGoes points out in the comments , the Z at the end of the line indicates Zulu time (more commonly known as UTC).

The code

You can use .ToUniversalTime().ToString(yyyy-MM-ddTHH:mm:ss.fffZ) to parse DateTimeOffset.

To make sure your DateTimeOffset is formatted using the correct time zone, always use .ToUniversalTime() to first convert the DateTimeOffset value to UTC, because the Z at the end of the line indicates UTC, that is, "Zulu Time".

 DateTimeOffset currentTime = DateTimeOffset.UtcNow; string dateTimeOffsetAsAPIParameter = currentDateTimeOffset.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); string apiUrl = string.Format("http://localhost:1234/api/values/1?date={0}", dateTimeOffsetAsAPIParameter); 
+13
source share

Create a custom type converter as follows:

 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); } 

In your startup sequence, for example WebApiConfig.Register , dynamically add this type of converter to the DateTimeOffset structure:

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

Now you can simply pass the DateTimeOffset values ​​in a compact form of ISO8601, which does not allow hyphens or colons that interfere with the URL:

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

Note that if you have fractional seconds, you may need to include a trailing braid in the URL .

 api/values/20171231T012345.1234567-0530/ 

You can also queue it if you want:

 api/values?foo=20171231T012345-0530 
+3
source share

To achieve this, I use

 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); } } 
+2
source share

Use the ISO 8601 datetime format specifier:

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

or

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

The current accepted response discards the time zone information, which is important in some cases. The following maintains the time zone and does not lose any accuracy. It also keeps your code concise when building a query string.

 public static string UrlEncode(this DateTimeOffset dateTimeOffset) { return HttpUtility.UrlEncode(dateTimeOffset.ToString("o")); } 
+1
source share

The best way to find out if WebAPI should generate the expected URL format:

 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); } } 

When a call / offset is made, the response returns 302 to the URL that contains the "date" parameter in the query string. It will look something like this:

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

I could not find an overload of DateTimeOffset.ToString () that generates a string value in this format, except for an explicit format definition in string format:

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

Hope this helps.

0
source share

Here is the easiest way for those who are looking for some kind of synchronization between client and server using datetime. I implemented this for a mobile application. It does not depend on the culture of the client. because my mobile application supports multiple cultures, and it's boring to use formatting between these cultures. thank you .net has perfect features called ToFileTime and FromFileTime

Server Controller Action:

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

Client side

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

All Articles