Added LanguageViewLocationExpander into framework and removed it from samples

This commit is contained in:
Kirthi Krishnamraju 2015-06-02 14:34:44 -07:00
parent ffd1dc1fb0
commit 30d11a8227
12 changed files with 258 additions and 155 deletions

View File

@ -1,69 +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;
using System.Collections.Generic;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Razor;
namespace MvcSample.Web
{
/// <summary>
/// A <see cref="IViewLocationExpander"/> that replaces adds the language as an extension prefix to view names.
/// </summary>
/// <example>
/// For the default case with no areas, views are generated with the following patterns (assuming controller is
/// "Home", action is "Index" and language is "en")
/// Views/Home/en/Action
/// Views/Home/Action
/// Views/Shared/en/Action
/// Views/Shared/Action
/// </example>
public class LanguageViewLocationExpander : IViewLocationExpander
{
private const string ValueKey = "language";
private readonly Func<ActionContext, string> _valueFactory;
/// <summary>
/// Initializes a new instance of <see cref="LanguageViewLocationExpander"/>.
/// </summary>
/// <param name="valueFactory">A factory that provides tbe language to use for expansion.</param>
public LanguageViewLocationExpander(Func<ActionContext, string> valueFactory)
{
_valueFactory = valueFactory;
}
/// <inheritdoc />
public void PopulateValues(ViewLocationExpanderContext context)
{
var value = _valueFactory(context.ActionContext);
if (!string.IsNullOrEmpty(value))
{
context.Values[ValueKey] = value;
}
}
/// <inheritdoc />
public virtual IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context,
IEnumerable<string> viewLocations)
{
string value;
if (context.Values.TryGetValue(ValueKey, out value))
{
return ExpandViewLocationsCore(viewLocations, value);
}
return viewLocations;
}
private IEnumerable<string> ExpandViewLocationsCore(IEnumerable<string> viewLocations,
string value)
{
foreach (var location in viewLocations)
{
yield return location.Replace("{0}", value + "/{0}");
yield return location;
}
}
}
}

View File

