2012-04-24 35 views
13

Ich poste ein Objekt auf einem MVC-Controller. Das Objekt enthält ein Feld namens StartDt und auf dem Client ist es ein JavaScript-Date-Objekt in Ortszeit.Wie ASP.Net MVC-Modellbinder behandeln eingehende Datum als UTC?

Wenn ich JSON.stringify für das Objekt aufrufen und es mit jQuerys ajax-Methode an den Server senden, kann ich in Firebug sehen, dass eine ISO-Zeichenfolge wie "1900-12-31T13: 00: 00.000" an den Server gesendet wird Z "was ich glaube, sollte die Ortszeit im UTC-Format sein.

Wenn ich aber das DateTime-Feld in meinem Controller anschaue, sieht es so aus, als wäre es zurück zur lokalen Zeit und nicht UTC. Wie kann ich das beheben?

Ich möchte die UTC-Version des Datums, die vom Client kam, speichern.

Antwort

3

Möglicherweise müssen Sie die DateTime.ToUniversalTime() -Methode verwenden, um die UTC-Zeit zurückzuerhalten.

+7

Diese ist die Art von Sache, die besser von der Infrastruktur behandelt werden würde, so dass wir nicht darüber nachdenken müssen * Wir behandeln jedes Date * DateTime. –

+0

@DavidBoike: http://www.martin-brennan.com/custom-utc-datetime-model-binding-mvc/ –

4

Ich habe dieses kleine Attribut erstellt.

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

Das wird also Daten verlassen, die nicht angegeben sind oder bereits Utc allein. Sie können es den gesamten Controller anwenden.

+0

Was passiert, wenn wir eine benutzerdefinierte Klasse mit der Eigenschaft DateTime übergeben? IsAssignableFrom (typeof (DateTime)) sollte nicht funktionieren. –

+1

Yeh das funktioniert nur für Daten, die direkte Parameter für die Aktion sind. Eine robustere Alternative wäre wahrscheinlich, Modellbindung zu verwenden. Siehe den Ordner unten und auch meinen Kommentar dazu. – Sam

+0

Warum die Downvotes? Dies ist eine gültige Alternative zum Schreiben eines Modellbinders. – Sam

16

fand ich einen Kern auf Google mit Code für ein ISO 8601 konformen DateTime Model Binder, und es dann wie folgt geändert:

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

ich der Kern-Code zu restriktiv ist - es will 6 Dezimalstellen auf Sekunden oder akzeptiert den Zeitstempel nicht. Dies verwendet TryParse anstelle von TryParseExact, so dass es eine Vielzahl von Zeitstempeltypen technisch akzeptiert. Der wichtige Teil ist, dass das DateTimeStyles.RoundtripKind verwendet wird, um die Zeitzone zu berücksichtigen, die durch das Z impliziert wird. Dies ist also technisch gesehen keine ISO 8601-spezifische Implementierung mehr.

Sie könnten dann diese in die Pipeline MVC Haken mit einem Modell Binder Attribute oder mit diesem Code-Schnipsel in einem App_Start:

var dateTimeBinder = new DateTimeBinder(); 

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

Sie müssen den Datetime-Wert selbst nicht analysieren. Wie in der Frage angegeben, ist das Problem nicht das Parsen des Datums (es analysiert nur gut) das Verhalten des Standardmodellbinders, um das analysierte UTD-Datum in das Server-lokale Datum zu konvertieren. Ich stimme zu, dass ein Modellbinder wahrscheinlich besser passt als ein Aktionsfilter - jedoch muss er nur den Wert aus dem base.BindModel-Aufruf abrufen, prüfen, ob der Wert Kind = Local ist, ihn in Utc konvertieren und zurückgeben. – Sam

+1

Schön, ich frage mich, warum .NET ISO-Datumsangaben nicht standardmäßig mit 'DateTimeStyles.RoundtripKind' analysiert. –

+0

Ohne das vollständige Bild zu kennen, würde ich sagen, dass dies der Standard sein sollte. Jetzt, wenn ich Strings in ISO 8601 übergebe, werden sie korrekt analysiert, und Konvertierungen in die lokale Serverzeit sind ebenfalls korrekt. – beruic

2

Dieses Problem in ASP.NET Core 2.0 weiterhin besteht. Mit dem folgenden Code wird das Problem gelöst, indem die grundlegenden und erweiterten ISO 8601-Formate unterstützt werden und der Wert ordnungsgemäß beibehalten und DateTimeKind richtig eingestellt wird. Dies stimmt mit dem Standardverhalten von JSON.Net überein, sodass das Modellbindungsverhalten mit dem Rest des Systems übereinstimmt.

Fügen Sie zunächst das folgende Modell Binder:

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

das folgende Modell Binder Anbieter Dann fügen:

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

dann den Provider in Ihrer Startup.cs Datei registrieren:

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

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

     ... 
    } 
} 
+0

Das ist großartig. Es sieht so aus, als ob bei 'ConfigureServices' die Reihenfolge der 'ModelBinderProviders' von Bedeutung ist. Ich gehe davon aus, dass Sie eher einen Insert als einen Add zur Collection machen. –