Skip to content

Commit

Permalink
Implemented CookieRequestCultureStrategy & other changes:
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
DamianEdwards committed May 8, 2015
1 parent ec8ede5 commit 944c84b
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 62 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
[Bb]in/
TestResults/
.nuget/
.vs/
_ReSharper.*/
packages/
artifacts/
Expand All @@ -25,4 +26,4 @@ nuget.exe
*.ipch
*.sln.ide
debugSettings.json
project.lock.json
project.lock.json
30 changes: 26 additions & 4 deletions samples/LocalizationSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,15 @@ public void Configure(IApplicationBuilder app, IStringLocalizer<Startup> SR)
};
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";

Expand All @@ -39,12 +46,25 @@ await context.Response.WriteAsync(
$@"<!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>");
Expand All @@ -57,8 +77,9 @@ await context.Response.WriteAsync(
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>");
Expand Down Expand Up @@ -102,6 +123,7 @@ 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>");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -53,11 +50,11 @@ public RequestCulture DetermineRequestCulture([NotNull] HttpContext httpContext)
// 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) { }
}
}

Expand Down
86 changes: 84 additions & 2 deletions src/Microsoft.AspNet.Localization/CookieRequestCultureStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
60 changes: 60 additions & 0 deletions src/Microsoft.AspNet.Localization/Internal/CultureInfoCache.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
}
29 changes: 0 additions & 29 deletions src/Microsoft.AspNet.Localization/Internal/CultureUtilities.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,15 @@ public RequestCulture DetermineRequestCulture([NotNull] HttpContext httpContext)
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);
}
}
}
Loading

0 comments on commit 944c84b

Please sign in to comment.