解决 .NET Core 在 Linux Container 中获取 CurrentCulture 不正确的问题
背景
在將公司一款基于 .NET Framework 的控制臺(tái)程序遷移到 .NET Core 3.1 時(shí),發(fā)現(xiàn)程序中本地化的部分失效,癥狀類似于對(duì)?Thread.CurrentThread.CurrentCulture.Name?的值進(jìn)行?Substring()?操作時(shí)拋出?ArgumentOutOfRangeException?異常。
該程序在 Windows Container 中工作良好,遷移為 .NET Core 后在我的 Windows 開發(fā)機(jī)上也運(yùn)行良好,一旦部署到 K8s 的 Linux 容器中就會(huì)出現(xiàn)問題。容器使用的是基于微軟官方的 .NET Runtime 3.1 鏡像(https://hub.docker.com/_/microsoft-dotnet-runtime/)。
本文按我當(dāng)時(shí)解決此問題的思路記錄,從 Windows 開始,挨個(gè)環(huán)境測(cè)試?CurrentThread.CurrentCulture。
TL;DR 先上結(jié)論
.NET Core Runtime 的 Linux 鏡像沒有設(shè)置語言信息,導(dǎo)致通過?CurrentThread.CurrentCulture?獲取的?Name?為?String.Empty。
只需要在生成鏡像時(shí)為 Linux 設(shè)置語言即可,本文末尾附有解決方案。
在 Windows 中獲取區(qū)域設(shè)置
先創(chuàng)建一個(gè)名為?CultureTest?的控制臺(tái)項(xiàng)目看看效果,這里使用 .NET Core 的 LTS 版本 .NET Core 3.1 為例,通過命令行執(zhí)行:
dotnet new console -o CultureTest --framework netcoreapp3.1然后進(jìn)入?CultureTest?文件夾,將生成的?Program.cs?替換為如下內(nèi)容:
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?對(duì)象的這些屬性值都打印出來。
Thread.Sleep()?是為在后面測(cè)試 docker 時(shí)用于防止程序運(yùn)行后直接退出之用。
我在一臺(tái)區(qū)域設(shè)置為?中文(簡(jiǎn)體,中國(guó))?的 Windows 10 PC 上運(yùn)行上述代碼:
dotnet run可以看見?Name?為?zh-CN?和 Windows 一致。
查看信息后,由于有?Thread.Sleep()?的邏輯,需要通過?Ctrl + C?來停止程序的運(yùn)行(后面 Linux 和 Docker 中也一樣)。
在 Linux 中獲取區(qū)域信息
我在 WSL(https://docs.microsoft.com/windows/wsl/install-win10) 中安裝了 Debian 10,并安裝了 .NET Core 3.1 SDK,下面用 Debian 來進(jìn)行測(cè)試。
locale 命令
在 Linux 中,可以使用?locale?命令查看當(dāng)前語言環(huán)境信息:
locale關(guān)注?LANG?的值,現(xiàn)在顯示為?en_US.UTF-8。
locale?命令加上?-a?選項(xiàng)后可以查看可用的語言環(huán)境信息:
locale -a可以看到這個(gè) Debian 除了當(dāng)前的?en_US.UTF-8,還支持其它幾種語言環(huán)境。
通過 CurrentThread 獲取
由于是 WSL,可以通過?/mnt?中掛載的 Windows 文件系統(tǒng),直接導(dǎo)航到上一節(jié)創(chuàng)建的項(xiàng)目中,并運(yùn)行:
cd /mnt/d/projects/CultureTest dotnet run可以看見?Name?為?en-US?和?locale?命令得到的一致。
在 Docker 容器中獲取區(qū)域信息
下面將測(cè)試程序放入容器中運(yùn)行。
發(fā)布測(cè)試程序
先發(fā)布?CultureTest?項(xiàng)目:
dotnet publish -c Release默認(rèn)會(huì)發(fā)布到?.\bin\Release\netcoreapp3.1\publish\?文件夾下,可以使用?dir(Windows) 或?ls(Linux) 命令查看發(fā)布結(jié)果。
創(chuàng)建 Dockerfile
接下來為?CultureTest?生成鏡像。
首先在?CultureInfo?項(xiàng)目根目錄(.csproj?所在的目錄)下創(chuàng)建?Dockerfile?并填入以下內(nèi)容:
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
拷貝剛剛發(fā)布到?.\bin\Release\netcoreapp3.1\publish\?的程序到容器的?/app?文件夾下
將容器的工作目錄設(shè)為?/app?文件夾
通過?dotnet CultureTest.dll?命令運(yùn)行測(cè)試項(xiàng)目
生成鏡像
在?Dockerfile?所在的目錄下執(zhí)行?docker build?命令生成鏡像:
docker build -t culture-test .-t culture-test?設(shè)置鏡像的名稱為?culture-test
不要漏掉最后的?.
運(yùn)行并查看結(jié)果
通過上一步創(chuàng)建的?culture-test?鏡像生成一個(gè)容器,并查看執(zhí)行結(jié)果:
docker run culture-test發(fā)現(xiàn)?Name?后沒有任何內(nèi)容。
經(jīng)過測(cè)試,CurrentThread.CurrentCulture?不會(huì)為?null,并且?Name?屬性的值為?String.Empty?而非?null。這也是我遇到問題的原因,對(duì)?String.Empty?進(jìn)行了?Substring()?操作,所以拋出了?ArgumentOutOfRangeException?異常,問題重現(xiàn)。
在容器中執(zhí)行 locale
進(jìn)入容器,查看 Linux 的語言環(huán)境信息:
docker run -d culture-test docker exec -it [your_container_id] /bin/bash通過?-d?讓程序后臺(tái)運(yùn)行(有?Thread.Sleep()?在,所以程序不會(huì)退出,這樣我們就能進(jìn)入到容器內(nèi)執(zhí)行命令),這一步執(zhí)行后會(huì)返回容器 id
通過?exec?執(zhí)行容器里的?/bin/bash
發(fā)現(xiàn)?locale?命令返回的?LANG?也是空白的。
并且?locale -a?命令返回的?C?和?POSIX?都是默認(rèn)不含語言的環(huán)境。
原因
如果使用過 Linux 的 GUI 安裝 Linux,一般會(huì)讓選擇語言和地區(qū),但?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),都沒有設(shè)置語言,所以導(dǎo)致我們通過?locale?或是 C# 的?CurrentThread.CurrentCulture?獲取到的都是空白的內(nèi)容。
那么結(jié)合上面的信息,要想讓依賴于區(qū)域語言信息的程序不報(bào)錯(cuò),有兩種方案:
修改程序,增加對(duì)?CurrentCulture.Name?的判斷:如果?CurrentCulture.Name == String.Empty,則為程序設(shè)置一個(gè)默認(rèn) Culture
修改運(yùn)行環(huán)境,將默認(rèn)語言信息設(shè)置為需要的值(例如?en-US)
為容器中的 Linux 設(shè)置語言信息
雖然最后我選擇的是修改程序,但也來了解一下這種情況如何為容器中的 Linux 設(shè)置語言信息。
通過搜索,找到了 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?包,可以把所有支持的語言信息都安裝到系統(tǒng)中,再通過環(huán)境變量設(shè)置需要的語言。
修改?Dockerfile:
FROM mcr.microsoft.com/dotnet/runtime:3.1# 安裝所有支持的語言信息,并設(shè)置 en_US.UTF-8 為當(dāng)前語言 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 文件,再設(shè)置語言 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?后,會(huì)生成?/etc/locale.gen?文件,文件內(nèi)容類似于:
# 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:執(zhí)行替換
/^#:將行首的?#
/:替換為空白
然后執(zhí)行?locale-gen?命令并設(shè)置?LANG?的值為?en_US.UTF-8。
這兩種方式都能確保?CurrentThread.CurrentCulture?獲取到正確的 Culture Name。
總結(jié)
以上是生活随笔為你收集整理的解决 .NET Core 在 Linux Container 中获取 CurrentCulture 不正确的问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NET问答: Entity Framew
- 下一篇: C# 可空类型