Implemented CookieRequestCultureStrategy & other changes:

- Updated sample to enable setting/clearing cultures via cookie
- Cache CultureInfo construction as it's not built into .NET Core
- Cache RequestCulture construction as they're immutable anyway and created lots per app if the middleware is running
- Fix issue where by invalid culture names were not handled (it crashed)
- Handle the pesky favicon.ico request from browsers
- Ignore .vs folder
This commit is contained in:
damianedwards 2015-05-07 18:11:10 -07:00
Родитель ec8ede5d8a
Коммит 944c84bc5d
11 изменённых файлов: 255 добавлений и 62 удалений

3
.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

Просмотреть файл

@ -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
$@"<!doctype html>
<html>
<head>
<title>Request Localization</title>
<title>{SR["Request Localization"]}</title>
<style>
body {{ font-family: 'Segoe UI', Helvetica, Sans-Serif }}
h1, h2, h3, h4, th {{ font-family: 'Segoe UI Light', Helvetica, Sans-Serif }}
th {{ text-align: left }}
</style>
<script>
function useCookie() {{
var culture = document.getElementById('culture');
var uiCulture = document.getElementById('uiCulture');
var cookieValue = '{CookieRequestCultureStrategy.DefaultCookieName}=c='+culture.options[culture.selectedIndex].value+'|uic='+uiCulture.options[uiCulture.selectedIndex].value;
document.cookie = cookieValue;
window.location = window.location.href.split('?')[0];
}}
function clearCookie() {{
document.cookie='{CookieRequestCultureStrategy.DefaultCookieName}=""""';
}}
</script>
</head>
<body>");
await context.Response.WriteAsync($"<h1>{SR["Request Localization Sample"]}</h1>");
@ -57,8 +77,9 @@ $@"<!doctype html>
await context.Response.WriteAsync("<select id=\"uiCulture\" name=\"ui-culture\">");
await WriteCultureSelectOptions(context);
await context.Response.WriteAsync("</select><br />");
await context.Response.WriteAsync("<input type=\"submit\" value=\"go\" /> ");
await context.Response.WriteAsync($"<a href=\"/\">{SR["reset"]}</a>");
await context.Response.WriteAsync("<input type=\"submit\" value=\"go QS\" /> ");
await context.Response.WriteAsync($"<input type=\"button\" value=\"go cookie\" onclick='useCookie();' /> ");
await context.Response.WriteAsync($"<a href=\"/\" onclick='clearCookie();'>{SR["reset"]}</a>");
await context.Response.WriteAsync("</form>");
await context.Response.WriteAsync("<br />");
await context.Response.WriteAsync("<table><tbody>");
@ -102,6 +123,7 @@ $@"<!doctype html>
#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>");
}
}
}

Просмотреть файл

@ -13,9 +13,6 @@ namespace Microsoft.AspNet.Localization
/// <summary>
/// Determines the culture information for a request via the value of the Accept-Language header.
/// </summary>
/// <remarks>
///
/// </remarks>
public class AcceptLanguageHeaderRequestCultureStrategy : IRequestCultureStrategy
{
/// <summary>
@ -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) { }
}
}

Просмотреть файл

