Skip to content

Commit

Permalink
Culture names are now limited to a known list:
Browse files Browse the repository at this point in the history
- Added tool to generate a set of known culture names from the OS/Fx
- CultureInfoCache is now limited to only caching/returning cultures from the known list
- #6
  • Loading branch information
DamianEdwards committed May 11, 2015
1 parent ca4b85e commit 9834a27
Show file tree
Hide file tree
Showing 7 changed files with 606 additions and 19 deletions.
7 changes: 7 additions & 0 deletions Localization.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
3 changes: 2 additions & 1 deletion samples/LocalizationSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ private static async System.Threading.Tasks.Task WriteCultureSelectOptions(HttpC
#if DNX451
await context.Response.WriteAsync($" <option value=\"{new CultureInfo("zh-CHT").Name}\">{new CultureInfo("zh-CHT").DisplayName}</option>");
#endif
await context.Response.WriteAsync($" <option value=\"en-NOTREAL\">English (Not a real culture)</option>");
await context.Response.WriteAsync($" <option value=\"en-NOTREAL\">English (Not a real locale)</option>");
await context.Response.WriteAsync($" <option value=\"pp-NOTREAL\">Made-up (Not a real anything)</option>");
}
}
}
20 changes: 20 additions & 0 deletions src/CultureInfoGenerator/CultureInfoGenerator.xproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>

<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>bd22ae1c-6631-4da6-874d-0dc0f803ceab</ProjectGuid>
<RootNamespace>CultureInfoGenerator</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>

<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
116 changes: 116 additions & 0 deletions src/CultureInfoGenerator/Program.cs
Original file line number Diff line number Diff line change
@@ -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<string> KnownCultureNames = new HashSet<string>
{{"
);

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";
}
}
}
16 changes: 16 additions & 0 deletions src/CultureInfoGenerator/project.json
Original file line number Diff line number Diff line change
@@ -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": { }
}
}
27 changes: 9 additions & 18 deletions src/Microsoft.AspNet.Localization/Internal/CultureInfoCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ public static class CultureInfoCache
{
private static readonly ConcurrentDictionary<string, CacheEntry> _cache = new ConcurrentDictionary<string, CacheEntry>();

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;
}
Expand All @@ -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;
}

Expand All @@ -47,14 +45,7 @@ public CacheEntry(CultureInfo cultureInfo)
CultureInfo = cultureInfo;
}

public CacheEntry(Exception exception)
{
Exception = exception;
}

public CultureInfo CultureInfo { get; }

public Exception Exception { get; }
}
}
}
Loading

0 comments on commit 9834a27

Please sign in to comment.