29

我将一个对象发布到 MVC 控制器。该对象包含一个名为 StartDt 的字段,在客户端上它是本地时间的 javascript Date 对象。

当我在对象上调用 JSON.stringify 并使用 jQuery 的 ajax 方法将其发布到服务器时,我可以在 Firebug 中看到发送到服务器的是一个 ISO 字符串,例如“1900-12-31T13:00:00.000Z”,我相信应该是UTC格式的本地时间。

但是,当我查看控制器中的 DateTime 字段时,它看起来像是回到本地时间而不是 UTC。我怎样才能解决这个问题?

我想存储来自客户端的日期的 UTC 版本。

4

7 回答 7

23

此问题在 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());

        ...
    }
}
于 2017-09-19T20:02:19.323 回答
21

我在 Google 上找到了一个要点,其中包含符合 ISO 8601DateTime 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 特定的实现。

然后,您可以使用模型绑定器属性或 App_Start 中的此代码段将其挂接到 MVC 管道中:

var dateTimeBinder = new DateTimeBinder();

ModelBinders.Binders.Add(typeof(DateTime), dateTimeBinder);
ModelBinders.Binders.Add(typeof(DateTime?), dateTimeBinder);
于 2012-11-12T22:17:13.187 回答
9

我创建了这个小属性。

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);
    }
}

因此,这将留下未指定的日期或单独的 Utc 日期。您可以将其应用于整个控制器。

于 2012-04-27T01:34:31.647 回答
8

或者,您可以将绑定对象指定为DateTimeOffset而不是DateTime,并且不会自动转换。只要传入的字符串有Z后缀,你就应该得到偏移量为 的原始日期+00:00

于 2018-08-02T13:53:03.030 回答
7

您可能必须使用 DateTime.ToUniversalTime() 方法来取回 UTC 时间。

于 2012-04-24T07:27:09.367 回答
1

我认为这在 ASP.NET Core 5.0 中得到了解决。

https://github.com/dotnet/aspnetcore/issues/11584

于 2021-07-16T15:28:59.137 回答
0

这在 ASP.NET Core 5.0+ 中得到解决。但是,如果您还想将不表示本地或 UTC 时间的输入视为 UTC,您可以这样做:

using System;
using System.Globalization;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

internal class CustomDateTimeModelBinderProvider : IModelBinderProvider
{
    internal const DateTimeStyles SupportedStyles = DateTimeStyles.AdjustToUniversal | DateTimeStyles.AllowWhiteSpaces;

    public IModelBinder? GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        var modelType = context.Metadata.UnderlyingOrModelType;
        if (modelType == typeof(DateTime))
        {
            var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
            return new CustomDateTimeModelBinder(SupportedStyles, loggerFactory);
        }

        return null;
    }
}
using System;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.Extensions.Logging;

internal class CustomDateTimeModelBinder : IModelBinder
{
    private readonly DateTimeModelBinder _dateTimeModelBinder;

    public CustomDateTimeModelBinder(DateTimeStyles supportedStyles, ILoggerFactory loggerFactory)
    {
        _dateTimeModelBinder = new DateTimeModelBinder(supportedStyles, loggerFactory);
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        await _dateTimeModelBinder.BindModelAsync(bindingContext);

        if (bindingContext.Result.IsModelSet)
        {
            var value = (DateTime)bindingContext.Result.Model!;

            if (value.Kind == DateTimeKind.Unspecified)
            {
                var model = DateTime.SpecifyKind(value, DateTimeKind.Utc);

                bindingContext.Result = ModelBindingResult.Success(model);
            }
        }
    }
}
services.AddMvc(o =>
{
    o.ModelBinderProviders.Insert(0, new CustomDateTimeModelBinderProvider());
});
于 2022-02-25T08:23:26.677 回答