-
Notifications
You must be signed in to change notification settings - Fork 65
Added support for get culture information from request's route data i… #288
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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")] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// 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; | ||
|
||
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"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a new options There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Experience here is in line with existing culture providers. So I do not want to do anything different with this new provider. |
||
|
||
/// <inheritdoc /> | ||
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext) | ||
{ | ||
if (httpContext == null) | ||
{ | ||
throw new ArgumentNullException(nameof(httpContext)); | ||
} | ||
|
||
string culture = null; | ||
string uiCulture = null; | ||
|
||
if (!string.IsNullOrWhiteSpace(RouteDataStringKey)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't check white space. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No description provided. |
||
{ | ||
culture = httpContext.GetRouteValue(RouteDataStringKey) as string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
|
||
if (!string.IsNullOrWhiteSpace(UIRouteDataStringKey)) | ||
{ | ||
uiCulture = httpContext.GetRouteValue(UIRouteDataStringKey) as string; | ||
} | ||
|
||
if (culture == null && uiCulture == null) | ||
{ | ||
// No values specified for either so no match | ||
return Task.FromResult((ProviderCultureResult)null); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
} | ||
|
||
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); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"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": "1.1.0-*" | ||
}, | ||
"frameworks": { | ||
"net451": {}, | ||
"netstandard1.3": {} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same thing here add we can adds the test to the |
||
<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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.Threading.Tasks; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.AspNetCore.Routing; | ||
using Microsoft.AspNetCore.TestHost; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Xunit; | ||
using System.Net; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please sort the namesapces |
||
|
||
namespace Microsoft.AspNetCore.Localization.Routing | ||
{ | ||
public class RouteDataRequestCultureProviderTest | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 3 tests in this class seem to share a lot of boilerplate. Can any of that be moved into helper methods to reduce the repetition and make the differences between the setups more obvious? |
||
{ | ||
[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) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can subApp instead There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do call this a forked pipeline. But |
||
{ | ||
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[] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplify this with options.RequestCultureProviders.Insert(0, new RouteDataRequestCultureProvider()
{
Options = options
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add new line here |
||
{ | ||
new RouteDataRequestCultureProvider() | ||
{ | ||
Options = options | ||
} | ||
}; | ||
fork.UseRequestLocalization(options); | ||
|
||
fork.Run(context => | ||
{ | ||
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>(); | ||
var requestCulture = requestCultureFeature.RequestCulture; | ||
return context.Response.WriteAsync( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add new line here |
||
$"{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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add new line here |
||
} | ||
} | ||
|
||
[Fact] | ||
public async Task GetDefaultCultureInfo_IfCultureKeysAreMissing() | ||
{ | ||
var builder = new WebHostBuilder() | ||
.Configure(app => | ||
{ | ||
var options = new RequestLocalizationOptions | ||
{ | ||
DefaultRequestCulture = new RequestCulture("en-US") | ||
}; | ||
options.RequestCultureProviders = new[] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as my previous suggestion There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add new line here |
||
{ | ||
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}"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: lessen the indenting |
||
}); | ||
}); | ||
|
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add new line here |
||
} | ||
} | ||
|
||
[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) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
{ | ||
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[] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can replace it with options.RequestCultureProviders.Insert(0, new RouteDataRequestCultureProvider()
{
Options = options,
RouteDataStringKey = "c",
UIRouteDataStringKey = "uic"
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as previous suggestion There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add new line here |
||
{ | ||
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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add new line here |
||
$"{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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pleas add new line here |
||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why you're creating a new project for Routing? It will be nice to follow the structure that's already used in the repo. I think adding
RouteDataRequestCultureProvider.cs
is appropriate choiceThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We want to avoid dependency of Routing in core localization package as it does not depend on routing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Valid point