diff --git a/.gitignore b/.gitignore
index 65df276..aba8492 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
[Bb]in/
TestResults/
.nuget/
+.vs/
_ReSharper.*/
packages/
artifacts/
@@ -25,4 +26,4 @@ nuget.exe
*.ipch
*.sln.ide
debugSettings.json
-project.lock.json
+project.lock.json
\ No newline at end of file
diff --git a/samples/LocalizationSample/Startup.cs b/samples/LocalizationSample/Startup.cs
index dc7fb81..d819c93 100644
--- a/samples/LocalizationSample/Startup.cs
+++ b/samples/LocalizationSample/Startup.cs
@@ -27,8 +27,15 @@ namespace LocalizationSample
};
app.UseRequestLocalization(options);
- app.Run(async (context) =>
+ app.Use(async (context, next) =>
{
+ if (context.Request.Path.Value.EndsWith("favicon.ico"))
+ {
+ // Pesky browsers
+ context.Response.StatusCode = 404;
+ return;
+ }
+
context.Response.StatusCode = 200;
context.Response.ContentType = "text/html; charset=utf-8";
@@ -39,12 +46,25 @@ namespace LocalizationSample
$@"
- Request Localization
+ {SR["Request Localization"]}
+
");
await context.Response.WriteAsync($"{SR["Request Localization Sample"]}
");
@@ -57,8 +77,9 @@ $@"
await context.Response.WriteAsync("
");
- await context.Response.WriteAsync(" ");
- await context.Response.WriteAsync($"{SR["reset"]}");
+ await context.Response.WriteAsync(" ");
+ await context.Response.WriteAsync($" ");
+ await context.Response.WriteAsync($"{SR["reset"]}");
await context.Response.WriteAsync("");
await context.Response.WriteAsync("
");
await context.Response.WriteAsync("");
@@ -102,6 +123,7 @@ $@"
#if DNX451
await context.Response.WriteAsync($" ");
#endif
+ await context.Response.WriteAsync($" ");
}
}
}
diff --git a/src/Microsoft.AspNet.Localization/AcceptLanguageHeaderRequestCultureStrategy.cs b/src/Microsoft.AspNet.Localization/AcceptLanguageHeaderRequestCultureStrategy.cs
index 1a90e53..030e294 100644
--- a/src/Microsoft.AspNet.Localization/AcceptLanguageHeaderRequestCultureStrategy.cs
+++ b/src/Microsoft.AspNet.Localization/AcceptLanguageHeaderRequestCultureStrategy.cs
@@ -13,9 +13,6 @@ namespace Microsoft.AspNet.Localization
///
/// Determines the culture information for a request via the value of the Accept-Language header.
///
- ///
- ///
- ///
public class AcceptLanguageHeaderRequestCultureStrategy : IRequestCultureStrategy
{
///
@@ -53,11 +50,11 @@ namespace Microsoft.AspNet.Localization
// the CultureInfo ctor
if (language.Value != null)
{
- try
+ var culture = CultureInfoCache.GetCultureInfo(language.Value);
+ if (culture != null)
{
- return new RequestCulture(CultureUtilities.GetCultureFromName(language.Value));
+ return RequestCulture.GetRequestCulture(culture);
}
- catch (CultureNotFoundException) { }
}
}
diff --git a/src/Microsoft.AspNet.Localization/CookieRequestCultureStrategy.cs b/src/Microsoft.AspNet.Localization/CookieRequestCultureStrategy.cs
index 902d581..ac9a1b3 100644
--- a/src/Microsoft.AspNet.Localization/CookieRequestCultureStrategy.cs
+++ b/src/Microsoft.AspNet.Localization/CookieRequestCultureStrategy.cs
@@ -3,16 +3,98 @@
using System;
using Microsoft.AspNet.Http;
+using Microsoft.AspNet.Localization.Internal;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Localization
{
+ ///
+ /// Determines the culture information for a request via the value of a cookie.
+ ///
public class CookieRequestCultureStrategy : IRequestCultureStrategy
{
+ private static readonly char[] _cookieSeparator = new[] { '|' };
+ private static readonly string _culturePrefix = "c=";
+ private static readonly string _uiCulturePrefix = "uic=";
+
+ ///
+ /// The name of the cookie that contains the user's preferred culture information.
+ /// Defaults to .
+ ///
+ public string CookieName { get; set; } = DefaultCookieName;
+
+ ///
public RequestCulture DetermineRequestCulture([NotNull] HttpContext httpContext)
{
- // TODO
- return null;
+ var cookie = httpContext.Request.Cookies[CookieName];
+
+ if (cookie == null)
+ {
+ return null;
+ }
+
+ return ParseCookieValue(cookie);
+ }
+
+ ///
+ /// The default name of the cookie used to track the user's preferred culture information.
+ ///
+ public static string DefaultCookieName { get; } = "ASPNET_CULTURE";
+
+ ///
+ /// Creates a string representation of a for placement in a cookie.
+ ///
+ /// The .
+ /// The cookie value.
+ public static string MakeCookieValue([NotNull] RequestCulture requestCulture)
+ {
+ var seperator = _cookieSeparator[0].ToString();
+
+ return string.Join(seperator,
+ $"{_culturePrefix}{requestCulture.Culture.Name}",
+ $"{_uiCulturePrefix}{requestCulture.UICulture.Name}");
+ }
+
+ ///
+ /// Parses a from the specified cookie value.
+ /// Returns null if parsing fails.
+ ///
+ /// The cookie value to parse.
+ /// The or null if parsing fails.
+ public static RequestCulture ParseCookieValue([NotNull] string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return null;
+ }
+
+ var parts = value.Split(_cookieSeparator, StringSplitOptions.RemoveEmptyEntries);
+
+ if (parts.Length != 2)
+ {
+ return null;
+ }
+
+ var potentialCultureName = parts[0];
+ var potentialUICultureName = parts[1];
+
+ if (!potentialCultureName.StartsWith(_culturePrefix) || !potentialUICultureName.StartsWith(_uiCulturePrefix))
+ {
+ return null;
+ }
+
+ var cultureName = potentialCultureName.Substring(_culturePrefix.Length);
+ var uiCultureName = potentialUICultureName.Substring(_uiCulturePrefix.Length);
+
+ var culture = CultureInfoCache.GetCultureInfo(cultureName);
+ var uiCulture = CultureInfoCache.GetCultureInfo(uiCultureName);
+
+ if (culture == null || uiCulture == null)
+ {
+ return null;
+ }
+
+ return RequestCulture.GetRequestCulture(culture, uiCulture);
}
}
}
diff --git a/src/Microsoft.AspNet.Localization/Internal/CultureInfoCache.cs b/src/Microsoft.AspNet.Localization/Internal/CultureInfoCache.cs
new file mode 100644
index 0000000..87662bf
--- /dev/null
+++ b/src/Microsoft.AspNet.Localization/Internal/CultureInfoCache.cs
@@ -0,0 +1,60 @@
+// 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.Collections.Concurrent;
+using System.Globalization;
+
+namespace Microsoft.AspNet.Localization.Internal
+{
+ public static class CultureInfoCache
+ {
+ private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary();
+
+ public static CultureInfo GetCultureInfo(string name, bool throwIfNotFound = false)
+ {
+ // Allow empty string values as they map to InvariantCulture, whereas null culture values will throw in
+ // the CultureInfo ctor
+ if (name == null)
+ {
+ return null;
+ }
+
+ var entry = _cache.GetOrAdd(name, n =>
+ {
+ try
+ {
+ return new CacheEntry(CultureInfo.ReadOnly(new CultureInfo(n)));
+ }
+ catch (CultureNotFoundException ex)
+ {
+ return new CacheEntry(ex);
+ }
+ });
+
+ if (entry.Exception != null && throwIfNotFound)
+ {
+ throw entry.Exception;
+ }
+
+ return entry.CultureInfo;
+ }
+
+ private class CacheEntry
+ {
+ 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/CultureUtilities.cs b/src/Microsoft.AspNet.Localization/Internal/CultureUtilities.cs
deleted file mode 100644
index bea6d21..0000000
--- a/src/Microsoft.AspNet.Localization/Internal/CultureUtilities.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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.Globalization;
-
-namespace Microsoft.AspNet.Localization.Internal
-{
- public static class CultureUtilities
- {
- public static CultureInfo GetCultureFromName(string cultureName)
- {
- // Allow empty string values as they map to InvariantCulture, whereas null culture values will throw in
- // the CultureInfo ctor
- if (cultureName == null)
- {
- return null;
- }
-
- try
- {
- return new CultureInfo(cultureName);
- }
- catch (CultureNotFoundException)
- {
- return null;
- }
- }
- }
-}
diff --git a/src/Microsoft.AspNet.Localization/QueryStringRequestCultureStrategy.cs b/src/Microsoft.AspNet.Localization/QueryStringRequestCultureStrategy.cs
index c7cfdb7..80a7ba7 100644
--- a/src/Microsoft.AspNet.Localization/QueryStringRequestCultureStrategy.cs
+++ b/src/Microsoft.AspNet.Localization/QueryStringRequestCultureStrategy.cs
@@ -59,9 +59,15 @@ namespace Microsoft.AspNet.Localization
queryUICulture = queryCulture;
}
- return new RequestCulture(
- CultureUtilities.GetCultureFromName(queryCulture),
- CultureUtilities.GetCultureFromName(queryUICulture));
+ var culture = CultureInfoCache.GetCultureInfo(queryCulture);
+ var uiCulture = CultureInfoCache.GetCultureInfo(queryUICulture);
+
+ if (culture == null || uiCulture == null)
+ {
+ return null;
+ }
+
+ return RequestCulture.GetRequestCulture(culture, uiCulture);
}
}
}
diff --git a/src/Microsoft.AspNet.Localization/RequestCulture.cs b/src/Microsoft.AspNet.Localization/RequestCulture.cs
index ec109e6..9f0e275 100644
--- a/src/Microsoft.AspNet.Localization/RequestCulture.cs
+++ b/src/Microsoft.AspNet.Localization/RequestCulture.cs
@@ -1,6 +1,7 @@
// 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.Concurrent;
using System.Globalization;
using Microsoft.Framework.Internal;
@@ -11,24 +12,15 @@ namespace Microsoft.AspNet.Localization
///
public class RequestCulture
{
- ///
- /// Creates a new object has its and
- /// properties set to the same value.
- ///
- /// The for the request.
- public RequestCulture([NotNull] CultureInfo culture)
+ private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary();
+
+ private RequestCulture([NotNull] CultureInfo culture)
: this (culture, culture)
{
}
- ///
- /// Creates a new object has its and
- /// properties set to the respective values provided.
- ///
- /// The for the request to be used for formatting.
- /// The for the request to be used for text, i.e. language.
- public RequestCulture([NotNull] CultureInfo culture, [NotNull] CultureInfo uiCulture)
+ private RequestCulture([NotNull] CultureInfo culture, [NotNull] CultureInfo uiCulture)
{
Culture = culture;
UICulture = uiCulture;
@@ -43,5 +35,66 @@ namespace Microsoft.AspNet.Localization
/// Gets the for the request to be used for text, i.e. language;
///
public CultureInfo UICulture { get; }
+
+ ///
+ /// Gets a cached instance that has its and
+ /// properties set to the same value.
+ ///
+ /// The for the request.
+ public static RequestCulture GetRequestCulture([NotNull] CultureInfo culture)
+ {
+ return GetRequestCulture(culture, culture);
+ }
+
+ ///
+ /// Gets a cached instance that has its and
+ /// properties set to the respective values provided.
+ ///
+ /// The for the request to be used for formatting.
+ /// The for the request to be used for text, i.e. language.
+ ///
+ public static RequestCulture GetRequestCulture([NotNull] CultureInfo culture, [NotNull] CultureInfo uiCulture)
+ {
+ var key = new CacheKey(culture, uiCulture);
+ return _cache.GetOrAdd(key, k => new RequestCulture(culture, uiCulture));
+ }
+
+ private class CacheKey
+ {
+ private readonly int _hashCode;
+
+ public CacheKey(CultureInfo culture, CultureInfo uiCulture)
+ {
+ Culture = culture;
+ UICulture = uiCulture;
+ _hashCode = new { Culture, UICulture }.GetHashCode();
+ }
+
+ public CultureInfo Culture { get; }
+
+ public CultureInfo UICulture { get; }
+
+ public bool Equals(CacheKey other)
+ {
+ return Culture == other.Culture && UICulture == other.UICulture;
+ }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as CacheKey;
+
+ if (other != null)
+ {
+ return Equals(other);
+ }
+
+ return base.Equals(obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return _hashCode;
+ }
+ }
}
}
diff --git a/src/Microsoft.AspNet.Localization/RequestLocalizationMiddleware.cs b/src/Microsoft.AspNet.Localization/RequestLocalizationMiddleware.cs
index 43ac349..1cd4ea2 100644
--- a/src/Microsoft.AspNet.Localization/RequestLocalizationMiddleware.cs
+++ b/src/Microsoft.AspNet.Localization/RequestLocalizationMiddleware.cs
@@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Localization
public async Task Invoke([NotNull] HttpContext context)
{
var requestCulture = _options.DefaultRequestCulture ??
- new RequestCulture(CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture);
+ RequestCulture.GetRequestCulture(CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture);
IRequestCultureStrategy winningStrategy = null;
@@ -61,12 +61,12 @@ namespace Microsoft.AspNet.Localization
// Ensure that selected cultures are in the supported list and if not, set them to the default
if (!_options.SupportedCultures.Contains(requestCulture.Culture))
{
- requestCulture = new RequestCulture(_options.DefaultRequestCulture.Culture, requestCulture.UICulture);
+ requestCulture = RequestCulture.GetRequestCulture(_options.DefaultRequestCulture.Culture, requestCulture.UICulture);
}
if (!_options.SupportedUICultures.Contains(requestCulture.UICulture))
{
- requestCulture = new RequestCulture(requestCulture.Culture, _options.DefaultRequestCulture.UICulture);
+ requestCulture = RequestCulture.GetRequestCulture(requestCulture.Culture, _options.DefaultRequestCulture.UICulture);
}
}
diff --git a/src/Microsoft.AspNet.Localization/RequestLocalizationMiddlewareOptions.cs b/src/Microsoft.AspNet.Localization/RequestLocalizationMiddlewareOptions.cs
index 32827eb..0f24d18 100644
--- a/src/Microsoft.AspNet.Localization/RequestLocalizationMiddlewareOptions.cs
+++ b/src/Microsoft.AspNet.Localization/RequestLocalizationMiddlewareOptions.cs
@@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Localization
///
public RequestLocalizationMiddlewareOptions()
{
- DefaultRequestCulture = new RequestCulture(CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture);
+ DefaultRequestCulture = RequestCulture.GetRequestCulture(CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture);
RequestCultureStrategies = new List
{
diff --git a/src/Microsoft.AspNet.Localization/project.json b/src/Microsoft.AspNet.Localization/project.json
index a6d3910..069295b 100644
--- a/src/Microsoft.AspNet.Localization/project.json
+++ b/src/Microsoft.AspNet.Localization/project.json
@@ -13,6 +13,7 @@
"dnxcore50": {
"dependencies": {
"System.Collections": "4.0.10-*",
+ "System.Collections.Concurrent": "4.0.10-*",
"System.Linq": "4.0.0-*",
"System.Globalization": "4.0.10-*",
"System.Threading": "4.0.10-*",