Подтвердить что ты не робот

Как заставить связующее устройство ASP.Net MVC обрабатывать входящую дату как UTC?

Я отправляю объект на контроллер MVC. Объект содержит поле StartDt, а на клиенте это объект даты javascript в локальное время.

Когда я вызываю JSON.stringify на объекте и POST его на сервер, используя jQuery ajax-метод, я вижу в Firebug, что на сервер отправляется строка ISO, например "1900-12-31T13: 00: 00.000Z" который, я считаю, должен быть местным временем в формате UTC.

Когда я смотрю на поле DateTime в моем контроллере, это похоже на его возвращение к местному времени, а не по UTC. Как я могу это исправить?

Я хочу сохранить версию UTC Даты, которая была получена от клиента.

4b9b3361

Ответ 1

Вам может понадобиться использовать метод DateTime.ToUniversalTime(), чтобы вернуть время UTC.

Ответ 2

Я нашел суть в Google с кодом для ISO 8601 DateTime Model Binder, а затем изменил его следующим образом:

public class DateTimeBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var name = bindingContext.ModelName;
        var value = bindingContext.ValueProvider.GetValue(name);
        if (value == null) 
            return null;

        DateTime date;
        if (DateTime.TryParse(value.AttemptedValue, null, DateTimeStyles.RoundtripKind, out date))
            return date;
        else
            return base.BindModel(controllerContext, bindingContext);
    }
}

Я считаю, что gist-код слишком ограничительный - он хочет 6 десятичных знаков в секундах или не будет принимать отметки времени. Это использует TryParse вместо TryParseExact, поэтому он технически примет много типов временных меток. Важная часть состоит в том, что он использует DateTimeStyles.RoundtripKind для учета часового пояса, подразумеваемого Z. Таким образом, это уже не технически специфическая реализация ISO 8601.

Затем вы можете связать это с конвейером MVC с атрибутом привязки модели или с помощью этого фрагмента в App_Start:

var dateTimeBinder = new DateTimeBinder();

ModelBinders.Binders.Add(typeof(DateTime), dateTimeBinder);
ModelBinders.Binders.Add(typeof(DateTime?), dateTimeBinder);

Ответ 3

Я создал этот маленький атрибут.

public class ConvertDateToUTCAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var dateArgs =
            filterContext.ActionParameters.Where(
                x => x.Value != null && x.Value.GetType().IsAssignableFrom(typeof(DateTime))).ToList();

        foreach (var keyValuePair in dateArgs)
        {
            var date = (DateTime) keyValuePair.Value;

            if (date.Kind == DateTimeKind.Local)
                filterContext.ActionParameters[keyValuePair.Key] = date.ToUniversalTime();
        }

        base.OnActionExecuting(filterContext);
    }
}

Таким образом, это приведет к тому, что даты, которые являются Unspecified или уже Utc в одиночку. Вы можете применить к нему весь контроллер.

Ответ 4

Эта проблема сохраняется в ASP.NET Core 2.0. Следующий код разрешит его, поддерживая базовые и расширенные форматы ISO 8601, правильно сохраняя значение и устанавливая DateTimeKind правильно. Это согласуется с поведением JSON.Net по умолчанию, поэтому он сохраняет поведение привязки модели согласованно с остальной частью системы.

Сначала добавьте следующее связующее:

public class DateTimeModelBinder : IModelBinder
{
    private static readonly string[] DateTimeFormats = { "yyyyMMdd'T'HHmmss.FFFFFFFK", "yyyy-MM-dd'T'HH:mm:ss.FFFFFFFK" };

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        var stringValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).FirstValue;

        if (bindingContext.ModelType == typeof(DateTime?) && string.IsNullOrEmpty(stringValue))
        {
            bindingContext.Result = ModelBindingResult.Success(null);
            return Task.CompletedTask;
        }

        bindingContext.Result = DateTime.TryParseExact(stringValue, DateTimeFormats,
            CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var result)
            ? ModelBindingResult.Success(result)
            : ModelBindingResult.Failed();

        return Task.CompletedTask;
    }
}

Затем добавьте следующий поставщик связующего поставщика:

public class DateTimeModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));

        if (context.Metadata.ModelType != typeof(DateTime) &&
            context.Metadata.ModelType != typeof(DateTime?))
            return null;

        return new BinderTypeModelBinder(typeof(DateTimeModelBinder));
    }
}

Затем зарегистрируйте поставщика в вашем файле Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        ...

        options.ModelBinderProviders.Insert(0, new DateTimeModelBinderProvider());

        ...
    }
}