Change default format for DateTime parsing in ASP.NET Core

67,776

Solution 1

Had the same problem. While passing DateTime in request body works fine (because Json converter handles this staff), passing DateTime in query string as a parameter has some culture issues.

I did not like the "change all requests culture" approach, bacause this could have impact on other type's parsing, which is not desirable.

So my choise was to override the default DateTime model binding using IModelBinder: https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding

What I did:

1) Define custom binder (c# 7 syntax for 'out' parameter is used):

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) Define provider for your binder:

 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) And finally, register your provider to be used by ASP.NET Core:

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

Now your DateTime will be parsed as expected.

Solution 2

I wanted to format the dates in my responses and I did the following in ConfigureServices method:

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

Hope that helps.

Solution 3

Consider using a custom TypeConverter for your datetime (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);
   }
}

and use TypeConverter attribute on your property:

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

Update

Based on my experience and thanks to this answer and @zdeněk comment, TypeConverter attribute do not work and you should register TypeConverter in Startup.cs:

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

Solution 4

Try setting the culture manually in your web.config

<configuration>
   <system.web>    
      <globalization culture="de-DE" uiCulture="de-DE"/>
   </system.web>
</configuration>

EDIT: Since i just realized this is Core, you can do it this way in StartUp.Configure:

var cultureInfo = new CultureInfo("de-DE");
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;

Solution 5

MVC has always used InvariantCulture for route data and query strings (parameters that go in the URL). The reason behind it is that URLs in localized application must be universal. Otherwise, one url can provide different data depending on the user locale.

You can replace the query and route ValueProviderFactories with your own that respect current culture (or use method="POST" in forms)

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. It is reasonable behavior, but I don't understand why documentation don't cover this very important thing.

Share:
67,776
Lion
Author by

Lion

Updated on September 28, 2020

Comments

  • Lion
    Lion over 3 years

    I get a Date in an ASP.NET Core Controller like this:

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

    The framework is able to parse the date, but only in English format. When I pass 04.12.2017 as date parameter, I mean the 4th of december 2017. This would get parsed as english date, so my date object gets the value 12th of April 2017. I tried adding german only using this article and also this, but without success.

    What needs to be done that ASP.NET Core automatically parse dates in the correct German format?

    Update I Tried to set the 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;
    });
    

    Still not working. I call example.com/Test?date=12.04.2017 and got this in my debugger:

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