解决 .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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/303420.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java程序设计当中包的使用_【学习笔记】 唐大仕—Java程序设计 第4讲 类、包和接口之4.2 类的继承...

【学习笔记】 唐大仕—Java程序设计 第4讲 类、包和接口之4.2 类的继承super的使用1.使用super访问父类的域和方法注意:正是由于继承,使用this可以访问父类的域和方法。但是有时为了明确指明父类的域和方法,就要用关键字super。this和super都…

机器学习资料升级版来了!!!

机器学习一直是一个热门的领域。上次分享的机器学习资料【资源】机器学习资料包来袭受到大家的广泛好评,今天小编打算分享一份机器学习升级版的资料,有斯坦福大学-深度学习基础教程、机器学习实战、人工智能与大数据、Tagging等。顺序最下优化算法Error …

java consumer 使用_java.util.function.Consumer的用法

Consumer类包含两个方法,一个accept方法用来对输入的参数进行自定义操作,因为是个抽象方法,所以需要实例化对象的时候进行Override,另一个andThen方法跟Function的方法一样是一个default方法,已经有内部实现所以不需要…

程序员从初级到中级10个秘诀

Justin James曾发表过一篇博文《10 tips for advancing from a beginner to an intermediate developer》,为我们分享如何才能完成程序员从初级到中级的蜕变,现将中文译文转载于此,供大家借鉴。 在一封与TechRepublic会员交流的邮件当中&…

C# 可空类型

C# 单问号 ? 与 双问号 ?? ? : 单问号用于对 int,double,bool 等无法直接赋值为 null 的数据类型进行 null 的赋值&#xff0c;意思是这个数据类型是 Nullable 类型的。int? i 3;等同于&#xff1a;Nullable<int> i new Nullable<int>(3); int i; //默认值0…

K-Means算法的10个有趣用例

K-means算法具有悠久的历史&#xff0c;并且也是最常用的聚类算法之一。K-means算法实施起来非常简单&#xff0c;因此&#xff0c;它非常适用于机器学习新手爱好者。首先我们来回顾K-Means算法的起源&#xff0c;然后介绍其较为典型的应用场景。起源1967年&#xff0c;James M…

计算数字的出现次数 java_关于Java:如何计算数字在.txt文件中出现的次数

早上好&#xff0c;我正在做一些Java练习&#xff0c;偶然发现了这个问题。 我有一个.txt文件&#xff0c;其中包含多个数字&#xff0c;每行一个。 练习的目的是查看哪些数字等于10 ^ 0&#xff0c;...&#xff0c;10 ^ n直到达到n。 然后&#xff0c;我必须在.txt中写入每个数…

Asp.net中的HttpModule和HttpHandler的简单用法

在Asp.net中&#xff0c;HttpModule和HttpHandler均可以截取IIS消息进行处理&#xff0c;这使得我们制作人员能够非常方便的进行诸如图片水印添加&#xff0c;图片盗链检查等功能。 下面先就HttpModule的使用方法进行简单说明&#xff1a; using System;using System.Web;names…

C# $的用法

今天闲来无事&#xff0c;就随便在网上乱看&#xff0c;突然想到Jquery的$符号很强大&#xff0c;那么C#有没有这个东西呢&#xff0c;一查&#xff0c;果然有。经查证发现&#xff0c;这个是在C#6.0出现的一个新特性&#xff0c;也就是一个小语法糖&#xff0c;其作用相当于对…

想转行人工智能?机会来了!!!

一个坏消息&#xff1a;2018年1月 教育部印发的《普通高中课程方案和语文等学科课程标准》新加入了数据结构、人工智能、开源硬件设计等 AI 相关的课程。这意味着职场新人和准备找工作的同学们&#xff0c;为了在今后十年内不被淘汰&#xff0c;你们要补课了&#xff0c;从初中…

tankwar java_TankWar 单机(JAVA版) 版本0.3 画出坦克

