Изменить формат по умолчанию для синтаксического анализа 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/...