Skip to content
This repository was archived by the owner on Nov 29, 2018. It is now read-only.

Added support for get culture information from request's route data i… #288

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Localization.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
Copy link
Contributor

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 choice

Copy link
Member Author

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid point

<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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a new options class.

Copy link
Member Author

Choose a reason for hiding this comment

The 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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't check white space.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No description provided.

{
culture = httpContext.GetRouteValue(RouteDataStringKey) as string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use ToString() here?

Copy link
Contributor

@dougbu dougbu Sep 26, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hishamco good idea. @kichalla why didn't you make this change and do the same thing for uiCulture?

}

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Microsoft.Extensions.TaskCache.Sources to avoid creating this Task in every request.

}

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);
}
}
}
30 changes: 30 additions & 0 deletions src/Microsoft.AspNetCore.Localization.Routing/project.json
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"?>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here add we can adds the test to the Microsoft.AspNetCore.Localization.Tests project

<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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sort the namesapces


namespace Microsoft.AspNetCore.Localization.Routing
{
public class RouteDataRequestCultureProviderTest
Copy link
Contributor

Choose a reason for hiding this comment

The 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) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fork ??

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can subApp instead

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do call this a forked pipeline. But subApp is also commonly used.

{
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[]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify this with

options.RequestCultureProviders.Insert(0, new RouteDataRequestCultureProvider()
                                {
                                    Options = options
                                });

Copy link
Contributor

Choose a reason for hiding this comment

The 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(
Copy link
Contributor

Choose a reason for hiding this comment

The 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);
Copy link
Contributor

Choose a reason for hiding this comment

The 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[]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as my previous suggestion

Copy link
Contributor

Choose a reason for hiding this comment

The 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}");
Copy link
Contributor

Choose a reason for hiding this comment

The 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);
Copy link
Contributor

Choose a reason for hiding this comment

The 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) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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[]
Copy link
Contributor

Choose a reason for hiding this comment

The 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"
                                });

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as previous suggestion

Copy link
Contributor

Choose a reason for hiding this comment

The 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(
Copy link
Contributor

Choose a reason for hiding this comment

The 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pleas add new line here

}
}
}
}
Loading