web-dev-qa-db-fra.com

Modifier le format par défaut pour l'analyse DateTime dans ASP.NET Core

Je reçois une date dans un contrôleur ASP.NET Core comme ceci:

public class MyController:Controller{
    public IActionResult Test(DateTime date) {

    }
}

Le framework est capable d’analyser la date, mais uniquement au format anglais. Quand je passe 04.12.2017 comme paramètre de date, je parle du 4 décembre 2017. Cela sera analysé comme une date anglaise, donc mon objet date aura la valeur le 12 avril 2017. J'ai essayé d'ajouter de l'allemand uniquement avec this article et aussi this , mais sans succès. 

Qu'est-ce qui doit être fait pour qu'ASP.NET Core analyse automatiquement les dates dans le bon format allemand?

Update J'ai essayé de définir les options RequestLocalizationOptions

services.Configure<RequestLocalizationOptions>(opts =>
{
    var supportedCultures = new[]
    {
        new CultureInfo("de-DE"),
    };

    opts.DefaultRequestCulture = new RequestCulture("de-DE");
    // Formatting numbers, dates, etc.
    opts.SupportedCultures = supportedCultures;
    // UI strings that we have localized.
    opts.SupportedUICultures = supportedCultures;
});

Ne fonctionne toujours pas. J'appelle example.com/Test?date=12.04.2017 et je reçois ceci dans mon débogueur:

public IActionResult Test(DateTime date) {
    string dateString = date.ToString("d"); // 04.12.2016
    string currentDateString = DateTime.Now.ToString("d"); // 14.01.2016
    return Ok();
}
9
Lion

Avait le même problème. Bien que transmettre DateTime dans le corps de la requête fonctionne correctement (car le convertisseur Json gère cette portée), transmettre DateTime dans la chaîne de requête en tant que paramètre pose certains problèmes de culture.

Je n'aimais pas l'approche "changer la culture de toutes les requêtes", car cela pourrait avoir un impact sur l'analyse syntaxique d'autres types, ce qui n'est pas souhaitable.

Mon choix a donc été de remplacer la liaison de modèle DateTime par défaut avec IModelBinder: https://docs.Microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding

Ce que j'ai fait:

1) Définir le classeur personnalisé (la syntaxe c # 7 du paramètre 'out' est utilisée):

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

        // Try to fetch the value of the argument by name
        var modelName = bindingContext.ModelName;
        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
        if (valueProviderResult == ValueProviderResult.None)
            return Task.CompletedTask;

        bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

        var dateStr = valueProviderResult.FirstValue;
        // Here you define your custom parsing logic, i.e. using "de-DE" culture
        if (!DateTime.TryParse(dateStr, new CultureInfo("de-DE"), DateTimeStyles.None, out DateTime date))
        {
            bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, "DateTime should be in format 'dd.MM.yyyy HH:mm:ss'");
            return Task.CompletedTask;
        }

        bindingContext.Result = ModelBindingResult.Success(date);
        return Task.CompletedTask;
    }
}

2) Définir le fournisseur pour votre classeur:

 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 new DateTimeModelBinder();
        }

        return null;
    }
}

3) Et finalement, enregistrez votre fournisseur pour qu'il soit utilisé par ASP.NET Core:

services.AddMvc(options =>
{
    options.ModelBinderProviders.Insert(0, new DateTimeModelBinderProvider());
});

Maintenant, votre DateTime sera analysé comme prévu.

9
Igor Fedchenko

Je voulais formater les dates dans mes réponses et j'ai procédé comme suit dans la méthode ConfigureServices:

services.AddMvc()
.AddJsonOptions(options =>
{
    options.SerializerSettings.DateFormatString = "mm/dd/yy, dddd";
});

J'espère que cela pourra aider.

6
PayamGerami

