Added support for get culture information from request's route data information.

[Fixes #122] Get segment from mapped route for CustomRequestCultureProvider
This commit is contained in:
Kiran Challa 2016-09-24 12:55:04 -07:00
parent 07e7741463
commit 861ae52de5
12 changed files with 403 additions and 6 deletions

View File

@ -35,6 +35,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ResourcesClassLibraryWithAt
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ResourcesClassLibraryNoAttribute", "test\ResourcesClassLibraryNoAttribute\ResourcesClassLibraryNoAttribute.xproj", "{34740578-D5B5-4FB4-AFD4-5E87B5443E20}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Localization.Routing", "src\Microsoft.AspNetCore.Localization.Routing\Microsoft.AspNetCore.Localization.Routing.xproj", "{E1B8DA18-7885-40ED-94ED-881BB0804F23}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Localization.Routing.Tests", "test\Microsoft.AspNetCore.Localization.Routing.Tests\Microsoft.AspNetCore.Localization.Routing.Tests.xproj", "{375B000B-5DC0-4D16-AC0C-A5432D8E7581}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -85,6 +89,14 @@ Global
{34740578-D5B5-4FB4-AFD4-5E87B5443E20}.Debug|Any CPU.Build.0 = Debug|Any CPU
{34740578-D5B5-4FB4-AFD4-5E87B5443E20}.Release|Any CPU.ActiveCfg = Release|Any CPU
{34740578-D5B5-4FB4-AFD4-5E87B5443E20}.Release|Any CPU.Build.0 = Release|Any CPU
{E1B8DA18-7885-40ED-94ED-881BB0804F23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E1B8DA18-7885-40ED-94ED-881BB0804F23}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1B8DA18-7885-40ED-94ED-881BB0804F23}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1B8DA18-7885-40ED-94ED-881BB0804F23}.Release|Any CPU.Build.0 = Release|Any CPU
{375B000B-5DC0-4D16-AC0C-A5432D8E7581}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{375B000B-5DC0-4D16-AC0C-A5432D8E7581}.Debug|Any CPU.Build.0 = Debug|Any CPU
{375B000B-5DC0-4D16-AC0C-A5432D8E7581}.Release|Any CPU.ActiveCfg = Release|Any CPU
{375B000B-5DC0-4D16-AC0C-A5432D8E7581}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -101,5 +113,7 @@ Global
{B1B441BA-3AC8-49F8-850D-E5A178E77DE2} = {B723DB83-A670-4BCB-95FB-195361331AD2}
{F27639B9-913E-43AF-9D64-BBD98D9A420A} = {B723DB83-A670-4BCB-95FB-195361331AD2}
{34740578-D5B5-4FB4-AFD4-5E87B5443E20} = {B723DB83-A670-4BCB-95FB-195361331AD2}
{E1B8DA18-7885-40ED-94ED-881BB0804F23} = {FB313677-BAB3-4E49-8CDB-4FA4A9564767}
{375B000B-5DC0-4D16-AC0C-A5432D8E7581} = {B723DB83-A670-4BCB-95FB-195361331AD2}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0.25420" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25420</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>e1b8da18-7885-40ed-94ed-881bb0804f23</ProjectGuid>
<RootNamespace>Microsoft.AspNetCore.Localization.Routing</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,11 @@
// 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.Reflection;
using System.Resources;
[assembly: AssemblyMetadata("Serviceable", "True")]
[assembly: NeutralResourcesLanguage("en-us")]
[assembly: AssemblyCompany("Microsoft Corporation.")]
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
[assembly: AssemblyProduct("Microsoft ASP.NET Core")]

View File

