web-dev-qa-db-fra.com

Passer UTC DateTime à la méthode HttpGet de l'API Web entraîne l'heure locale

J'essaie de passer une date UTC en tant que paramètre de chaîne de requête à une méthode d'API Web. L'URL ressemble à

/api/order?endDate=2014-04-01T00:00:00Z&zoneId=4

La signature de la méthode ressemble à

[HttpGet]
public object Index(int zoneId, DateTime? endDate = null)

La date arrive sous 31/03/2014 8:00:00 PM Mais j'aimerais qu'elle entre sous la forme 01/04/2014 12:00:00 AM

Mon JsonFormatter.SerializerSettings Ressemble à ceci

new JsonSerializerSettings
{
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
    DateTimeZoneHandling = DateTimeZoneHandling.Utc,
    DateFormatHandling = DateFormatHandling.IsoDateFormat
};

EDIT # 1: J'ai remarqué que lorsque je POST 2014-04-01T00:00:00Z Il sera sérialisé au type UTC DateTime en C #. Cependant, j'ai trouvé un moyen de faire de la fonction endDate.Value.ToUniversalTime() pour le convertir bien que je trouve étrange comment cela fonctionne pour un POST mais pas un GET.

39
Ryan

La valeur du paramètre de chaîne de requête que vous envoyez 2014-04-01T00:00:00Z Est l'heure UTC. Ainsi, la même chose est traduite en une heure basée sur votre horloge locale et si vous appelez ToUniversalTime(), elle est reconvertie en UTC.

Alors, quelle est exactement la question? Si la question est pourquoi cela se produit-il s'il est envoyé en tant que chaîne de requête mais pas lorsqu'il est publié dans le corps de la demande, la réponse à cette question est que l'API Web ASP.NET lie le chemin URI, la chaîne de requête, etc. en utilisant liaison de modèle et le corps utilisant liaison de paramètres. Pour ce dernier, il utilise un formateur de média. Si vous envoyez JSON, le formateur de média JSON est utilisé et il est basé sur JSON.NET.

Puisque vous avez spécifié DateTimeZoneHandling.Utc, Il utilise ce paramètre et vous obtenez le type date-heure que vous souhaitez. BTW, si vous changez ce paramètre en DateTimeZoneHandling.Local, Alors vous verrez le même comportement que la liaison de modèle.

35
Badri

Si vous souhaitez que la conversion soit transparente, vous pouvez utiliser un TypeConverter personnalisé:

public sealed class UtcDateTimeConverter : DateTimeConverter
{
    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        return ((DateTime)base.ConvertFrom(context, culture, value)).ToUniversalTime();
    }
}

et le câbler en utilisant:

TypeDescriptor.AddAttributes(typeof(DateTime), new TypeConverterAttribute(typeof(UtcDateTimeConverter)));

Ensuite, le paramètre de chaîne de requête sera instancié comme DateTimeKind.Utc.

26
Sean Fausett

J'ai fini par utiliser la méthode ToUniversalTime() lorsque les paramètres entrent.

13
Ryan

Donc, pour ceux d'entre vous qui ne souhaitent pas remplacer la conversion chaîne à date dans votre application entière, et ne veulent pas non plus avoir à se rappeler de modifier chaque méthode qui prend un paramètre de date, voici comment procéder pour un Projet d'API Web.

En fin de compte, les instructions générales viennent d'ici:

https://docs.Microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api#model-binders

Voici les instructions spécialisées pour ce cas:

  1. Dans votre classe "WebApiConfig", ajoutez ce qui suit:

        var provider = new SimpleModelBinderProvider(typeof(DateTime),new UtcDateTimeModelBinder());
        config.Services.Insert(typeof(ModelBinderProvider), 0, provider);
    
  2. Créez une nouvelle classe appelée UtcDateTimeModelBinder:

    public class UtcDateTimeModelBinder : IModelBinder
    {
        public bool BindModel(HttpActionContext actionContext,
            ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType != typeof(DateTime)) return false;
    
            var val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            if (val == null)
            {
                return false;
            }
    
            var key = val.RawValue as string;
            if (key == null)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName,
                    "Wrong value type");
                return false;
            }
    
            DateTime result;
            if (DateTime.TryParse(key, out result))
            {
                bindingContext.Model = result.ToUniversalTime();
                return true;
            }
    
            bindingContext.ModelState.AddModelError(bindingContext.ModelName,
                "Cannot convert value to Utc DateTime");
            return false;
        }
    }
    
1
Reginald Blue