зеркало из https://github.com/aspnet/Localization.git
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:
Родитель
ec8ede5d8a
Коммит
944c84bc5d
|
@ -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-*",
|
||||
|
|
Загрузка…
Ссылка в новой задаче