Управление форматированием параметров DateTime в WebAPI 2

Итак, у меня есть контроллер WebAPI 2, написанный на С#, в котором помимо прочего используется параметр запроса типа DateTime. Это API, который возвращает все значения из хранилища данных на основе фильтра даты. Что-то вроде, скажем:

public MyThing GetThing([FromUri]DateTime startTime)
{
 // filter and return some results
}

Я столкнулся с двумя проблемами:

  1. По какой-то причине, несмотря на то, что в ISO 8601 UTC отформатирована (с Z) дата, WebAPI де-сериализует ее как локальную DateTime, а не Utc. Это явно нежелательно. Я не уверен, как изменить конвейер, чтобы он правильно понял UTC-0 DateTimes.
  2. Я возвращаю ссылку на ресурс как часть тела ответа, в котором я использую объекты UrlHelper (полученные из исходного класса абстрактного класса ApiController) Link() для генерации href. Я передаю набор параметров запроса, которые я хочу добавить в маршрут. По какой-либо причине передача форматов DateTime в формате, отличном от ISO8601. Я не могу найти, где это контролируется. Я не хочу явно указывать ToString(), поскольку это не подлежит обязательному исполнению.

Короче говоря, я хочу выяснить, как убедиться, что

  1. DateTimes, которые передаются через параметры запроса FromUri, правильно понимаются как ISO8601, включая соответствующие смещения часовых поясов
  2. 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.