@ -0,0 +1,74 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Localization.Routing
{
/// <summary>
/// Determines the culture information for a request via values in the route data.
/// </summary>
public class RouteDataRequestCultureProvider : RequestCultureProvider
{
/// <summary>
/// The key that contains the culture name.
/// Defaults to "culture".
/// </summary>
public string RouteDataStringKey { get; set; } = "culture";
/// <summary>
/// The key that contains the UI culture name. If not specified or no value is found,
/// <see cref="RouteDataStringKey"/> will be used.
/// Defaults to "ui-culture".
/// </summary>
public string UIRouteDataStringKey { get; set; } = "ui-culture";
/// <inheritdoc />
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
string culture = null;
string uiCulture = null;
if (!string.IsNullOrEmpty(RouteDataStringKey))
{
culture = httpContext.GetRouteValue(RouteDataStringKey)?.ToString();
}
if (!string.IsNullOrEmpty(UIRouteDataStringKey))
{
uiCulture = httpContext.GetRouteValue(UIRouteDataStringKey)?.ToString();
}
if (culture == null && uiCulture == null)
{
// No values specified for either so no match
return TaskCache<ProviderCultureResult>.DefaultCompletedTask;
}
if (culture != null && uiCulture == null)
{
// Value for culture but not for UI culture so default to culture value for both
uiCulture = culture;
}
if (culture == null && uiCulture != null)
{
// Value for UI culture but not for culture so default to UI culture value for both
culture = uiCulture;
}
var providerResultCulture = new ProviderCultureResult(culture, uiCulture);
return Task.FromResult(providerResultCulture);
}
}
}

View File