@ -3,16 +3,98 @@
using System;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Localization.Internal;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Localization
{
/// <summary>
/// Determines the culture information for a request via the value of a cookie.
/// </summary>
public class CookieRequestCultureStrategy : IRequestCultureStrategy
{
private static readonly char[] _cookieSeparator = new[] { '|' };
private static readonly string _culturePrefix = "c=";
private static readonly string _uiCulturePrefix = "uic=";
/// <summary>
/// The name of the cookie that contains the user's preferred culture information.
/// Defaults to <see cref="DefaultCookieName"/>.
/// </summary>
public string CookieName { get; set; } = DefaultCookieName;
/// <inheritdoc />
public RequestCulture DetermineRequestCulture([NotNull] HttpContext httpContext)
{
// TODO
return null;
var cookie = httpContext.Request.Cookies[CookieName];
if (cookie == null)
{
return null;
}
return ParseCookieValue(cookie);
}
/// <summary>
/// The default name of the cookie used to track the user's preferred culture information.
/// </summary>
public static string DefaultCookieName { get; } = "ASPNET_CULTURE";
/// <summary>
/// Creates a string representation of a <see cref="RequestCulture"/> for placement in a cookie.
/// </summary>
/// <param name="requestCulture">The <see cref="RequestCulture"/>.</param>
/// <returns>The cookie value.</returns>
public static string MakeCookieValue([NotNull] RequestCulture requestCulture)
{
var seperator = _cookieSeparator[0].ToString();
return string.Join(seperator,
$"{_culturePrefix}{requestCulture.Culture.Name}",
$"{_uiCulturePrefix}{requestCulture.UICulture.Name}");
}
/// <summary>
/// Parses a <see cref="RequestCulture"/> from the specified cookie value.
/// Returns <c>null</c> if parsing fails.
/// </summary>
/// <param name="value">The cookie value to parse.</param>
/// <returns>The <see cref="RequestCulture"/> or <c>null</c> if parsing fails.</returns>
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);
}
}
}

Просмотреть файл

@ -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<string, CacheEntry> _cache = new ConcurrentDictionary<string, CacheEntry>();
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; }
}
}
}

Просмотреть файл

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

Просмотреть файл

@ -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);
}
}
}

Просмотреть файл

@ -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
/// </summary>
public class RequestCulture
{
/// <summary>
/// Creates a new <see cref="RequestCulture"/> object has its <see cref="Culture"/> and <see cref="UICulture"/>
/// properties set to the same <see cref="CultureInfo"/> value.
/// </summary>
/// <param name="culture">The <see cref="CultureInfo"/> for the request.</param>
public RequestCulture([NotNull] CultureInfo culture)
private static readonly ConcurrentDictionary<CacheKey, RequestCulture> _cache = new ConcurrentDictionary<CacheKey, RequestCulture>();
private RequestCulture([NotNull] CultureInfo culture)
: this (culture, culture)
{
}
/// <summary>
/// Creates a new <see cref="RequestCulture"/> object has its <see cref="Culture"/> and <see cref="UICulture"/>
/// properties set to the respective <see cref="CultureInfo"/> values provided.
/// </summary>
/// <param name="culture">The <see cref="CultureInfo"/> for the request to be used for formatting.</param>
/// <param name="uiCulture">The <see cref="CultureInfo"/> for the request to be used for text, i.e. language.</param>
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 <see cref="CultureInfo"/> for the request to be used for text, i.e. language;
/// </summary>
public CultureInfo UICulture { get; }
/// <summary>
/// Gets a cached <see cref="RequestCulture"/> instance that has its <see cref="Culture"/> and <see cref="UICulture"/>
/// properties set to the same <see cref="CultureInfo"/> value.
/// </summary>
/// <param name="culture">The <see cref="CultureInfo"/> for the request.</param>
public static RequestCulture GetRequestCulture([NotNull] CultureInfo culture)
{
return GetRequestCulture(culture, culture);
}
/// <summary>
/// Gets a cached <see cref="RequestCulture"/> instance that has its <see cref="Culture"/> and <see cref="UICulture"/>
/// properties set to the respective <see cref="CultureInfo"/> values provided.
/// </summary>
/// <param name="culture">The <see cref="CultureInfo"/> for the request to be used for formatting.</param>
/// <param name="uiCulture">The <see cref="CultureInfo"/> for the request to be used for text, i.e. language.</param>
/// <returns></returns>
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;
}
}
}
}

Просмотреть файл

@ -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);
}
}

Просмотреть файл

@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Localization
/// </summary>
public RequestLocalizationMiddlewareOptions()
{
DefaultRequestCulture = new RequestCulture(CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture);
DefaultRequestCulture = RequestCulture.GetRequestCulture(CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture);
RequestCultureStrategies = new List<IRequestCultureStrategy>
{

Просмотреть файл

@ -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-*",