@ -46,6 +46,8 @@ namespace MvcSample.Web
options.Filters.Add(new FormatFilterAttribute());
});
services.AddMvcLocalization();
#if DNX451
// Fully-qualify configuration path to avoid issues in functional tests. Just "config.json" would be fine
// but Configuration uses CallContextServiceLocator.Locator.ServiceProvider to get IApplicationEnvironment.
@ -63,12 +65,6 @@ namespace MvcSample.Web
diSystem.Equals("AutoFac", StringComparison.OrdinalIgnoreCase))
{
_autoFac = true;
services.ConfigureRazorViewEngine(options =>
{
var expander = new LanguageViewLocationExpander(
context => context.HttpContext.Request.Query["language"]);
options.ViewLocationExpanders.Insert(0, expander);
});
// Create the autofac container
var builder = new ContainerBuilder();
@ -102,6 +98,7 @@ namespace MvcSample.Web
app.UseMiddleware<MonitoringMiddlware>();
}
#endif
app.UseRequestLocalization();
app.UseInMemorySession();
app.UseMvc(routes =>

View File

@ -9,6 +9,7 @@
"dependencies": {
"Kestrel": "1.0.0-*",
"Microsoft.AspNet.Diagnostics": "1.0.0-*",
"Microsoft.AspNet.Localization": "1.0.0-*",
"Microsoft.AspNet.Mvc": "6.0.0-*",
"Microsoft.AspNet.Mvc.Xml": "6.0.0-*",
"Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*",

View File

@ -0,0 +1,83 @@
// 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.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// A <see cref="IViewLocationExpander"/> that adds the language as an extension prefix to view names. Language
/// that is getting added as extension prefix comes from <see cref="Microsoft.AspNet.Http.HttpContext"/>.
/// </summary>
/// <example>
/// For the default case with no areas, views are generated with the following patterns (assuming controller is
/// "Home", action is "Index" and language is "en")
/// Views/Home/en/Action
/// Views/Home/Action
/// Views/Shared/en/Action
/// Views/Shared/Action
/// </example>
public class LanguageViewLocationExpander : IViewLocationExpander
{
private const string ValueKey = "language";
/// <inheritdoc />
public void PopulateValues([NotNull] ViewLocationExpanderContext context)
{
// Using CurrentUICulture so it loads the locale specific resources for the views.
#if DNX451
context.Values[ValueKey] = Thread.CurrentThread.CurrentUICulture.Name;
#else
context.Values[ValueKey] = CultureInfo.CurrentUICulture.Name;
#endif
}
/// <inheritdoc />
public virtual IEnumerable<string> ExpandViewLocations(
[NotNull] ViewLocationExpanderContext context,
[NotNull] IEnumerable<string> viewLocations)
{
string value;
context.Values.TryGetValue(ValueKey, out value);
if (!string.IsNullOrEmpty(value))
{
CultureInfo culture;
try
{
culture = new CultureInfo(value);
}
catch (CultureNotFoundException)
{
return viewLocations;
}
return ExpandViewLocationsCore(viewLocations, culture);
}
return viewLocations;
}
private IEnumerable<string> ExpandViewLocationsCore(IEnumerable<string> viewLocations, CultureInfo cultureInfo)
{
foreach (var location in viewLocations)
{
var temporaryCultureInfo = cultureInfo;
while (temporaryCultureInfo != temporaryCultureInfo.Parent)
{
yield return location.Replace("{0}", temporaryCultureInfo.Name + "/{0}");
temporaryCultureInfo = temporaryCultureInfo.Parent;
}
yield return location;
}
}
}
}

View File

@ -254,6 +254,16 @@ namespace Microsoft.Framework.DependencyInjection
return WithControllersAsServices(services, controllerTypes.Select(type => type.AsType()));
}
public static IServiceCollection AddMvcLocalization([NotNull] this IServiceCollection services)
{
services.ConfigureRazorViewEngine(options =>
{
options.ViewLocationExpanders.Add(new LanguageViewLocationExpander());
});
return services;
}
private static void ConfigureDefaultServices(IServiceCollection services)
{
services.AddOptions();

View File

@ -60,7 +60,7 @@ False";
<ValidationInView class=""field-validation-error"" data-valmsg-for=""Error"" data-valmsg-replace=""true"">An error occurred.</ValidationInView>
<input id=""Prefix!Property1"" name=""Prefix.Property1"" type=""text"" value="""" />
<div class=""editor-label""><label for=""MyDate"">MyDate</label></div>
<div class=""editor-field""><input class=""text-box single-line"" data-val=""true"" data-val-required=""The MyDate field is required."" id=""MyDate"" name=""MyDate"" type=""datetime"" value=""02/01/2000 03:04:05 &#x2B;00:00"" /> <ValidationInView class=""field-validation-valid"" data-valmsg-for=""MyDate"" data-valmsg-replace=""true""></ValidationInView></div>
<div class=""editor-field""><input class=""text-box single-line"" data-val=""true"" data-val-required=""The MyDate field is required."" id=""MyDate"" name=""MyDate"" type=""datetime"" value=""1/2/2000 3:04:05 AM &#x2B;00:00"" /> <ValidationInView class=""field-validation-valid"" data-valmsg-for=""MyDate"" data-valmsg-replace=""true""></ValidationInView></div>
True
@ -70,7 +70,7 @@ True
<ValidationInPartialView class=""field-validation-error"" data-valmsg-for=""Error"" data-valmsg-replace=""true"">An error occurred.</ValidationInPartialView>
<input id=""Prefix!Property1"" name=""Prefix.Property1"" type=""text"" value="""" />
<div class=""editor-label""><label for=""MyDate"">MyDate</label></div>
<div class=""editor-field""><input class=""text-box single-line"" id=""MyDate"" name=""MyDate"" type=""datetime"" value=""02/01/2000 03:04:05 &#x2B;00:00"" /> <ValidationInPartialView class=""field-validation-valid"" data-valmsg-for=""MyDate"" data-valmsg-replace=""true""></ValidationInPartialView></div>
<div class=""editor-field""><input class=""text-box single-line"" id=""MyDate"" name=""MyDate"" type=""datetime"" value=""1/2/2000 3:04:05 AM &#x2B;00:00"" /> <ValidationInPartialView class=""field-validation-valid"" data-valmsg-for=""MyDate"" data-valmsg-replace=""true""></ValidationInPartialView></div>
True";

View File

@ -7,6 +7,7 @@ using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Net.Http.Headers;
using RazorWebSite;
using Xunit;
@ -138,7 +139,7 @@ component-content";
var expected1 = string.Join(Environment.NewLine,
"expander-index",
"gb-partial");
yield return new[] { "gb", expected1 };
yield return new[] { "en-GB", expected1 };
var expected2 = string.Join(Environment.NewLine,
"fr-index",
@ -159,10 +160,13 @@ component-content";
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var cultureCookie = "c=" + value + "|uic=" + value;
client.DefaultRequestHeaders.Add(
"Cookie",
new CookieHeaderValue("ASPNET_CULTURE", cultureCookie).ToString());
// Act
var body = await client.GetStringAsync("http://localhost/TemplateExpander?language-expander-value=" +
value);
var body = await client.GetStringAsync("http://localhost/TemplateExpander");
// Assert
Assert.Equal(expected, body.Trim());
@ -288,7 +292,7 @@ ViewWithNestedLayout-Content
View With Layout
</language-layout>";
yield return new[] { "gb", expected1 };
yield return new[] { "en-GB", expected1 };
yield return new[] { "na", expected1 };
var expected2 =
@ -307,10 +311,13 @@ View With Layout
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var cultureCookie = "c=" + value + "|uic=" + value;
client.DefaultRequestHeaders.Add(
"Cookie",
new CookieHeaderValue("ASPNET_CULTURE", cultureCookie).ToString());
// Act
var body = await client.GetStringAsync("http://localhost/TemplateExpander/ViewWithLayout?language-expander-value=" +
value);
var body = await client.GetStringAsync("http://localhost/TemplateExpander/ViewWithLayout");
// Assert
Assert.Equal(expected, body.Trim());

View File

@ -0,0 +1,140 @@
// 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 Xunit;
namespace Microsoft.AspNet.Mvc.Razor
{
public class LanguageViewLocationExpanderTest
{
public static IEnumerable<object[]> ViewLocationExpanderTestDataWithExpectedValues
{
get
{
yield return new object[]
{
new[]
{
"/Views/{1}/{0}.cshtml",
"/Views/Shared/{0}.cshtml"
},
new[]
{
"/Views/{1}/en-GB/{0}.cshtml",
"/Views/{1}/en/{0}.cshtml",
"/Views/{1}/{0}.cshtml",
"/Views/Shared/en-GB/{0}.cshtml",
"/Views/Shared/en/{0}.cshtml",
"/Views/Shared/{0}.cshtml"
}
};
yield return new object[]
{
new[]
{
"/Areas/{2}/Views/{1}/{0}.cshtml",
"/Areas/{2}/Views/Shared/{0}.cshtml",
"/Views/Shared/{0}.cshtml"
},
new[]
{
"/Areas/{2}/Views/{1}/en-GB/{0}.cshtml",
"/Areas/{2}/Views/{1}/en/{0}.cshtml",
"/Areas/{2}/Views/{1}/{0}.cshtml",
"/Areas/{2}/Views/Shared/en-GB/{0}.cshtml",
"/Areas/{2}/Views/Shared/en/{0}.cshtml",
"/Areas/{2}/Views/Shared/{0}.cshtml",
"/Views/Shared/en-GB/{0}.cshtml",
"/Views/Shared/en/{0}.cshtml",
"/Views/Shared/{0}.cshtml"
}
};
}
}
public static IEnumerable<object[]> ViewLocationExpanderTestData
{
get
{
yield return new object[]
{
new[]
{
"/Views/{1}/{0}.cshtml",
"/Views/Shared/{0}.cshtml"
}
};
yield return new object[]
{
new[]
{
"/Areas/{2}/Views/{1}/{0}.cshtml",
"/Areas/{2}/Views/Shared/{0}.cshtml",
"/Views/Shared/{0}.cshtml"
}
};
}
}
[Theory]
[MemberData(nameof(ViewLocationExpanderTestDataWithExpectedValues))]
public void ExpandViewLocations_SpecificLocale(
IEnumerable<string> viewLocations,
IEnumerable<string> expectedViewLocations)
{
// Arrange
var viewLocationExpanderContext = new ViewLocationExpanderContext(new ActionContext(),"testView", false);
var languageViewLocationExpander = new LanguageViewLocationExpander();
viewLocationExpanderContext.Values = new Dictionary<string, string>();
viewLocationExpanderContext.Values["language"] = "en-GB";
// Act
var expandedViewLocations = languageViewLocationExpander.ExpandViewLocations(
viewLocationExpanderContext,
viewLocations);
// Assert
Assert.Equal(expectedViewLocations, expandedViewLocations);
}
[Theory]
[MemberData(nameof(ViewLocationExpanderTestData))]
public void ExpandViewLocations_NullContextValue(IEnumerable<string> viewLocations)
{
// Arrange
var viewLocationExpanderContext = new ViewLocationExpanderContext(new ActionContext(), "testView", false);
var languageViewLocationExpander = new LanguageViewLocationExpander();
viewLocationExpanderContext.Values = new Dictionary<string, string>();
// Act
var expandedViewLocations = languageViewLocationExpander.ExpandViewLocations(
viewLocationExpanderContext,
viewLocations);
// Assert
Assert.Equal(viewLocations, expandedViewLocations);
}
[Theory]
[MemberData(nameof(ViewLocationExpanderTestData))]
public void ExpandViewLocations_IncorrectLocaleContextValue(IEnumerable<string> viewLocations)
{
// Arrange
var viewLocationExpanderContext = new ViewLocationExpanderContext(new ActionContext(), "testView", false);
var languageViewLocationExpander = new LanguageViewLocationExpander();
viewLocationExpanderContext.Values = new Dictionary<string, string>();
viewLocationExpanderContext.Values["language"] = "gb";
// Act
var expandedViewLocations = languageViewLocationExpander.ExpandViewLocations(
viewLocationExpanderContext,
viewLocations);
// Assert
Assert.Equal(viewLocations, expandedViewLocations);
}
}
}

View File

@ -1,69 +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;
using System.Collections.Generic;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Razor;
namespace RazorWebSite
{
/// <summary>
/// A <see cref="IViewLocationExpander"/> that replaces adds the language as an extension prefix to view names.
/// </summary>
/// <example>
/// For the default case with no areas, views are generated with the following patterns (assuming controller is
/// "Home", action is "Index" and language is "en")
/// Views/Home/en/Action
/// Views/Home/Action
/// Views/Shared/en/Action
/// Views/Shared/Action
/// </example>
public class LanguageViewLocationExpander : IViewLocationExpander
{
private const string ValueKey = "language";
private readonly Func<ActionContext, string> _valueFactory;
/// <summary>
/// Initializes a new instance of <see cref="LanguageViewLocationExpander"/>.
/// </summary>
/// <param name="valueFactory">A factory that provides tbe language to use for expansion.</param>
public LanguageViewLocationExpander(Func<ActionContext, string> valueFactory)
{
_valueFactory = valueFactory;
}
/// <inheritdoc />
public void PopulateValues(ViewLocationExpanderContext context)
{
var value = _valueFactory(context.ActionContext);
if (!string.IsNullOrEmpty(value))
{
context.Values[ValueKey] = value;
}
}
/// <inheritdoc />
public virtual IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context,
IEnumerable<string> viewLocations)
{
string value;
if (context.Values.TryGetValue(ValueKey, out value))
{
return ExpandViewLocationsCore(viewLocations, value);
}
return viewLocations;
}
private IEnumerable<string> ExpandViewLocationsCore(IEnumerable<string> viewLocations,
string value)
{
foreach (var location in viewLocations)
{
yield return location.Replace("{0}", value + "/{0}");
yield return location;
}
}
}
}

View File

@ -1,7 +1,9 @@
// 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;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Localization;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Razor;
using Microsoft.Framework.DependencyInjection;
@ -20,9 +22,6 @@ namespace RazorWebSite
services.AddTransient<FrameworkSpecificHelper>();
services.Configure<RazorViewEngineOptions>(options =>
{
var expander = new LanguageViewLocationExpander(
context => context.HttpContext.Request.Query["language-expander-value"]);
options.ViewLocationExpanders.Add(expander);
options.ViewLocationExpanders.Add(new CustomPartialDirectoryViewLocationExpander());
});
services.ConfigureMvc(options =>
@ -33,12 +32,15 @@ namespace RazorWebSite
options.HtmlHelperOptions.ValidationMessageElement = "validationMessageElement";
options.HtmlHelperOptions.ValidationSummaryMessageElement = "validationSummaryElement";
});
services.AddMvcLocalization();
}
public void Configure(IApplicationBuilder app)
{
app.UseCultureReplacer();
app.UseRequestLocalization();
// Add MVC to the request pipeline
app.UseMvcWithDefaultRoute();
}

View File

@ -5,6 +5,7 @@
},
"dependencies": {
"Kestrel": "1.0.0-*",
"Microsoft.AspNet.Localization": "1.0.0-*",
"Microsoft.AspNet.Mvc": "6.0.0-*",
"Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0",
"Microsoft.AspNet.Server.IIS": "1.0.0-*",