@ -0,0 +1,34 @@
{
"version": "1.1.0-*",
"description": "Provides a request culture provider which gets culture and ui-culture from request's route data.",
"packOptions": {
"repository": {
"type": "git",
"url": "https://github.com/aspnet/localization"
},
"tags": [
"aspnetcore",
"localization"
]
},
"buildOptions": {
"warningsAsErrors": true,
"keyFile": "../../tools/Key.snk",
"nowarn": [
"CS1591"
],
"xmlDoc": true
},
"dependencies": {
"Microsoft.AspNetCore.Localization": "1.1.0-*",
"Microsoft.AspNetCore.Routing.Abstractions": "1.1.0-*",
"Microsoft.Extensions.TaskCache.Sources": {
"type": "build",
"version": "1.1.0-*"
}
},
"frameworks": {
"net451": {},
"netstandard1.3": {}
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Localization
@ -33,7 +34,7 @@ namespace Microsoft.AspNetCore.Localization
if (acceptLanguageHeader == null || acceptLanguageHeader.Count == 0)
{
return Task.FromResult((ProviderCultureResult)null);
return TaskCache<ProviderCultureResult>.DefaultCompletedTask;
}
var languages = acceptLanguageHeader.AsEnumerable();
@ -53,7 +54,7 @@ namespace Microsoft.AspNetCore.Localization
return Task.FromResult(new ProviderCultureResult(orderedLanguages));
}
return Task.FromResult((ProviderCultureResult)null);
return TaskCache<ProviderCultureResult>.DefaultCompletedTask;
}
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Localization
{
@ -39,7 +40,7 @@ namespace Microsoft.AspNetCore.Localization
if (string.IsNullOrEmpty(cookie))
{
return Task.FromResult<ProviderCultureResult>(null);
return TaskCache<ProviderCultureResult>.DefaultCompletedTask;
}
var providerResultCulture = ParseCookieValue(cookie);

View File

@ -4,6 +4,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Localization
{
@ -36,7 +37,7 @@ namespace Microsoft.AspNetCore.Localization
var request = httpContext.Request;
if (!request.QueryString.HasValue)
{
return Task.FromResult((ProviderCultureResult)null);
return TaskCache<ProviderCultureResult>.DefaultCompletedTask;
}
string queryCulture = null;
@ -55,7 +56,7 @@ namespace Microsoft.AspNetCore.Localization
if (queryCulture == null && queryUICulture == null)
{
// No values specified for either so no match
return Task.FromResult((ProviderCultureResult)null);
return TaskCache<ProviderCultureResult>.DefaultCompletedTask;
}
if (queryCulture != null && queryUICulture == null)

View File

@ -23,7 +23,11 @@
"Microsoft.AspNetCore.Http.Extensions": "1.1.0-*",
"Microsoft.Extensions.Globalization.CultureInfoCache": "1.1.0-*",
"Microsoft.Extensions.Localization.Abstractions": "1.1.0-*",
"Microsoft.Extensions.Options": "1.1.0-*"
"Microsoft.Extensions.Options": "1.1.0-*",
"Microsoft.Extensions.TaskCache.Sources": {
"type": "build",
"version": "1.1.0-*"
}
},
"frameworks": {
"net451": {},

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0.25420" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25420</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>375b000b-5dc0-4d16-ac0c-a5432d8e7581</ProjectGuid>
<RootNamespace>Microsoft.AspNetCore.Localization.Routing.Tests</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,193 @@
// 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;
using System.Globalization;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Microsoft.AspNetCore.Localization.Routing
{
public class RouteDataRequestCultureProviderTest
{
[Theory]
[InlineData("{culture}/{ui-culture}/hello", "ar-SA/ar-YE/hello", "ar-SA", "ar-YE")]
[InlineData("{CULTURE}/{UI-CULTURE}/hello", "ar-SA/ar-YE/hello", "ar-SA", "ar-YE")]
[InlineData("{culture}/{ui-culture}/hello", "unsupported/unsupported/hello", "en-US", "en-US")]
[InlineData("{culture}/hello", "ar-SA/hello", "ar-SA", "en-US")]
[InlineData("hello", "hello", "en-US", "en-US")]
[InlineData("{ui-culture}/hello", "ar-YE/hello", "en-US", "ar-YE")]
public async Task GetCultureInfo_FromRouteData(
string routeTemplate,
string requestUrl,
string expectedCulture,
string expectedUICulture)
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRouter(routes =>
{
routes.MapRoute(routeTemplate, (IApplicationBuilder fork) =>
{
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-YE")
}
};
options.RequestCultureProviders = new[]
{
new RouteDataRequestCultureProvider()
{
Options = options
}
};
fork.UseRequestLocalization(options);
fork.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
return context.Response.WriteAsync(
$"{requestCulture.Culture.Name},{requestCulture.UICulture.Name}");
});
});
});
})
.ConfigureServices(services =>
{
services.AddRouting();
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync(requestUrl);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var data = await response.Content.ReadAsStringAsync();
Assert.Equal($"{expectedCulture},{expectedUICulture}", data);
}
}
[Fact]
public async Task GetDefaultCultureInfo_IfCultureKeysAreMissing()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US")
};
options.RequestCultureProviders = new[]
{
new RouteDataRequestCultureProvider()
{
Options = options
}
};
app.UseRequestLocalization(options);
app.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
return context.Response.WriteAsync(
$"{requestCulture.Culture.Name},{requestCulture.UICulture.Name}");
});
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync("/page");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var data = await response.Content.ReadAsStringAsync();
Assert.Equal("en-US,en-US", data);
}
}
[Theory]
[InlineData("{c}/{uic}/hello", "ar-SA/ar-YE/hello", "ar-SA", "ar-YE")]
[InlineData("{C}/{UIC}/hello", "ar-SA/ar-YE/hello", "ar-SA", "ar-YE")]
[InlineData("{c}/hello", "ar-SA/hello", "ar-SA", "en-US")]
[InlineData("hello", "hello", "en-US", "en-US")]
[InlineData("{uic}/hello", "ar-YE/hello", "en-US", "ar-YE")]
public async Task GetCultureInfo_FromRouteData_WithCustomKeys(
string routeTemplate,
string requestUrl,
string expectedCulture,
string expectedUICulture)
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseRouter(routes =>
{
routes.MapRoute(routeTemplate, (IApplicationBuilder fork) =>
{
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("ar-SA")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("ar-YE")
}
};
options.RequestCultureProviders = new[]
{
new RouteDataRequestCultureProvider()
{
Options = options,
RouteDataStringKey = "c",
UIRouteDataStringKey = "uic"
}
};
fork.UseRequestLocalization(options);
fork.Run(context =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
return context.Response.WriteAsync(
$"{requestCulture.Culture.Name},{requestCulture.UICulture.Name}");
});
});
});
})
.ConfigureServices(services =>
{
services.AddRouting();
});
using (var server = new TestServer(builder))
{
var client = server.CreateClient();
var response = await client.GetAsync(requestUrl);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var data = await response.Content.ReadAsStringAsync();
Assert.Equal($"{expectedCulture},{expectedUICulture}", data);
}
}
}
}

View File

@ -0,0 +1,24 @@
{
"buildOptions": {
"warningsAsErrors": true
},
"dependencies": {
"dotnet-test-xunit": "2.2.0-*",
"Microsoft.AspNetCore.Localization.Routing": "1.1.0-*",
"Microsoft.AspNetCore.Routing": "1.1.0-*",
"Microsoft.AspNetCore.TestHost": "1.1.0-*",
"xunit": "2.2.0-*"
},
"testRunner": "xunit",
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0-*",
"type": "platform"
}
}
},
"net451": {}
}
}