Изменить формат по умолчанию для синтаксического анализа DateTime в ASP.NET Core

Я получаю дату в ASP.NET Core Controller следующим образом:

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

    }
}

Фреймворк умеет разбирать дату, но только в английском формате. Когда я передаю 04.12.2017 в качестве параметра даты, я имею в виду 4 декабря 2017 года. Это будет обрабатываться как английская дата, поэтому мой объект даты получает значение 12 апреля 2017 года. Я попытался добавить немецкий только с использованием эта статья, а также эта, но безуспешно.

Что нужно сделать, чтобы ASP.NET Core автоматически анализировал даты в правильном немецком формате?

Обновление Я пытался установить 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;
});

Все еще не работает. Я звоню example.com/Test?date=12.04.2017 и получаю это в моем отладчике:

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

Ответ 1

Имела та же проблема. При передаче DateTime в тело запроса работает отлично (поскольку Json-конвертер обрабатывает этот персонал), передача DateTime в строке запроса в качестве параметра имеет некоторые проблемы с культурой.

Мне не понравился подход "изменить все запросы", так как это могло повлиять на разбор другого типа, что нежелательно.

Поэтому мой выбор состоял в том, чтобы переопределить привязку модели DateTime по умолчанию, используя IModelBinder: https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding

Что я сделал:

1) Определить настраиваемый связующий (используется синтаксис С# 7 для параметра "out"):

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) Определите поставщика для вашего связующего:

 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) И, наконец, зарегистрируйте поставщика, который будет использоваться в ASP.NET Core:

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

Теперь ваш DateTime будет анализироваться, как ожидалось.

Ответ 2

Я хотел отформатировать даты в своих ответах, и я сделал следующее в методе ConfigureServices:

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

Надеюсь, что это поможет.

Ответ 3

MVC всегда использовал InvariantCulture для данных маршрута и строк запроса (параметры, которые идут в URL-адресе). Причина этого в том, что URL-адреса в локализованном приложении должны быть универсальными. В противном случае один URL-адрес может предоставлять разные данные в зависимости от локали пользователя.

Вы можете заменить запрос и маршрут ValueProviderFactories своими собственными, которые уважают текущую культуру (или используют method="POST" в формах)

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

PS Это разумное поведение, но я не понимаю, почему документация не охватывает эту очень важную вещь.

Ответ 4

Рассмотрите возможность использования настраиваемого TypeConverter для вашего времени и времени (Источник):

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

и используйте атрибут TypeConverter для вашего свойства:

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

Обновить

Основываясь на моем опыте и благодаря этому ответ и @Зденек комментарий, TypeConverter атрибут не работает, и вы должны зарегистрироваться TypeConverter в Startup.cs:

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

Ответ 5

Лучше отправлять дату с фронта на контроллер в формате ISO: "гггг-мм-дд"

https://www.w3schools.com/js/js_date_formats.asp

Любая сторона сервера с любой культурой будет правильно понимать этот формат даты.

Итак, я использую отправку так:

const dateStart = new Date();
$.post("localhost:4200/start", { dateStart: dateStart.toISOString() },
    function(data) {
        console.log("Started!");
    });

Ответ 6

Попробуйте настроить культуру вручную в web.config

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

EDIT: Поскольку я только понял, что это Core, вы можете сделать это в StartUp.Configure:

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

Ответ 7

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

Ответ 8

Если вы не против использования общего метода StatusCode для выполнения этого вызова, вы можете сделать что-то вроде следующего:

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

Вы можете добавить это в базовый класс и вызвать его так:

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

Это зависит от вас, если вы хотите создать свои собственные методы Ok, BadRequest и т.д., но для меня это работает, и я надеюсь, что это поможет кому-то еще. Вы даже можете использовать код по умолчанию = 200, если большинство ваших запросов - GET. Этот код предполагает, что вы либо хотите ответить строкой, логическим или пользовательским объектом, но вы можете легко обрабатывать все примитивы, проверяя Type.GetTypeInfo(). IsPrimitive и даже выполняет некоторые проверки на десятичные значения, строку, DateTime, TimeSpan, DateTimeOffset, или Guid.

Ответ 9

У меня было такое же проблемное объявление, что я почти разозлился. Я все время пробовал без сучков. Сначала я нашел обходное решение для решения части моей проблемы:

Обход проблемы:

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

Но, таким образом, мне всегда нужно устанавливать инвариантную культуру ко всему моему дате. Я нашел решение, устанавливающее мою культуру в configure на startup.cs.

Установить культуру в 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();
        }

Надеюсь, что это поможет вам.

Ответ 10

DateTime dt = DateTime.ParseExact(dateString, "ddMMyyyy", CultureInfo.InvariantCulture);
dt.ToString("yyyyMMdd");

Согласно fooobar.com/info/58113/...