Передача DateTimeOffset в виде строки запроса WebAPI
У меня есть действие WebAPI, которое выглядит так:
[Route("api/values/{id}")]
public async Task<HttpResponseMessage> 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
, говорящий, что параметр, не имеющий значения nullable date
не существует.
Я также попытался добавить атрибут [FromUri]
к параметру, но он по-прежнему не отображается. Если я изменю его на DateTimeOffset?
, я вижу, что он оставлен как null и смотрит на Request.RequestUri.Query
, это значение имеет значение, просто не отображаемое.
Наконец, я попытался не делать WebUtility.UrlEncode
, и он не изменился.
Ответы
Ответ 1
Проблема описывается точно в ответном сообщении 400, хотя оно могло бы быть более ясным. Маршрут, как определено атрибутом, ожидает только идентификатор параметра, но метод Delete ожидает другой параметр с именем date.
Если вы хотите предоставить это значение, используя строку запроса, вам нужно сделать этот параметр обнуляемым, используя "DateTimeOffset?", Который также преобразует его в необязательный параметр. Если дата является обязательным полем, рассмотрите возможность добавления ее в маршрут, например:
[Route("api/values/{id}/{date}")]
ОК, игнорируйте то, что я напечатал выше, это просто проблема форматирования. В Web API возникают проблемы с выяснением культуры, необходимой для анализа заданного значения, но если вы попытаетесь передать DateTimeOffset с использованием формата JSON в строке запроса, например 2014-05-06T22: 24: 55Z, это должно сработать.
Ответ 2
Ответ
Чтобы отправить DateTimeOffset
вашему API, отформатируйте его следующим образом после преобразования в UTC:
2017-04-17T05:04:18.070Z
Полный URL API будет выглядеть так:
http://localhost:1234/api/values/1?date=2017-04-17T05:45:18.070Z
Важно сначала преобразовать DateTimeOffset в UTC, потому что, как указывает @OffHeGoes в комментариях, Z
в конце строки указывает время Зулу (более широко известное как UTC).
Код
Вы можете использовать .ToUniversalTime().ToString(yyyy-MM-ddTHH:mm:ss.fffZ)
для анализа DateTimeOffset.
Чтобы убедиться, что ваш DateTimeOffset отформатирован с использованием правильного часового пояса, всегда используйте .ToUniversalTime()
чтобы сначала преобразовать значение DateTimeOffset
в UTC, потому что Z
в конце строки указывает UTC, то есть "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
Создайте конвертер настраиваемого типа следующим образом:
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)));
Теперь вы можете просто передать значения DateTimeOffset
в компактной форме ISO8601, которая не допускает дефисов и двоеточий, которые мешают URL:
api/values/20171231T012345-0530
api/values/20171231T012345+0000
api/values/20171231T012345Z
Обратите внимание, что если у вас есть дробные секунды, вам может потребоваться включить трейлинг-косу в URL.
api/values/20171231T012345.1234567-0530/
Вы также можете поместить его в очередь, если хотите:
api/values?foo=20171231T012345-0530
Ответ 4
Чтобы достичь этого, я использую
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);
}
}
Ответ 5
Используйте спецификатор ISO 8601 datetime format:
$"http://localhost:1234/api/values/1?date={DateTime.UtcNow.ToString("o")}"
или
$"http://localhost:1234/api/values/1?date={DateTime.UtcNow:o}"
Ответ 6
Текущий принятый ответ отбрасывает информацию о часовом поясе, что в некоторых случаях важно. Следующее поддерживает часовой пояс и не теряет никакой точности. Он также сохраняет ваш код лаконичным при построении строки запроса.
public static string UrlEncode(this DateTimeOffset dateTimeOffset)
{
return HttpUtility.UrlEncode(dateTimeOffset.ToString("o"));
}
Ответ 7
Лучший способ узнать, должен ли 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);
}
}
Когда выполняется вызов/смещение, ответ возвращает 302 URL-адресу, который содержит параметр "date" в строке запроса.
Он будет выглядеть примерно так:
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")
Надеюсь, что это поможет.
Ответ 8
Вот самый простой способ для тех, кто ищет какую-то синхронизацию между клиентом и сервером, используя datetime. Я реализовал это для мобильного приложения. Это не зависит от культуры клиента. потому что мое мобильное приложение поддерживает несколько культур, и скучно использовать форматирование между этими культурами. спасибо, что .net имеет совершенные функции, называемые ToFileTime
и FromFileTime
Действие контроллера сервера:
[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);
}
Сторона клиента
DateTimeOffset dateTime = DateTime.Now.ToFileTime();
var url= $"/PullAsync?since={datetime}&page={pageno}&count=10";