可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I've got a WebAPI action that looks like so:
[Route("api/values/{id}")]
public async Task<HttpResponseMessage> Delete(string id, DateTimeOffset date) {
//do stuff
}
But when I invoke this from a HttpClient
instance, making a URL like:
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 400
response back saying that the non-nullable parameter date
does not exist.
I've also tried adding the [FromUri]
attribute to the parameter but it still doesn't map through. If I change it to be DateTimeOffset?
I can see it is left as null and looking at Request.RequestUri.Query
the value is there, just not mapped.
Finally I tried not doing a WebUtility.UrlEncode
and it made no different.
回答1:
The problem is being described exactly by the 400 response message, although it could have been more clear. The route, as defined by the attribute, only expects a parameter id, but the Delete method expects another parameter called date.
If you want to provide this value using the query string, you'll need to make that parameter nullable, by using "DateTimeOffset?", which would also transform it into an optional parameter. If the date is a required field, consider adding it to the route, like:
[Route("api/values/{id}/{date}")]
OK, ignore what I typed above, it's just a formatting problem. Web API has trouble figuring out the culture needed to parse the given value, but if you try to pass on DateTimeOffset using a JSON format in the query string, like 2014-05-06T22:24:55Z, that should work.
回答2:
Answer
To send a DateTimeOffset
to your API, format it like this after converting it to UTC:
2017-04-17T05:04:18.070Z
The complete API URL will look like this:
http://localhost:1234/api/values/1?date=2017-04-17T05:45:18.070Z
It’s important to first convert the DateTimeOffset to UTC, because, as @OffHeGoes points out in the comments, the Z
at the end of the string indicates Zulu Time (more commonly known as UTC).
Code
You can use .ToUniversalTime().ToString(yyyy-MM-ddTHH:mm:ss.fffZ)
to parse the DateTimeOffset.
To ensure your DateTimeOffset is formatted using the correct timezone always use .ToUniversalTime()
to first convert the DateTimeOffset
value to UTC, because the Z
at the end of the string indicates UTC, aka "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);
回答3:
To achieve this, I'm using
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);
}
}
回答4:
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, such as WebApiConfig.Register
, add this type converter dynamically to the DateTimeOffset
struct:
TypeDescriptor.AddAttributes(typeof(DateTimeOffset),
new TypeConverterAttribute(typeof(DateTimeOffsetConverter)));
You can now just pass DateTimeOffset
values in the compact form of ISO8601, which omits hyphens and 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 slash in the url.
api/values/20171231T012345.1234567-0530/
You could also put it in the querystring if you like:
api/values?foo=20171231T012345-0530
回答5:
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}"
回答6:
The current accepted answer throws away the time zone information, which in some cases is important. The following maintains the time zone and doesn't lose any precision. It also keeps your code succinct when building a query string.
public static string UrlEncode(this DateTimeOffset dateTimeOffset)
{
return HttpUtility.UrlEncode(dateTimeOffset.ToString("o"));
}
回答7:
Best way to find out is to ask the WebAPI to generate the expected URL format itself:
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 to /offset is made the response will return a 302 to a url that contains the 'date' parameter in the querystring.
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 would generate a string value in that format except for explicitly defining the format in a string format:
DateTimeOffset.Now.ToString("dd/MM/yyyy HH:mm:ss zzz")
Hope that helps.
回答8:
Here is the easiest way for those who are looking for some kind of sync between client and server using datetime. I implemented that for mobile application. It is independent from the culture of the client. because my mobile app supports multiple cultures and it is boring to use formatting between those cultures. thanks that .net has a perfect functions 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";