diff --git a/Localization.sln b/Localization.sln
index ddfd06056ca0..cf7a3d95c67c 100644
--- a/Localization.sln
+++ b/Localization.sln
@@ -20,6 +20,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
global.json = global.json
EndProjectSection
EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CultureInfoGenerator", "src\CultureInfoGenerator\CultureInfoGenerator.xproj", "{BD22AE1C-6631-4DA6-874D-0DC0F803CEAB}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -42,6 +44,10 @@ Global
{55D9501F-15B9-4339-A0AB-6082850E5FCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{55D9501F-15B9-4339-A0AB-6082850E5FCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{55D9501F-15B9-4339-A0AB-6082850E5FCE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BD22AE1C-6631-4DA6-874D-0DC0F803CEAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BD22AE1C-6631-4DA6-874D-0DC0F803CEAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BD22AE1C-6631-4DA6-874D-0DC0F803CEAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BD22AE1C-6631-4DA6-874D-0DC0F803CEAB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -51,5 +57,6 @@ Global
{23E3BC23-3464-4D9B-BF78-02CB2182BEF0} = {FB313677-BAB3-4E49-8CDB-4FA4A9564767}
{A1FCF259-70F6-4605-AA2D-E4B356BE771A} = {FB313677-BAB3-4E49-8CDB-4FA4A9564767}
{55D9501F-15B9-4339-A0AB-6082850E5FCE} = {79878809-8D1C-4BD4-BA99-F1F13FF96FD8}
+ {BD22AE1C-6631-4DA6-874D-0DC0F803CEAB} = {FB313677-BAB3-4E49-8CDB-4FA4A9564767}
EndGlobalSection
EndGlobal
diff --git a/samples/LocalizationSample/Startup.cs b/samples/LocalizationSample/Startup.cs
index d819c939992a..2183eaf3afd3 100644
--- a/samples/LocalizationSample/Startup.cs
+++ b/samples/LocalizationSample/Startup.cs
@@ -123,7 +123,8 @@ private static async System.Threading.Tasks.Task WriteCultureSelectOptions(HttpC
#if DNX451
await context.Response.WriteAsync($" ");
#endif
- await context.Response.WriteAsync($" ");
+ await context.Response.WriteAsync($" ");
+ await context.Response.WriteAsync($" ");
}
}
}
diff --git a/src/CultureInfoGenerator/CultureInfoGenerator.xproj b/src/CultureInfoGenerator/CultureInfoGenerator.xproj
new file mode 100644
index 000000000000..6e980d08c1d2
--- /dev/null
+++ b/src/CultureInfoGenerator/CultureInfoGenerator.xproj
@@ -0,0 +1,20 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+
+ bd22ae1c-6631-4da6-874d-0dc0f803ceab
+ CultureInfoGenerator
+ ..\..\artifacts\obj\$(MSBuildProjectName)
+ ..\..\artifacts\bin\$(MSBuildProjectName)\
+
+
+
+ 2.0
+
+
+
diff --git a/src/CultureInfoGenerator/Program.cs b/src/CultureInfoGenerator/Program.cs
new file mode 100644
index 000000000000..7843c928d7ee
--- /dev/null
+++ b/src/CultureInfoGenerator/Program.cs
@@ -0,0 +1,116 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Globalization;
+using System.IO;
+using Microsoft.Framework.Runtime;
+using Microsoft.Win32;
+
+namespace CultureInfoGenerator
+{
+ public class Program
+ {
+ private readonly string _appName;
+ private readonly string _appPath;
+
+ public Program(IApplicationEnvironment appEnvironment)
+ {
+ _appName = appEnvironment.ApplicationName;
+ _appPath = appEnvironment.ApplicationBasePath;
+ }
+
+ public void Main(string[] args)
+ {
+ var outputFilePath = args.Length > 0 ? args[0] : Path.Combine(_appPath, "../Microsoft.AspNet.Localization/Internal/CultureInfoList.cs");
+ var netFxVersion = Get45or451FromRegistry();
+ var windowsVersion = Environment.OSVersion;
+
+ using (var writer = new StreamWriter(outputFilePath, false))
+ {
+ writer.WriteLine($@"// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+
+namespace Microsoft.AspNet.Localization.Internal
+{{
+ public static class CultureInfoList
+ {{
+ // This list of known cultures was generated by {_appName} using .NET Framework {netFxVersion} on
+ // {windowsVersion}.
+ // As new versions of .NET Framework and Windows are released, this list should be regenerated to ensure it
+ // contains the latest culture names.
+ public static readonly HashSet KnownCultureNames = new HashSet
+ {{"
+ );
+
+ var cultures = CultureInfo.GetCultures(
+ CultureTypes.NeutralCultures
+ | CultureTypes.InstalledWin32Cultures
+ | CultureTypes.SpecificCultures);
+
+ var format = " \"{0}\"";
+
+ for (int i = 0; i < cultures.Length; i++)
+ {
+ var culture = cultures[i];
+
+ writer.Write(format, culture.Name);
+
+ if (i < cultures.Length - 1)
+ {
+ writer.WriteLine(",");
+ }
+ else
+ {
+ // Last entry
+ writer.WriteLine();
+ }
+ }
+
+ writer.WriteLine(
+@" };
+ }
+}");
+ }
+ }
+
+ // .NET Framework detection code copied from https://msdn.microsoft.com/en-us/library/hh925568%28v=vs.110%29.aspx#net_d
+ private static string Get45or451FromRegistry()
+ {
+ using (var ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)
+ .OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\\"))
+ {
+ var releaseKey = Convert.ToInt32(ndpKey.GetValue("Release"));
+ return CheckFor45DotVersion(releaseKey);
+ }
+ }
+
+ // Checking the version using >= will enable forward compatibility,
+ // however you should always compile your code on newer versions of
+ // the framework to ensure your app works the same.
+ private static string CheckFor45DotVersion(int releaseKey)
+ {
+ if (releaseKey >= 393273)
+ {
+ return "4.6 RC or later";
+ }
+ if ((releaseKey >= 379893))
+ {
+ return "4.5.2 or later";
+ }
+ if ((releaseKey >= 378675))
+ {
+ return "4.5.1 or later";
+ }
+ if ((releaseKey >= 378389))
+ {
+ return "4.5 or later";
+ }
+ // This line should never execute. A non-null release key should mean
+ // that 4.5 or later is installed.
+ return "No 4.5 or later version detected";
+ }
+ }
+}
diff --git a/src/CultureInfoGenerator/project.json b/src/CultureInfoGenerator/project.json
new file mode 100644
index 000000000000..f88d824517af
--- /dev/null
+++ b/src/CultureInfoGenerator/project.json
@@ -0,0 +1,16 @@
+{
+ "version": "1.0.0-*",
+ "description": "Generates a list of known culture names from the OS using CultureInfo.GetCultures. This tool is intended to be run on Windows using full .NET Framework.",
+
+ "dependencies": {
+ "Microsoft.Framework.Runtime.Abstractions": "1.0.0-beta5-11739"
+ },
+
+ "commands": {
+ "CultureInfoGenerator": "CultureInfoGenerator"
+ },
+
+ "frameworks": {
+ "dnx451": { }
+ }
+}
diff --git a/src/Microsoft.AspNet.Localization/Internal/CultureInfoCache.cs b/src/Microsoft.AspNet.Localization/Internal/CultureInfoCache.cs
index 87662bf84058..9e8ee0a9930e 100644
--- a/src/Microsoft.AspNet.Localization/Internal/CultureInfoCache.cs
+++ b/src/Microsoft.AspNet.Localization/Internal/CultureInfoCache.cs
@@ -11,11 +11,11 @@ public static class CultureInfoCache
{
private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary();
- public static CultureInfo GetCultureInfo(string name, bool throwIfNotFound = false)
+ public static CultureInfo GetCultureInfo(string name)
{
- // Allow empty string values as they map to InvariantCulture, whereas null culture values will throw in
- // the CultureInfo ctor
- if (name == null)
+ // Allow only known culture names as this API is called with input from users (HTTP requests) and
+ // creating CultureInfo objects is expensive and we don't want it to throw either.
+ if (name == null || !CultureInfoList.KnownCultureNames.Contains(name))
{
return null;
}
@@ -26,17 +26,15 @@ public static CultureInfo GetCultureInfo(string name, bool throwIfNotFound = fal
{
return new CacheEntry(CultureInfo.ReadOnly(new CultureInfo(n)));
}
- catch (CultureNotFoundException ex)
+ catch (CultureNotFoundException)
{
- return new CacheEntry(ex);
+ // This can still throw as the list of culture names we have is generated from latest .NET Framework
+ // on latest Windows and thus contains names that won't be supported on lower framework or OS versions.
+ // We can just cache the null result in these cases as it's ultimately bound by the list anyway.
+ return new CacheEntry(cultureInfo: null);
}
});
- if (entry.Exception != null && throwIfNotFound)
- {
- throw entry.Exception;
- }
-
return entry.CultureInfo;
}
@@ -47,14 +45,7 @@ public CacheEntry(CultureInfo cultureInfo)
CultureInfo = cultureInfo;
}
- public CacheEntry(Exception exception)
- {
- Exception = exception;
- }
-
public CultureInfo CultureInfo { get; }
-
- public Exception Exception { get; }
}
}
}
diff --git a/src/Microsoft.AspNet.Localization/Internal/CultureInfoList.cs b/src/Microsoft.AspNet.Localization/Internal/CultureInfoList.cs
new file mode 100644
index 000000000000..1d178738eb6e
--- /dev/null
+++ b/src/Microsoft.AspNet.Localization/Internal/CultureInfoList.cs
@@ -0,0 +1,436 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+
+namespace Microsoft.AspNet.Localization.Internal
+{
+ public static class CultureInfoList
+ {
+ // This list of known cultures was generated by CultureInfoGenerator using .NET Framework 4.6 RC or later on
+ // Microsoft Windows NT 6.2.9200.0.
+ // As new versions of .NET Framework and Windows are released, this list should be regenerated to ensure it
+ // contains the latest culture names.
+ public static readonly HashSet KnownCultureNames = new HashSet
+ {
+ "",
+ "af",
+ "af-ZA",
+ "am",
+ "am-ET",
+ "ar",
+ "ar-AE",
+ "ar-BH",
+ "ar-DZ",
+ "ar-EG",
+ "ar-IQ",
+ "ar-JO",
+ "ar-KW",
+ "ar-LB",
+ "ar-LY",
+ "ar-MA",
+ "ar-OM",
+ "ar-QA",
+ "ar-SA",
+ "ar-SY",
+ "ar-TN",
+ "ar-YE",
+ "arn",
+ "arn-CL",
+ "as",
+ "as-IN",
+ "az",
+ "az-Cyrl",
+ "az-Cyrl-AZ",
+ "az-Latn",
+ "az-Latn-AZ",
+ "ba",
+ "ba-RU",
+ "be",
+ "be-BY",
+ "bg",
+ "bg-BG",
+ "bn",
+ "bn-BD",
+ "bn-IN",
+ "bo",
+ "bo-CN",
+ "br",
+ "br-FR",
+ "bs",
+ "bs-Cyrl",
+ "bs-Cyrl-BA",
+ "bs-Latn",
+ "bs-Latn-BA",
+ "ca",
+ "ca-ES",
+ "ca-ES-valencia",
+ "chr",
+ "chr-Cher",
+ "chr-Cher-US",
+ "co",
+ "co-FR",
+ "cs",
+ "cs-CZ",
+ "cy",
+ "cy-GB",
+ "da",
+ "da-DK",
+ "de",
+ "de-AT",
+ "de-CH",
+ "de-DE",
+ "de-LI",
+ "de-LU",
+ "dsb",
+ "dsb-DE",
+ "dv",
+ "dv-MV",
+ "el",
+ "el-GR",
+ "en",
+ "en-029",
+ "en-AU",
+ "en-BZ",
+ "en-CA",
+ "en-GB",
+ "en-HK",
+ "en-IE",
+ "en-IN",
+ "en-JM",
+ "en-MY",
+ "en-NZ",
+ "en-PH",
+ "en-SG",
+ "en-TT",
+ "en-US",
+ "en-ZA",
+ "en-ZW",
+ "es",
+ "es-419",
+ "es-AR",
+ "es-BO",
+ "es-CL",
+ "es-CO",
+ "es-CR",
+ "es-DO",
+ "es-EC",
+ "es-ES",
+ "es-GT",
+ "es-HN",
+ "es-MX",
+ "es-NI",
+ "es-PA",
+ "es-PE",
+ "es-PR",
+ "es-PY",
+ "es-SV",
+ "es-US",
+ "es-UY",
+ "es-VE",
+ "et",
+ "et-EE",
+ "eu",
+ "eu-ES",
+ "fa",
+ "fa-IR",
+ "ff",
+ "ff-Latn",
+ "ff-Latn-SN",
+ "fi",
+ "fi-FI",
+ "fil",
+ "fil-PH",
+ "fo",
+ "fo-FO",
+ "fr",
+ "fr-BE",
+ "fr-CA",
+ "fr-CD",
+ "fr-CH",
+ "fr-CI",
+ "fr-CM",
+ "fr-FR",
+ "fr-HT",
+ "fr-LU",
+ "fr-MA",
+ "fr-MC",
+ "fr-ML",
+ "fr-RE",
+ "fr-SN",
+ "fy",
+ "fy-NL",
+ "ga",
+ "ga-IE",
+ "gd",
+ "gd-GB",
+ "gl",
+ "gl-ES",
+ "gn",
+ "gn-PY",
+ "gsw",
+ "gsw-FR",
+ "gu",
+ "gu-IN",
+ "ha",
+ "ha-Latn",
+ "ha-Latn-NG",
+ "haw",
+ "haw-US",
+ "he",
+ "he-IL",
+ "hi",
+ "hi-IN",
+ "hr",
+ "hr-BA",
+ "hr-HR",
+ "hsb",
+ "hsb-DE",
+ "hu",
+ "hu-HU",
+ "hy",
+ "hy-AM",
+ "id",
+ "id-ID",
+ "ig",
+ "ig-NG",
+ "ii",
+ "ii-CN",
+ "is",
+ "is-IS",
+ "it",
+ "it-CH",
+ "it-IT",
+ "iu",
+ "iu-Cans",
+ "iu-Cans-CA",
+ "iu-Latn",
+ "iu-Latn-CA",
+ "ja",
+ "ja-JP",
+ "jv",
+ "jv-Latn",
+ "jv-Latn-ID",
+ "ka",
+ "ka-GE",
+ "kk",
+ "kk-KZ",
+ "kl",
+ "kl-GL",
+ "km",
+ "km-KH",
+ "kn",
+ "kn-IN",
+ "ko",
+ "ko-KR",
+ "kok",
+ "kok-IN",
+ "ku",
+ "ku-Arab",
+ "ku-Arab-IQ",
+ "ky",
+ "ky-KG",
+ "lb",
+ "lb-LU",
+ "lo",
+ "lo-LA",
+ "lt",
+ "lt-LT",
+ "lv",
+ "lv-LV",
+ "mg",
+ "mg-MG",
+ "mi",
+ "mi-NZ",
+ "mk",
+ "mk-MK",
+ "ml",
+ "ml-IN",
+ "mn",
+ "mn-Cyrl",
+ "mn-MN",
+ "mn-Mong",
+ "mn-Mong-CN",
+ "mn-Mong-MN",
+ "moh",
+ "moh-CA",
+ "mr",
+ "mr-IN",
+ "ms",
+ "ms-BN",
+ "ms-MY",
+ "mt",
+ "mt-MT",
+ "my",
+ "my-MM",
+ "nb",
+ "nb-NO",
+ "ne",
+ "ne-IN",
+ "ne-NP",
+ "nl",
+ "nl-BE",
+ "nl-NL",
+ "nn",
+ "nn-NO",
+ "no",
+ "nqo",
+ "nqo-GN",
+ "nso",
+ "nso-ZA",
+ "oc",
+ "oc-FR",
+ "om",
+ "om-ET",
+ "or",
+ "or-IN",
+ "pa",
+ "pa-Arab",
+ "pa-Arab-PK",
+ "pa-IN",
+ "pl",
+ "pl-PL",
+ "prs",
+ "prs-AF",
+ "ps",
+ "ps-AF",
+ "pt",
+ "pt-AO",
+ "pt-BR",
+ "pt-PT",
+ "qut",
+ "qut-GT",
+ "quz",
+ "quz-BO",
+ "quz-EC",
+ "quz-PE",
+ "rm",
+ "rm-CH",
+ "ro",
+ "ro-MD",
+ "ro-RO",
+ "ru",
+ "ru-RU",
+ "rw",
+ "rw-RW",
+ "sa",
+ "sa-IN",
+ "sah",
+ "sah-RU",
+ "sd",
+ "sd-Arab",
+ "sd-Arab-PK",
+ "se",
+ "se-FI",
+ "se-NO",
+ "se-SE",
+ "si",
+ "si-LK",
+ "sk",
+ "sk-SK",
+ "sl",
+ "sl-SI",
+ "sma",
+ "sma-NO",
+ "sma-SE",
+ "smj",
+ "smj-NO",
+ "smj-SE",
+ "smn",
+ "smn-FI",
+ "sms",
+ "sms-FI",
+ "sn",
+ "sn-Latn",
+ "sn-Latn-ZW",
+ "so",
+ "so-SO",
+ "sq",
+ "sq-AL",
+ "sr",
+ "sr-Cyrl",
+ "sr-Cyrl-BA",
+ "sr-Cyrl-CS",
+ "sr-Cyrl-ME",
+ "sr-Cyrl-RS",
+ "sr-Latn",
+ "sr-Latn-BA",
+ "sr-Latn-CS",
+ "sr-Latn-ME",
+ "sr-Latn-RS",
+ "st",
+ "st-ZA",
+ "sv",
+ "sv-FI",
+ "sv-SE",
+ "sw",
+ "sw-KE",
+ "syr",
+ "syr-SY",
+ "ta",
+ "ta-IN",
+ "ta-LK",
+ "te",
+ "te-IN",
+ "tg",
+ "tg-Cyrl",
+ "tg-Cyrl-TJ",
+ "th",
+ "th-TH",
+ "ti",
+ "ti-ER",
+ "ti-ET",
+ "tk",
+ "tk-TM",
+ "tn",
+ "tn-BW",
+ "tn-ZA",
+ "tr",
+ "tr-TR",
+ "ts",
+ "ts-ZA",
+ "tt",
+ "tt-RU",
+ "tzm",
+ "tzm-Latn",
+ "tzm-Latn-DZ",
+ "tzm-Tfng",
+ "tzm-Tfng-MA",
+ "ug",
+ "ug-CN",
+ "uk",
+ "uk-UA",
+ "ur",
+ "ur-IN",
+ "ur-PK",
+ "uz",
+ "uz-Cyrl",
+ "uz-Cyrl-UZ",
+ "uz-Latn",
+ "uz-Latn-UZ",
+ "vi",
+ "vi-VN",
+ "wo",
+ "wo-SN",
+ "xh",
+ "xh-ZA",
+ "yo",
+ "yo-NG",
+ "zgh",
+ "zgh-Tfng",
+ "zgh-Tfng-MA",
+ "zh",
+ "zh-CN",
+ "zh-Hans",
+ "zh-Hant",
+ "zh-HK",
+ "zh-MO",
+ "zh-SG",
+ "zh-TW",
+ "zu",
+ "zu-ZA",
+ "zh-CHS",
+ "zh-CHT"
+ };
+ }
+}