Управление форматированием параметров DateTime в WebAPI 2
Итак, у меня есть контроллер WebAPI 2, написанный на С#, в котором помимо прочего используется параметр запроса типа DateTime. Это API, который возвращает все значения из хранилища данных на основе фильтра даты. Что-то вроде, скажем:
public MyThing GetThing([FromUri]DateTime startTime)
{
// filter and return some results
}
Я столкнулся с двумя проблемами:
- По какой-то причине, несмотря на то, что в ISO 8601 UTC отформатирована (с Z) дата, WebAPI де-сериализует ее как локальную DateTime, а не Utc. Это явно нежелательно. Я не уверен, как изменить конвейер, чтобы он правильно понял UTC-0 DateTimes.
- Я возвращаю ссылку на ресурс как часть тела ответа, в котором я использую объекты UrlHelper (полученные из исходного класса абстрактного класса ApiController) Link() для генерации href. Я передаю набор параметров запроса, которые я хочу добавить в маршрут. По какой-либо причине передача форматов DateTime в формате, отличном от ISO8601. Я не могу найти, где это контролируется. Я не хочу явно указывать ToString(), поскольку это не подлежит обязательному исполнению.
Короче говоря, я хочу выяснить, как убедиться, что
- DateTimes, которые передаются через параметры запроса FromUri, правильно понимаются как ISO8601, включая соответствующие смещения часовых поясов
- UrlHelper.Link() генерирует ISO8601-совместимые DateTimes в строке выходного URI в общепринятом статически типизированном виде.
WebAPI 2 действительно предоставляет прекрасные возможности для форматирования JSON, которые я использую, поэтому простое возвращение DateTime в корпусе JSON форматирует его по желанию с использованием формата ISO8601, а также правильно понимается в теле JSON [FromBody]. Я не могу найти способы потянуть строки вокруг обработки URI, хотя, и я бы очень хотел!
Ответы
Ответ 1
Вы можете использовать modelbinder для преобразования входящих данных в вашу модель.
GetThings([ModelBinder(typeof(UtcDateTimeModelBinder)), FromUri] DateTime dt){//do somthing}
public class UtcDateTimeModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
if (bindingContext.ModelMetadata.ModelType == typeof(DateTime))
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var str = valueProviderResult.AttemptedValue;
return DateTime.Parse(str).ToUniversalTime();
}
return null;
}
Таким образом вы можете установить его как defaultbilder DateTime.
ModelBinders.Binders.Add(typeof(DateTime), new UtcDateTimeModelBinder());
Ответ 2
Почему бы не использовать DateTimeOffset вместо DateTime, если вы хотите сохранить смещение UTC? Вот несколько кодов, работающих с сериализацией JSON:
Контроллер Api:
public class ValuesController : ApiController
{
public object Get(DateTimeOffset dt)
{
return new {
Input = dt,
Local = dt.LocalDateTime,
Utc = dt.UtcDateTime
};
}
}
Razor Просмотр образца кода (если у вас есть маршрут api по умолчанию, созданный в шаблоне Visual Studio MVC + Web API)
<a href="@Url.RouteUrl("DefaultApi",new {httproute = "",controller = "values",dt = DateTimeOffset.UtcNow})">Utc Now</a>
Представлено как:
<a href="/api/values?dt=06%2F26%2F2018%2009%3A37%3A24%20%2B00%3A00">Utc Now</a>
И вы можете назвать свой API с помощью смещения datetime:
2018-06-26T08:25:48Z: http://localhost:1353/api/values?dt=2018-06-26T08:25:48Z
{"Input":"2018-06-26T08:25:48+00:00","Local":"2018-06-26T10:25:48+02:00","Utc":"2018-06-26T08:25:48Z"}
2018-06-26T08:25:48+01:00: http://localhost:1353/api/values?dt=2018-06-26T08%3A25%3A48%2B01%3A00 (note that : and + must be url encoded)
{"Input":"2018-06-26T08:25:48+01:00","Local":"2018-06-26T09:25:48+02:00","Utc":"2018-06-26T07:25:48Z"}
Ответ 3
Значение параметра строки запроса, которое вы отправляете, - это время UTC. Таким образом, то же самое переводится на время, основанное на ваших локальных часах, и если вы вызываете ToUniversalTime()
, он преобразуется обратно в UTC.
Итак, что конкретно представляет собой вопрос? Если возникает вопрос, почему это происходит, если он отправлен как строка запроса, но не когда отправлен в тело запроса, ответ на этот вопрос заключается в том, что ASP.NET Web API связывает путь URI, строку запроса и т.д., Используя привязку модели и использование тела привязка параметров. Для последнего используется медиаформат. Если вы отправляете JSON, используется форматировщик JSON, и он основан на JSON.NET.
Поскольку вы указали DateTimeZoneHandling.Utc, он использует этот параметр, и вы получите нужный тип даты. BTW, если вы измените этот параметр на DateTimeZoneHandling.Local, вы увидите то же поведение, что и привязка модели.
Чтобы получить желаемое форматирование, все, что вам нужно сделать, это вызвать метод ToUniversalTime()
.
Ответ 4
1.
Вы должны проверить часовой пояс своего параметра "startTime" (который должен быть часовым поясом вашего сервера/компьютера).
Правило DateTime, предоставляемое веб-API, зависит от ВАШЕГО часового пояса.
2.
Создайте сериализатор Json DateTime для генерации даты в формате ISO8601.