ASP.NET Core中更改DateTime解析的默认格式

36

我在 ASP.NET Core 控制器中获取日期的方法如下:

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

    }
}
该框架能够解析日期,但仅支持英文格式。当我将04.12.2017作为日期参数传递时,我指的是2017年12月4日。它会被解析为英文日期,因此我的日期对象的值是2017年4月12日。我尝试添加德语,使用这篇文章这篇文章,但没有成功。
需要做什么才能使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();
}

你是如何传递需要解析的日期的?请展示你所使用的URL格式。 - Scott Chamberlain
正如我所说:使用Test?date=12.04.2017参数进行调用,将在Test函数内得到04.12.2017作为日期对象。我期望得到未修改的日期,即12.04.2017,而不是04.12.2017。 - Lion
将其作为字符串传递,然后使用 DateTime.ParseExact 函数以你想要的精确方式进行解析。 - Lasse V. Karlsen
2
为了明确一点(以便于讨论),2017年4月12日是美国英语格式,而12月4日则是英式英语格式,更准确地说是英国英语格式。虽然英国使用/作为分隔符,而不是.。 - Chris
嗨@Lion,我贴了一个asp.net核心项目类型的Startup.cs,并设置了区域信息。当我查看控制器中的当前区域时,它被设置为DE。如果这有帮助,请尝试此操作。我尝试过,它以DE格式显示日期。我没有在控制器参数中尝试,但在Immediate窗口中尝试了一下。 - Yashveer Singh
你可以在这个博客上阅读更多相关内容:http://andrewlock.net/adding-localisation-to-an-asp-net-core-application/,我也找到了相应的Git URL:https://github.com/aspnet/Entropy/blob/dev/samples/Localization.StarterWeb/Startup.cs。 - Yashveer Singh
10个回答

24

我曾经遇到同样的问题。在请求正文中传递DateTime是可行的(因为Json转换器可以处理此类情况),但将DateTime作为查询字符串参数进行传递会涉及一些文化问题。

我不喜欢“更改所有请求的文化”方法,因为这可能会影响其他类型的解析,这是不希望看到的。

所以我的选择是使用IModelBinder覆盖默认的DateTime模型绑定:https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding

我的做法:

1) 定义自定义绑定器(使用c# 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) 为您的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) 最后,将您的提供程序注册以供 ASP.NET Core 使用:

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

现在你的 DateTime 将会按预期进行解析。


非常顺利地完成了工作。谢谢! - Alex G.
微软文档建议不要使用自定义模型绑定器将字符串转换为其他类型作为最佳实践:通常不应该用于将字符串转换为自定义类型,TypeConverter通常是更好的选择。请考虑使用TypeConverter。 - Shahab
谢谢,对我也有用。只是需要注意的是,上面的模型绑定器无法很好地处理DateTime?空值。 - Velyo

15

考虑使用自定义的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; }

更新

根据我的经验和感谢 这个答案 以及 @zdeněk 的评论,TypeConverter 属性不起作用,您应该在 Startup.cs 中注册 TypeConverter:

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

1
在我看来,最佳答案是 - reven

13

我想要在响应中格式化日期,我在ConfigureServices方法中进行了以下操作:

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

希望这可以帮助到你。


3
谢谢。如果您只想将其用于特定情况,也可以在转换时指定。 var deserialized = JsonConvert.DeserializeObject(myJsonstring, new JsonSerializerSettings { DateFormatString = "dd/MM/yyyy" }); - Jonny

4

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

顺便说一下,这是合理的行为,但我不明白为什么文档没有涵盖这个非常重要的事情。


感谢InvariantCulture的解释。 - A K

4

尝试在您的 web.config 文件中手动设置语言文化。

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

编辑:由于我刚刚意识到这是Core,因此您可以在StartUp.Configure中以这种方式完成:

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

5
这个问题是关于Asp.NET Core的。 - Pawel
1
我已经尝试设置 CultureInfo.DefaultThreadCurrentCultureCultureInfo.DefaultThreadCurrentUICulture 并在测试操作开始时,它们被正确地设置为 de-DE。但是 ASP.NET Core 不在意,GET 参数的解析错误。 - Lion

0

我曾经遇到过同样的问题,几乎要发疯了。我尝试了所有方法都没有成功。首先,我找到了一个解决部分问题的变通方法:

变通方法:

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

但是,这样我总是需要将InvariantCulture设置为所有我的日期时间。我发现解决方案是在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();
        }

希望这能帮到你。


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

无效。适用于 DateTime.Now 但不适用于从 GET 参数解析的参数。请查看我的编辑,因为它太长了,不能作为评论。 - Lion
哦,让我检查一下你编辑后的答案。只是确认在这种情况下应该是什么正确的结果? - Yashveer Singh
将输入的日期12.04.2017解析为4月12日2014年,而不是像ASP.NET一样解析为2014年12月4日。 - Lion

0

如果您不介意使用通用的 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等方法,那就由你决定。但对我来说,这个方法很有效,希望能帮到其他人。如果大多数请求都是GET请求,你甚至可以默认int code = 200。该代码假设你要么用字符串、布尔值或自定义对象进行响应,但你可以通过检查Type.GetTypeInfo().IsPrimitive并进行一些检查(decimal、string、DateTime、TimeSpan、DateTimeOffset或Guid)轻松处理所有基元。


-1

最好将日期从前端以ISO格式“yyyy-MM-dd”发送到控制器。

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

1
这实际上是有帮助的,而不是实现上述给定的解决方案。谢谢。 - Zafar

-4

3
我知道这个。但这对此处没有帮助,因为 ASP.NET Core 本身会将 GET 参数解析为 DateTime 对象。我不进行解析。像您建议的那样,使用字符串代替 DateTime 作为日期参数并进行解析是一种解决方法。但我想避免这样做,因为 ASP.NET Core 在这方面做得很好,只是格式不正确。 - Lion

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接