其实就是通过自定义的panel 重写里面的paint方法 使用Graphics类画一个圆然后把自定的panel添加到窗口中由于想到tank不能只画一个 所以我们封装一个tank类 里面有一个draw方法 用来画 坦克通过实例化tank 调用draw方法就能实现画tank了具体代码如下&#xff1a;Tank类pac…

轻量NuGet服务—BaGet

相信大家都受益过nuget.org&#xff0c;上面的海量的库、工具、模板为我们开发提供了极大的帮助&#xff0c;其中有很多都是非常宽松的开源协议&#xff0c;在此感谢那无私奉献的人。有的时候&#xff0c;在企业内部&#xff0c;有些库是私有的&#xff0c;专项的&#xff0c;要…

光驱读盘不正常

问&#xff1a;我的电脑安装的是Windows XP操作系统&#xff0c;安装好后出现了一个问题&#xff0c;就是不能连续读光盘&#xff0c;插入第一张光盘一切正常&#xff0c;打开光驱放入第二张光盘&#xff0c;发现里边的内容竟是第一张光盘的内容&#xff0c;重新启动机器后&…

在安卓上安装java_如何在Android上安装JavaCV并使用FrameGrabber

有人能告诉我我在哪里做错了吗&#xff1f;这些是我遵循的步骤&#xff1a;>从Android开发者网站下载了adt-bundle-windows>创建了一个新项目和一个libs / armeabi文件夹>将所有* .so文件从javacv-android-arm.jar,opencv-2.4.3-android-arm.zip和ffmpeg-1.0-android-…

使用 Github Actions artifact 在 workflow job 之间共享数据

AgileConfig 在使用 react 编写UI后&#xff0c;变成了一个彻彻底底的前后端分离的项目&#xff0c;上一次解决了把react spa 跟asp.net core 站点集成起来ASP.NET Core 集成 React SPA 应用。本来我每次提交代码的时候都需要手动运行npm run build,然后把dist的内容复制到asp…

撤回的微信消息真的看不到?78行Python代码帮你看穿一切!

导读&#xff1a;Python曾经对我说&#xff1a;"时日不多&#xff0c;赶紧用Python"。于是看到了一个基于python的微信开源库&#xff1a;itchat&#xff0c;玩了一天&#xff0c;做了一个程序&#xff0c;把私聊撤回的信息可以收集起来并发送到个人微信的文件传输助…

WCF的用户名+密码认证方式

概述 今天在做Master Data Service&#xff08;后面简称MDS&#xff09;项目时需要通过WCF来使用MDS的API&#xff0c;从而对MDS的数据进行操作。在这个过程中&#xff0c;遇到了一个棘手的问题&#xff0c;就是在客户端调用Web Service时的身份认证问题&#xff0c;于是乎对WC…

lmbs PHP,PHP 清空 MySql 指定数据表中的所有数据

PHP 清空 MySql 指定数据表中的所有数据PHP 清空 MySql 指定数据表中的所有数据&#xff0c;在烈火小编在网上闲逛时&#xff0c;这是某技术博客的一篇文章&#xff0c;说是文章&#xff0c;并没有文字&#xff0c;只有代码&#xff0c;经过测试可以使用&#xff0c;现在分享给…

全网最通透的“闭包”认知 · 跨越语言

闭包作为前端面试的必考题目&#xff0c;常让1-3年工作经验的JavaScripter感到困惑&#xff0c;其实主流语言都有闭包。今天我们深入聊一聊[闭包]&#xff0c; 查缺补漏&#xff01;1. 以面试题 投石问路 2. 以C#闭包 庖丁解牛 3. 跨越语言 追本溯源 • 头等函数 •…

北大最神博士论文:为什么学校打印店老板大多是湖南人?

导读&#xff1a;北京大学社会学系博士冯军旗的一篇论文&#xff0c;虽然是话题看起来有些奇葩&#xff0c;但仍然考据严谨&#xff0c;很有价值。01 写在前面&#xff08;非论文部分&#xff09;多媒体出版行业中&#xff0c;充满油墨芬芳的印刷总是长盛不衰。特别是在大学校园…