{"id":1487,"date":"2019-11-18T15:54:32","date_gmt":"2019-11-18T13:54:32","guid":{"rendered":"http:\/\/tpodolak.com\/blog\/?p=1487"},"modified":"2019-11-18T15:54:32","modified_gmt":"2019-11-18T13:54:32","slug":"net-core-missing-currency-symbol-docker-alpine-image","status":"publish","type":"post","link":"https:\/\/tpodolak.com\/blog\/2019\/11\/18\/net-core-missing-currency-symbol-docker-alpine-image\/","title":{"rendered":".NET Core &#8211; missing currency symbol in docker alpine image"},"content":{"rendered":"<p>During the process of moving a Scala-based API to .NET Core, we encountered an interesting localization issue when running our code in a docker container based on an alpine image. The code itself was doing a currency formatting based on some culture. It looked more or less as below<\/p>\n<pre lang=\"csharp\">\r\n[Route(\"api\/[controller]\")]\r\n[ApiController]\r\npublic class PriceController : ControllerBase\r\n{\r\n    [HttpGet]\r\n    public ActionResult<object> Get(string cultureCode = \"de-DE\")\r\n    {\r\n        var cultureInfo = CultureInfo.CreateSpecificCulture(cultureCode);\r\n        var price = 10m;\r\n        \r\n        return new\r\n        {\r\n            price,\r\n            formattedPrice = price.ToString(\"C\", cultureInfo),\r\n            currencySymbol = cultureInfo.NumberFormat.CurrencySymbol\r\n        };\r\n    }\r\n}\r\n<\/pre>\n<p>We also had some integration tests for that piece of logic<\/p>\n<pre lang=\"csharp\">\r\npublic class PriceTests : IClassFixture<WebApplicationFactory<Startup>>\r\n{\r\n    private readonly WebApplicationFactory<Startup> _factory;\r\n\r\n    public PriceTests(WebApplicationFactory<Startup> factory)\r\n    {\r\n        _factory = factory;\r\n    }\r\n        \r\n    [Fact]\r\n    public async Task GetPrice_ReturnsCorrectCurrency()\r\n    {\r\n        var httpClient = _factory.CreateClient();\r\n        var httpResponseMessage = await httpClient.GetAsync(\"api\/Price\");\r\n            \r\n        httpResponseMessage.EnsureSuccessStatusCode();\r\n        var content = await httpResponseMessage.Content.ReadAsStringAsync();\r\n        var jObject = JObject.Parse(content);\r\n\r\n        jObject[\"currencySymbol\"].ToObject<string>().Should().Be(\"\u20ac\");\r\n    }\r\n}\r\n<\/pre>\n<p>The tests were run during a CI build in a container with an image defined as below<\/p>\n<pre lang=\"docker\">\r\n# Builder image\r\nFROM mcr.microsoft.com\/dotnet\/core\/sdk:2.1.700-alpine AS builder\r\nWORKDIR \/sln\r\n\r\nCOPY .\/*.sln .\/\r\n\r\n# Copy the main source project files\r\nCOPY src\/*\/*.csproj .\/\r\nRUN for file in $(ls *.csproj); do mkdir -p src\/${file%.*}\/ && mv $file src\/${file%.*}\/; done\r\n\r\n# Copy the test project files\r\nCOPY test\/*\/*.csproj .\/\r\nRUN for file in $(ls *.csproj); do mkdir -p test\/${file%.*}\/ && mv $file test\/${file%.*}\/; done\r\n\r\nRUN dotnet restore\r\n\r\n# Copy across the rest of the source files\r\nCOPY .\/test .\/test\r\nCOPY .\/src .\/src\r\n\r\nRUN dotnet build -c Release\r\n\r\nRUN dotnet test \".\/test\/AlpineMissingCurrencySymbol.Tests\/AlpineMissingCurrencySymbol.Tests.csproj\" \\\r\n    -c Release --no-build --no-restore\r\n\r\nRUN dotnet publish \".\/src\/AlpineMissingCurrencySymbol\/AlpineMissingCurrencySymbol.csproj\" \\\r\n    -c Release -o \"..\/..\/dist\" --no-restore\r\n\r\n# App image\r\nFROM mcr.microsoft.com\/dotnet\/core\/aspnet:2.1.11-alpine\r\nWORKDIR \/app\r\nENTRYPOINT [\"dotnet\", \"AlpineMissingCurrencySymbol.dll\"]\r\nCOPY --from=builder \/sln\/dist .\r\n<\/pre>\n<p>At this point, we were sure that everything works fine, as the tests were green and everything was also working correctly on our local machines. However, after deployment to a testing environment, we started getting invalid currency symbols<\/p>\n<pre lang=\"json\">\r\ncurl localhost:8080\/api\/price \r\n{ \r\n  \"price\": 10, \r\n  \"formattedPrice\": \"\u00a410.00\", \r\n  \"currencySymbol\": \"\u00a4\" \r\n}\r\n<\/pre>\n<p>As you can see the response contains \u00a4 (invariant currency symbol) instead of expected \u20ac. It took us some time to figure this out but finally, it turned out that <i>aspnet:2.1.11-alpine<\/i> image(the one we used for running the application) contrary to the <i>SDK<\/i> image(used for building and running tests) is missing icu-libs package. In default conditions, the application should throw the following exception during the startup<\/p>\n<pre lang=\"csharp\">\r\nFailFast:\r\nCouldn't find a valid ICU package installed on the system. Set the configuration flag System.Globalization.Invariant to true if you want to run with no globalization support.\r\n\r\n   at System.Environment.FailFast(System.String)\r\n   at System.Globalization.GlobalizationMode.GetGlobalizationInvariantMode()\r\n   at System.Globalization.GlobalizationMode..cctor()\r\n   at System.Globalization.CultureData.CreateCultureWithInvariantData()\r\n   at System.Globalization.CultureData.get_Invariant()\r\n   at System.Globalization.CultureInfo..cctor()\r\n   at System.StringComparer..cctor()\r\n   at System.AppDomain.InitializeCompatibilityFlags()\r\n   at System.AppDomain.Setup(System.Object)\r\n\r\n<\/pre>\n<p>However, <i>aspnet:2.1.11-alpine<\/i> image has the <a href=\"https:\/\/github.com\/dotnet\/corefx\/blob\/master\/Documentation\/architecture\/globalization-invariant-mode.md\">DOTNET_SYSTEM_GLOBALIZATION_INVARIANT<\/a> flag set to true by default, so the missing package was not validated during a startup. After all, in order to fix the issue, we had to install the icu-libs package and also set the DOTNET_SYSTEM_GLOBALIZATION_INVARIANT back to false. This was done by these two lines in Dockerfile<\/p>\n<pre lang=\"docker\">\r\nENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \r\nRUN apk add --no-cache icu-libs \r\n<\/pre>\n<p>Once the lines were added the application started working as expected<\/p>\n<pre lang=\"json\">\r\ncurl localhost:8080\/api\/price \r\n{ \r\n  \"price\": 10, \r\n  \"formattedPrice\": \"10,00 \u20ac\", \r\n  \"currencySymbol\": \"\u20ac\" \r\n} \r\n<\/pre>\n<p>Source code for this post can be found <a href=\"https:\/\/github.com\/tpodolak\/Blog\/tree\/master\/AlpineMissingCurrencySymbol\">here<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>During the process of moving a Scala-based API to .NET Core, we encountered an interesting localization issue when running our code in a docker container based on an alpine image. The code itself was doing a currency formatting based on some culture. It looked more or less as below [Route(&#8220;api\/[controller]&#8221;)] [ApiController] public class PriceController : [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[328],"tags":[340],"class_list":["post-1487","post","type-post","status-publish","format-standard","hentry","category-net-core","tag-docker"],"_links":{"self":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1487","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/comments?post=1487"}],"version-history":[{"count":10,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1487\/revisions"}],"predecessor-version":[{"id":1497,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1487\/revisions\/1497"}],"wp:attachment":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/media?parent=1487"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/categories?post=1487"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/tags?post=1487"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}