2012-04-24 40 views
13

我正在發佈一個對象到MVC控制器。該對象包含一個名爲StartDt的字段,在客戶端它是一個本地時間的javascript Date對象。如何使ASP.Net MVC模型綁定器將傳入日期視爲UTC?

當我在對象上調用JSON.stringify並使用jQuery的ajax方法將它POST給服務器時,我可以在Firebug中看到發送到服務器的是ISO字符串,如「1900-12-31T13:00:00.000 Z「,我相信應該是UTC格式的當地時間。

當我查看我的控制器中的DateTime字段時,它看起來像回到本地時間而不是UTC。我怎樣才能解決這個問題?

我想存儲來自客戶端的日期的UTC版本。

回答

3

您可能必須使用DateTime.ToUniversalTime()方法來取回UTC時間。

+7

這是基礎設施可以更好地處理的事情,所以我們不必考慮它*每一次*我們處理日期時間。 – 2012-11-12 22:09:14

+0

@DavidBoike:http://www.martin-brennan.com/custom-utc-datetime-model-binding-mvc/ – 2015-11-04 02:39:29

4

我創建了這個小屬性。

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。您可以將其應用於整個控制器。

+0

如果我們傳遞一個內部具有DateTime屬性的自定義類,該怎麼辦? IsAssignableFrom(typeof(DateTime))不應該工作。 – 2013-11-25 11:06:12

+1

這隻適用於直接參數的日期。更強大的選擇可能是使用模型綁定。看到下面的聯編程序,也是我對它的評論。 – Sam 2013-11-26 03:24:05

+0

爲什麼downvotes?這是編寫模型聯編程序的有效替代方法。 – Sam 2014-07-15 02:19:22

16

我發現了一個要點在谷歌的代碼爲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); 
    } 
} 

我相信要點代碼過於侷限 - 它希望在秒或者6位小數不會接受時間戳。這使用TryParse代替TryParseExact,所以它在技術上會接受很多時間戳類型。重要的部分是它使用DateTimeStyles.RoundtripKind來尊重Z所暗示的時區。所以這在技術上不再是ISO 8601的具體實現。

然後,您可以掛鉤此爲MVC管道與模型綁定屬性,或者在這個片段在App_Start:在ASP.NET核2.0

var dateTimeBinder = new DateTimeBinder(); 

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

您無需解析日期時間值本身。正如問題所述,問題不在於解析日期(它解析得很好),而是將默認模型聯編程序將解析的utc日期轉換爲服務器本地日期時間的行爲。我同意一個模型綁定器可能比動作過濾器更合適 - 不過它需要的是從base.BindModel調用中獲取值,檢查該值是否爲Kind = Local,然後將其轉換爲Utc並返回。 – Sam 2012-11-13 10:24:55

+1

不錯,我想知道爲什麼.NET在默認情況下不會使用'DateTimeStyles.RoundtripKind'解析ISO日期。 – 2015-04-14 21:35:58

+0

不知道完整的圖片,我會說這應該是默認的。現在,當我在ISO 8601中提交字符串時,它們被正確解析,並且轉換爲服務器本地時間也是正確的。 – beruic 2016-04-14 10:42:37

2

這個問題仍然存在。以下代碼將解決它,支持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()); 

     ... 
    } 
} 
+0

這太好了。看起來在執行'ConfigureServices'時,'ModelBinderProviders'的順序很重要,我假定你爲什麼要做一個Insert,而不是添加到集合中。 – 2018-02-06 13:29:30