如题,当CultureInfo是俄文(ru-RU)时,浮点数中的点是用逗号表达的,如1.1会显示成1,1,造成很多的麻烦,当然如果全系统中全部采纳逗号作为浮点也没问题,只要用户接受就可以,但有时需要继续用点号,那么解决办法如下。
1. 修改DefaultThreadCurrentCulture
我们知道CultureInfo.CurrentCulture静态变量是跟踪线程的,每个现场都有独立的CultureInfo.CurrentCulture值,它会决定当前线程的文化区域,包括时间、数字等的显示格式,这一步处理就会解决大部分浮点是逗号的问题。
var cultureInfo = new CultureInfo("ru-RU");
cultureInfo.NumberFormat.NumberDecimalSeparator = ".";
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.ReadOnly(cultureInfo);
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.ReadOnly(cultureInfo);
2. aspnetcore中间件修改CultureInfo
这一步是今天想写这个文章的原因,因为找了很长时间才发现是这里导致的问题,虽然经过上面一步的修改,我们大部分业务代码不会再用逗号了,但是当我们在aspnetcore中启用了RequestLocalizationMiddleware,就会导致例如返回json时浮点数tostring或者拼接等还是逗号的问题。
修改的办法也很简单,启动时加如下代码就可以。
services.Configure<RequestLocalizationOptions>(options=>{
var cultures = options.SupportedCultures.Where(e => e.NumberFormat.NumberDecimalSeparator != ".").ToList();
foreach(var c in cultures)
{
c.NumberFormat.NumberDecimalSeparator = ".";
}
});
原因是因为aspnetcore中的这个中间件代码导致的,为了说明问题简化了代码(代码来自微软官方github),注意Invoke和SetCurrentThreadCulture方法是关键问题所在。
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Localization;
/// <summary>
/// Enables automatic setting of the culture for <see cref="HttpRequest"/>s based on information
/// sent by the client in headers and logic provided by the application.
/// </summary>
public class RequestLocalizationMiddleware
{
private const int MaxCultureFallbackDepth = 5;
private readonly RequestDelegate _next;
private readonly RequestLocalizationOptions _options;
private readonly ILogger _logger;
/// <summary>
/// Creates a new <see cref="RequestLocalizationMiddleware"/>.
/// </summary>
/// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param>
/// <param name="options">The <see cref="RequestLocalizationOptions"/> representing the options for the
/// <see cref="RequestLocalizationMiddleware"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> used for logging.</param>
public RequestLocalizationMiddleware(RequestDelegate next, IOptions<RequestLocalizationOptions> options, ILoggerFactory loggerFactory)
{
ArgumentNullException.ThrowIfNull(options);
_next = next ?? throw new ArgumentNullException(nameof(next));
_logger = loggerFactory?.CreateLogger<RequestLocalizationMiddleware>() ?? throw new ArgumentNullException(nameof(loggerFactory));
_options = options.Value;
}
/// <summary>
/// Invokes the logic of the middleware.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
/// <returns>A <see cref="Task"/> that completes when the middleware has completed processing.</returns>
public async Task Invoke(HttpContext context)
{
ArgumentNullException.ThrowIfNull(context);
var requestCulture = _options.DefaultRequestCulture;
...
if (_options.RequestCultureProviders != null)
{
foreach (var provider in _options.RequestCultureProviders)
{
...
CultureInfo? cultureInfo = null;
CultureInfo? uiCultureInfo = null;
if (_options.SupportedCultures != null)
{
cultureInfo = GetCultureInfo(
cultures,
_options.SupportedCultures,
_options.FallBackToParentCultures);
if (cultureInfo == null)
{
_logger.UnsupportedCultures(provider.GetType().Name, cultures);
}
}
...
cultureInfo ??= _options.DefaultRequestCulture.Culture;
uiCultureInfo ??= _options.DefaultRequestCulture.UICulture;
var result = new RequestCulture(cultureInfo, uiCultureInfo);
requestCulture = result;
winningProvider = provider;
break;
}
}
...
SetCurrentThreadCulture(requestCulture);
...
await _next(context);
}
private static void SetCurrentThreadCulture(RequestCulture requestCulture)
{
CultureInfo.CurrentCulture = requestCulture.Culture;
CultureInfo.CurrentUICulture = requestCulture.UICulture;
}
...
}