解决 .NET Core 在 Linux Container 中获取 CurrentCulture 不正确的问题
背景
在将公司一款基于 .NET Framework 的控制台程序迁移到 .NET Core 3.1 时,发现程序中本地化的部分失效,症状类似于对 Thread.CurrentThread.CurrentCulture.Name
的值进行 Substring()
操作时抛出 ArgumentOutOfRangeException
异常。
该程序在 Windows Container 中工作良好,迁移为 .NET Core 后在我的 Windows 开发机上也运行良好,一旦部署到 K8s 的 Linux 容器中就会出现问题。容器使用的是基于微软官方的 .NET Runtime 3.1 镜像(https://hub.docker.com/_/microsoft-dotnet-runtime/)。
本文按我当时解决此问题的思路记录,从 Windows 开始,挨个环境测试 CurrentThread.CurrentCulture
。
TL;DR 先上结论
.NET Core Runtime 的 Linux 镜像没有设置语言信息,导致通过 CurrentThread.CurrentCulture
获取的 Name
为 String.Empty
。
只需要在生成镜像时为 Linux 设置语言即可,本文末尾附有解决方案。
在 Windows 中获取区域设置
先创建一个名为 CultureTest
的控制台项目看看效果,这里使用 .NET Core 的 LTS 版本 .NET Core 3.1 为例,通过命令行执行:
dotnet new console -o CultureTest --framework netcoreapp3.1
然后进入 CultureTest
文件夹,将生成的 Program.cs
替换为如下内容:
using System;
using System.Globalization;
using System.Linq;
using System.Threading;namespace CultureTest
{class Program{static void Main(){PrintProperty(Thread.CurrentThread.CurrentCulture);Thread.Sleep(TimeSpan.FromDays(1));}private static void PrintProperty(CultureInfo cultureInfo){var printableProperties = cultureInfo.GetType().GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string));foreach (var property in printableProperties){Console.WriteLine($"{property.Name}: {property.GetValue(cultureInfo)}");}}}
}
PrintProperty()
方法主要用途是将CultureInfo
类中所有值类型和string
类型的属性找到,并将我们传入的Thread.CurrentThread.CurrentCulture
对象的这些属性值都打印出来。Thread.Sleep()
是为在后面测试 docker 时用于防止程序运行后直接退出之用。
我在一台区域设置为 中文(简体,中国)
的 Windows 10 PC 上运行上述代码:
dotnet run
可以看见 Name
为 zh-CN
和 Windows 一致。
查看信息后,由于有 Thread.Sleep()
的逻辑,需要通过 Ctrl + C 来停止程序的运行(后面 Linux 和 Docker 中也一样)。
在 Linux 中获取区域信息
我在 WSL(https://docs.microsoft.com/windows/wsl/install-win10) 中安装了 Debian 10,并安装了 .NET Core 3.1 SDK,下面用 Debian 来进行测试。
locale 命令
在 Linux 中,可以使用 locale
命令查看当前语言环境信息:
locale
关注 LANG
的值,现在显示为 en_US.UTF-8
。
locale
命令加上 -a
选项后可以查看可用的语言环境信息:
locale -a
可以看到这个 Debian 除了当前的 en_US.UTF-8
,还支持其它几种语言环境。
通过 CurrentThread 获取
由于是 WSL,可以通过 /mnt
中挂载的 Windows 文件系统,直接导航到上一节创建的项目中,并运行:
cd /mnt/d/projects/CultureTest
dotnet run
可以看见 Name
为 en-US
和 locale
命令得到的一致。
在 Docker 容器中获取区域信息
下面将测试程序放入容器中运行。
发布测试程序
先发布 CultureTest
项目:
dotnet publish -c Release
默认会发布到 .\bin\Release\netcoreapp3.1\publish\
文件夹下,可以使用 dir
(Windows) 或 ls
(Linux) 命令查看发布结果。
创建 Dockerfile
接下来为 CultureTest
生成镜像。
首先在 CultureInfo
项目根目录(.csproj
所在的目录)下创建 Dockerfile
并填入以下内容:
FROM mcr.microsoft.com/dotnet/runtime:3.1COPY ./bin/Release/netcoreapp3.1/publish/ /app/
WORKDIR /app
ENTRYPOINT ["dotnet", "CultureTest.dll"]
这里使用 .NET Core 官方提供的 .NET Runtime 镜像 mcr.microsoft.com/dotnet/runtime:3.1
作为 Runtime
拷贝刚刚发布到
.\bin\Release\netcoreapp3.1\publish\
的程序到容器的/app
文件夹下将容器的工作目录设为
/app
文件夹通过
dotnet CultureTest.dll
命令运行测试项目
生成镜像
在 Dockerfile
所在的目录下执行 docker build
命令生成镜像:
docker build -t culture-test .
-t culture-test
设置镜像的名称为culture-test
不要漏掉最后的
.
运行并查看结果
通过上一步创建的 culture-test
镜像生成一个容器,并查看执行结果:
docker run culture-test
发现 Name
后没有任何内容。
经过测试,CurrentThread.CurrentCulture
不会为 null
,并且 Name
属性的值为 String.Empty
而非 null
。这也是我遇到问题的原因,对 String.Empty
进行了 Substring()
操作,所以抛出了 ArgumentOutOfRangeException
异常,问题重现。
在容器中执行 locale
进入容器,查看 Linux 的语言环境信息:
docker run -d culture-test
docker exec -it [your_container_id] /bin/bash
通过
-d
让程序后台运行(有Thread.Sleep()
在,所以程序不会退出,这样我们就能进入到容器内执行命令),这一步执行后会返回容器 id通过
exec
执行容器里的/bin/bash
发现 locale
命令返回的 LANG
也是空白的。
并且 locale -a
命令返回的 C
和 POSIX
都是默认不含语言的环境。
原因
如果使用过 Linux 的 GUI 安装 Linux,一般会让选择语言和地区,但 mcr.microsoft.com/dotnet/runtime:3.1
以及它基于的 Debian
镜像(https://github.com/dotnet/dotnet-docker/blob/87cbc30052e5dc892313122e26364b5051df905b/src/runtime-deps/3.1/buster-slim/amd64/Dockerfile),都没有设置语言,所以导致我们通过 locale
或是 C# 的 CurrentThread.CurrentCulture
获取到的都是空白的内容。
那么结合上面的信息,要想让依赖于区域语言信息的程序不报错,有两种方案:
修改程序,增加对
CurrentCulture.Name
的判断:如果CurrentCulture.Name == String.Empty
,则为程序设置一个默认 Culture修改运行环境,将默认语言信息设置为需要的值(例如
en-US
)
为容器中的 Linux 设置语言信息
虽然最后我选择的是修改程序,但也来了解一下这种情况如何为容器中的 Linux 设置语言信息。
通过搜索,找到了 StackOverflow 上的提问:How to set the locale inside a Debian/Ubuntu Docker container?(https://stackoverflow.com/questions/28405902/how-to-set-the-locale-inside-a-debian-ubuntu-docker-container),并从中得到了解决方案。
方案一:通过安装 locales-all 包
通过安装 locales
和 locales-all
包,可以把所有支持的语言信息都安装到系统中,再通过环境变量设置需要的语言。
修改 Dockerfile
:
FROM mcr.microsoft.com/dotnet/runtime:3.1# 安装所有支持的语言信息,并设置 en_US.UTF-8 为当前语言
RUN apt-get update
RUN apt-get install -y locales locales-all
ENV LANG en_US.UTF-8COPY ./bin/Release/netcoreapp3.1/publish/ /app/
WORKDIR /app
ENTRYPOINT ["dotnet", "CultureTest.dll"]
方案二:通过安装 locales 包,并修改 locale.gen 文件
修改 Dockerfile
:
FROM mcr.microsoft.com/dotnet/runtime:3.1# 安装 locales 包,并修改 locale.gen 文件,再设置语言
RUN apt-get update
RUN apt-get install -y locales
RUN sed -i -e '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
ENV LANG en_US.UTF-8 COPY ./bin/Release/netcoreapp3.1/publish/ /app/
WORKDIR /app
ENTRYPOINT ["dotnet", "CultureTest.dll"]
安装 locales
后,会生成 /etc/locale.gen
文件,文件内容类似于:
# en_SG ISO-8859-1
# en_SG.UTF-8 UTF-8
# en_US ISO-8859-1
# en_US.ISO-8859-15 ISO-8859-15
# en_US.UTF-8 UTF-8
# en_ZA ISO-8859-1
# en_ZA.UTF-8 UTF-8
# en_ZM UTF-8
# en_ZW ISO-8859-1
# en_ZW.UTF-8 UTF-8
通过 sed
命令:
/en_US.UTF-8
:将包含en_US.UTF-8
字样的行/s
:执行替换/^#
:将行首的#
/
:替换为空白
然后执行 locale-gen
命令并设置 LANG
的值为 en_US.UTF-8
。
这两种方式都能确保 CurrentThread.CurrentCulture
获取到正确的 Culture Name。
解决 .NET Core 在 Linux Container 中获取 CurrentCulture 不正确的问题相关推荐
- 解决windows文件在linux系统中显示乱码的问题
解决windows文件在linux系统中显示乱码的问题 参考文章: (1)解决windows文件在linux系统中显示乱码的问题 (2)https://www.cnblogs.com/liyanpin ...
- Linux内核中获取纳秒时间戳的方法
Linux内核中获取纳秒时间戳的方法 1 方法1:使用getnstimeofday64方法 2 方法2:使用ktime_get_real_ns方法 1 方法1:使用getnstimeofday64方法 ...
- Linux系统中获取临时文件路径 `GetTempPath`和`GetTempFileName`函数
Linux系统中获取临时文件路径 为了在Linux系统中获得WIN API的GetTempPath和GetTempFileName函数的功能,我们要在Linux下实现这两个函数. GetTempPat ...
- 【转】Java中获取文件大小的正确方法
[转]Java中获取文件大小的正确方法 本文出处:http://blog.csdn.net/chaijunkun/article/details/22387305,转载请注明.由于本人不定期会整理相关 ...
- 从Linux内核中获取真随机数【转】
转自:http://www.cnblogs.com/bigship/archive/2010/04/04/1704228.html 内核随机数产生器 Linux内核实现了一个随机数产生器,从理论上说这 ...
- Linux电池电量信息读取,linux内核 – 如何在Linux内核模块中获取电池电量?
我正在尝试在 Linux内核模块中获得电池电量(该模块通过modprobe插入).我最好是使用内核API调用来获取电池信息.我已经在网上搜索了解决方案,我还探讨了Linux内核源代码和Michael ...
- linux shell中获取mongodb最大连接数、内存使用情况等
前两天接到了一个新的需求,需要在linux shell脚本中监控到mongodb最大连接数.内存使用情况等. 但是我对于linux shel很不了解,只是会一些简单常用的linux的操作而已,只要一顿 ...
- 获取Linux的方法,CDN Linux系统中获取LDNS的方法
在Linux命令行模式下,可通过以下方式获取: 方法一: curl -s http://13491234.testns.cdnunion.net/?callback? testns前面的数字输入一个随 ...
- 关于.rdc文件导入Blender软件时失败报错的解决办法(从谷歌地图中获取带纹理的倾斜模型数据)
文章目录 写在前面 一.如何获取谷歌地图中带纹理的倾斜模型数据 1.传送门 2.注意事项 二.遇到问题:导入.rdc文件后报错 三.解决办法:更换软件版本 1.版本选择 2.注意事项 四.写在最后 写 ...
最新文章
- 【STM32-V7】STM32H743XIH6开发板,丰富软件资源,强劲硬件配置,大量软件解决方案持续更新中(2020-07-22)
- javaweb火车车次信息管理+文件_厦门火车站启动“双十一”电商黄金周运输 投入列车数量为历年新高...
- oracle trace 文件名,限制oracle trace 文件大小
- hibernate中createQuery和createSqlQuery
- css 30 常用选择选择器
- 推荐一款Linux服务器连接工具FinalShell
- Ayla 物联网平台全面支持主流智能语音系统
- C# WebBrowser实现网页自动填表
- 保持长宽比 对背景图像进行修改android:scaleType=fitXY
- 1011 A+B 和 C (15 分)—PAT (Basic Level) Practice (中文)
- springboot整合quartz进行数据库存储
- fragstats教程
- 汽车ECU的bootloader程序设计
- 【win7黑屏终结者】win7电脑登录账户后黑屏(只有鼠标箭头)问题
- 2023年云南大学现代技术教育考研上岸前辈备考经验
- B2C之淘宝商城,图穷匕见
- 【英语】八月英语总结
- 计算机bios程序模拟器,怎么添加pcsx2 bios文件
- 5G RRC信令流程
- PHP极其强大的图片处理库Grafika详细教程(4):图形绘制 1