MVC a toujours utilisé InvariantCulture pour les données de routage et les chaînes de requête (paramètres inclus dans l'URL). La raison en est que les URL dans les applications localisées doivent être universelles. Sinon, une URL peut fournir des données différentes selon les paramètres régionaux de l'utilisateur.

Vous pouvez remplacer la requête et acheminer ValueProviderFactories par la vôtre qui respecte la culture actuelle (ou utiliser method="POST" dans les formulaires). 

public class CustomValueProviderFactory : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        var query = context.ActionContext.HttpContext.Request.Query;
        if (query != null && query.Count > 0)
        {
            var valueProvider = new QueryStringValueProvider(
                BindingSource.Query,
                query,
                CultureInfo.CurrentCulture);

            context.ValueProviders.Add(valueProvider);
        }

        return Task.CompletedTask;
    }
}

services.AddMvc(opts => {
    // 2 - Index QueryStringValueProviderFactory
    opts.ValueProviderFactories[2] = new CustomValueProviderFactory(); 
})

P.S. C'est un comportement raisonnable, mais je ne comprends pas pourquoi la documentation ne couvre pas cette chose très importante.

2
justserega

Pensez à utiliser une TypeConverter personnalisée pour votre date/heure ( Source ):

using System;
using System.ComponentModel;
using System.Globalization;
using System.Drawing;

public class DeDateTimeConverter : TypeConverter {
   // Overrides the CanConvertFrom method of TypeConverter.
   // The ITypeDescriptorContext interface provides the context for the
   // conversion. Typically, this interface is used at design time to 
   // provide information about the design-time container.
   public override bool CanConvertFrom(ITypeDescriptorContext context, 
      Type sourceType) {

      if (sourceType == typeof(string)) {
         return true;
      }
      return base.CanConvertFrom(context, sourceType);
   }
   // Overrides the ConvertFrom method of TypeConverter.
   public override object ConvertFrom(ITypeDescriptorContext context, 
      CultureInfo culture, object value) {
      if (value is string) {
         if (DateTime.TryParse(((string)value), new CultureInfo("de-DE") /*or use culture*/, DateTimeStyles.None, out DateTime date))
             return date;
      }
      return base.ConvertFrom(context, culture, value);
   }
}

et utilisez l'attribut TypeConverter sur votre propriété:

[TypeConverter(typeof(DeDateTimeConverter))]
public DateTime CustomDateTime { get; set; }

Mettre à jour

D'après mon expérience et grâce à cette réponse et @ zdeněk, l'attribut TypeConverter ne fonctionne pas et vous devez enregistrer TypeConverter dans Startup.cs:

TypeDescriptor.AddAttributes(typeof(DateTime), new TypeConverterAttribute(typeof(DeDateTimeConverter)));
0
KiNG

Si cela ne vous dérange pas d'utiliser la méthode générique StatusCode pour effectuer cet appel, vous pouvez effectuer les opérations suivantes:

internal IActionResult CreateResponse(int code, object content = null)
    {
        Type t = content?.GetType();
        bool textContent = t == typeof(string) || t == typeof(bool);
        //
        JsonSerializerSettings dateFormatSettings = new JsonSerializerSettings
        {

            DateFormatString = myDateFormat
        };

        string bodyContent = content == null || string.IsNullOrWhiteSpace(content + "")
                    ? null
                    : textContent
                        ? content + ""
                        : JsonConvert.SerializeObject(content, dateFormatSettings);

        ObjectResult or = base.StatusCode(code, bodyContent);
        string mediaType = 
                    !textContent
                        ? "application/json"
                        : "text/plain";
        or.ContentTypes.Add(new MediaTypeHeaderValue(mediaType));
        return or;
    }

Vous pouvez ajouter ceci à une classe de base et l'appeler comme suit:

return base.CreateResponse(StatusCodes.Status200OK, new { name = "My Name", age = 23});

C’est à vous de décider si vous voulez créer vos propres méthodes OK, BadRequest, etc., mais pour moi, cela fonctionne et j’espère que cela aidera tout le monde. Vous pouvez même utiliser le code int par défaut = 200, si la plupart de vos demandes sont des requêtes GET. Ce code suppose que vous souhaitiez soit répondre avec une chaîne, un booléen ou un objet personnalisé, mais vous pouvez facilement gérer toutes les primitives en vérifiant Type.GetTypeInfo (). IsPrimitive et même en vérifiant décimale, chaîne, DateTime, TimeSpan, DateTimeOffset , ou Guid.

0
pqsk

J'ai eu le même problème et presque me suis fâché. J'ai tout essayé sans succès. J'ai d'abord trouvé une solution pour résoudre une partie de mon problème:

Solution de contournement:

string data1 
string horainicio 
string horafim

var ageData = new AgendaData();
var user = await _userManager.GetUserAsync(User);
string usuario = user.Id;
int empresa = user.IdEmpresa;
int Idprospect = Convert.ToInt32(prospect);
int minutos = 0;           
var tipoAgenda = TipoAgenda.Contato;

var provider = CultureInfo.InvariantCulture;
provider = new CultureInfo("en-US");            
string formato = "dd/MM/yyyy HH:mm";

var dataInicio = DateTime.ParseExact(data1 + " " + horainicio, formato, provider);
var dataFim = DateTime.ParseExact(data1 + " " + horafim, formato, provider);           
var dataAlerta = dataInicio.AddMinutes(-minutos);

Mais, de cette façon, je dois toujours définir invariantculture pour tout mon datetime. J'ai trouvé la solution définissant ma culture à l'adresse configure on startup.cs.

Définir Culture sur startup.cs

 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, CRMContext context)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            //Fixar Cultura para en-US
            RequestLocalizationOptions localizationOptions = new RequestLocalizationOptions
            {
                SupportedCultures = new List<CultureInfo> { new CultureInfo("en-US") },
                SupportedUICultures = new List<CultureInfo> { new CultureInfo("en-US") },
                DefaultRequestCulture = new RequestCulture("en-US")
            };

            app.UseRequestLocalization(localizationOptions);      
            app.UseStaticFiles();
            app.UseIdentity();

            // Add external authentication middleware below. To configure them please see https://go.Microsoft.com/fwlink/?LinkID=532715

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });

            context.Database.EnsureCreated();
        }

J'espère que cela vous aidera.

0
Rogerio Azevedo
              using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Threading.Tasks;
        using Microsoft.AspNetCore.Builder;
        using Microsoft.AspNetCore.Hosting;
        using Microsoft.Extensions.Configuration;
        using Microsoft.Extensions.DependencyInjection;
        using Microsoft.Extensions.Logging;
        using Microsoft.Extensions.Options;
        using System.Globalization;
        using Microsoft.AspNetCore.Localization;

        namespace coreweb
        {
            public class Startup
            {
                public Startup(IHostingEnvironment env)
                {
                    var builder = new ConfigurationBuilder()
                        .SetBasePath(env.ContentRootPath)
                        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                        .AddEnvironmentVariables();

                    if (env.IsDevelopment())
                    {
                        // This will Push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
                        builder.AddApplicationInsightsSettings(developerMode: true);
                    }
                    Configuration = builder.Build();
                }

                public IConfigurationRoot Configuration { get; }

                // This method gets called by the runtime. Use this method to add services to the container.
                public void ConfigureServices(IServiceCollection services)
                {
                    // ... previous configuration not shown
                    services.AddMvc();
                    services.Configure<RequestLocalizationOptions>(
                        opts =>
                        {
                            var supportedCultures = new[]
                            {

                        new CultureInfo("de-DE"),
                            };

                            opts.DefaultRequestCulture = new RequestCulture("de-DE");
                    // Formatting numbers, dates, etc.
                    opts.SupportedCultures = supportedCultures;
                    // UI strings that we have localized.
                    opts.SupportedUICultures = supportedCultures;
                        });
                }

                // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
                public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
                {
                    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
                    loggerFactory.AddDebug();

                 //   app.UseApplicationInsightsRequestTelemetry();

                    if (env.IsDevelopment())
                    {
                        app.UseDeveloperExceptionPage();
                        app.UseBrowserLink();
                    }
                    else
                    {
                        app.UseExceptionHandler("/Home/Error");
                    }

                  //  app.UseApplicationInsightsExceptionTelemetry();

                    app.UseStaticFiles();

                    var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
                    app.UseRequestLocalization(options.Value);



                    app.UseMvc(routes =>
                    {
                        routes.MapRoute(
                            name: "default",
                            template: "{controller=Home}/{action=Index}/{id?}");
                    });
                }
            }
        }
0
Yashveer Singh