diff --git a/Localization.sln b/Localization.sln
index e641bd0819..3c4a667429 100644
--- a/Localization.sln
+++ b/Localization.sln
@@ -31,6 +31,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LocalizationWebsite", "test
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Localization.FunctionalTests", "test\Microsoft.AspNetCore.Localization.FunctionalTests\Microsoft.AspNetCore.Localization.FunctionalTests.xproj", "{B1B441BA-3AC8-49F8-850D-E5A178E77DE2}"
EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ResourcesClassLibraryWithAttribute", "test\ResourcesClassLibraryWithAttribute\ResourcesClassLibraryWithAttribute.xproj", "{F27639B9-913E-43AF-9D64-BBD98D9A420A}"
+EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ResourcesClassLibraryNoAttribute", "test\ResourcesClassLibraryNoAttribute\ResourcesClassLibraryNoAttribute.xproj", "{34740578-D5B5-4FB4-AFD4-5E87B5443E20}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -73,6 +77,14 @@ Global
{B1B441BA-3AC8-49F8-850D-E5A178E77DE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1B441BA-3AC8-49F8-850D-E5A178E77DE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1B441BA-3AC8-49F8-850D-E5A178E77DE2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F27639B9-913E-43AF-9D64-BBD98D9A420A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F27639B9-913E-43AF-9D64-BBD98D9A420A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F27639B9-913E-43AF-9D64-BBD98D9A420A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F27639B9-913E-43AF-9D64-BBD98D9A420A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {34740578-D5B5-4FB4-AFD4-5E87B5443E20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -87,5 +99,7 @@ Global
{19A2A931-5C60-47A0-816A-0DC9C4CE5736} = {B723DB83-A670-4BCB-95FB-195361331AD2}
{EF6C7431-2FB8-4396-8947-F50F31689AF4} = {B723DB83-A670-4BCB-95FB-195361331AD2}
{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}
EndGlobalSection
EndGlobal
diff --git a/src/Microsoft.Extensions.Localization/ResourceLocationAttribute.cs b/src/Microsoft.Extensions.Localization/ResourceLocationAttribute.cs
new file mode 100644
index 0000000000..5bf281d90e
--- /dev/null
+++ b/src/Microsoft.Extensions.Localization/ResourceLocationAttribute.cs
@@ -0,0 +1,33 @@
+// 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;
+
+namespace Microsoft.Extensions.Localization
+{
+ ///
+ /// Provides the location of resources for an Assembly.
+ ///
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
+ public class ResourceLocationAttribute : Attribute
+ {
+ ///
+ /// Creates a new .
+ ///
+ /// The location of resources for this Assembly.
+ public ResourceLocationAttribute(string resourceLocation)
+ {
+ if (string.IsNullOrEmpty(resourceLocation))
+ {
+ throw new ArgumentNullException(nameof(resourceLocation));
+ }
+
+ ResourceLocation = resourceLocation;
+ }
+
+ ///
+ /// The location of resources for this Assembly.
+ ///
+ public string ResourceLocation { get; }
+ }
+}
diff --git a/src/Microsoft.Extensions.Localization/ResourceManagerStringLocalizerFactory.cs b/src/Microsoft.Extensions.Localization/ResourceManagerStringLocalizerFactory.cs
index 95c672509c..e488019e87 100644
--- a/src/Microsoft.Extensions.Localization/ResourceManagerStringLocalizerFactory.cs
+++ b/src/Microsoft.Extensions.Localization/ResourceManagerStringLocalizerFactory.cs
@@ -14,12 +14,17 @@ namespace Microsoft.Extensions.Localization
///
/// An that creates instances of .
///
+ ///
+ /// offers multiple ways to set the relative path of
+ /// resources to be used. They are, in order of precedence:
+ /// -> -> the project root.
+ ///
public class ResourceManagerStringLocalizerFactory : IStringLocalizerFactory
{
private readonly IResourceNamesCache _resourceNamesCache = new ResourceNamesCache();
private readonly ConcurrentDictionary _localizerCache =
new ConcurrentDictionary();
- private readonly IHostingEnvironment _hostingEnvironment;
+ private readonly string _applicationName;
private readonly string _resourcesRelativePath;
///
@@ -41,7 +46,7 @@ namespace Microsoft.Extensions.Localization
throw new ArgumentNullException(nameof(localizationOptions));
}
- _hostingEnvironment = hostingEnvironment;
+ _applicationName = hostingEnvironment.ApplicationName;
_resourcesRelativePath = localizationOptions.Value.ResourcesPath ?? string.Empty;
if (!string.IsNullOrEmpty(_resourcesRelativePath))
{
@@ -62,7 +67,7 @@ namespace Microsoft.Extensions.Localization
throw new ArgumentNullException(nameof(typeInfo));
}
- return GetResourcePrefix(typeInfo, _hostingEnvironment.ApplicationName, _resourcesRelativePath);
+ return GetResourcePrefix(typeInfo, new AssemblyName(typeInfo.Assembly.FullName).Name, _resourcesRelativePath);
}
///
@@ -106,9 +111,16 @@ namespace Microsoft.Extensions.Localization
throw new ArgumentNullException(nameof(baseResourceName));
}
- var locationPath = baseNamespace == _hostingEnvironment.ApplicationName ?
- baseNamespace + "." + _resourcesRelativePath :
- baseNamespace + ".";
+ if (string.IsNullOrEmpty(baseNamespace))
+ {
+ throw new ArgumentNullException(nameof(baseNamespace));
+ }
+
+ var assemblyName = new AssemblyName(baseNamespace);
+ var assembly = Assembly.Load(assemblyName);
+ var resourceLocation = GetResourcePath(assembly);
+ var locationPath = baseNamespace + "." + resourceLocation;
+
baseResourceName = locationPath + TrimPrefix(baseResourceName, baseNamespace + ".");
return baseResourceName;
@@ -129,17 +141,12 @@ namespace Microsoft.Extensions.Localization
var typeInfo = resourceSource.GetTypeInfo();
var assembly = typeInfo.Assembly;
+ var assemblyName = new AssemblyName(assembly.FullName);
+ var resourcePath = GetResourcePath(assembly);
- // Re-root the base name if a resources path is set
- var baseName = GetResourcePrefix(typeInfo);
+ var baseName = GetResourcePrefix(typeInfo, assemblyName.Name, resourcePath);
- return _localizerCache.GetOrAdd(baseName, _ =>
- new ResourceManagerStringLocalizer(
- new ResourceManager(baseName, assembly),
- assembly,
- baseName,
- _resourceNamesCache)
- );
+ return _localizerCache.GetOrAdd(baseName, _ => CreateResourceManagerStringLocalizer(assembly, baseName));
}
///
@@ -155,21 +162,71 @@ namespace Microsoft.Extensions.Localization
throw new ArgumentNullException(nameof(baseName));
}
- location = location ?? _hostingEnvironment.ApplicationName;
-
- baseName = GetResourcePrefix(baseName, location);
+ location = location ?? _applicationName;
return _localizerCache.GetOrAdd($"B={baseName},L={location}", _ =>
{
- var assembly = Assembly.Load(new AssemblyName(location));
- return new ResourceManagerStringLocalizer(
- new ResourceManager(baseName, assembly),
- assembly,
- baseName,
- _resourceNamesCache);
+ var assemblyName = new AssemblyName(location);
+ var assembly = Assembly.Load(assemblyName);
+ baseName = GetResourcePrefix(baseName, location);
+
+ return CreateResourceManagerStringLocalizer(assembly, baseName);
});
}
+ /// Creates a for the given input.
+ /// The assembly to create a for.
+ /// The base name of the resource to search for.
+ /// A for the given and .
+ /// This method is virtual for testing purposes only.
+ protected virtual ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(
+ Assembly assembly,
+ string baseName)
+ {
+ return new ResourceManagerStringLocalizer(
+ new ResourceManager(baseName, assembly),
+ assembly,
+ baseName,
+ _resourceNamesCache);
+ }
+
+ ///
+ /// Gets the resource prefix used to look up the resource.
+ ///
+ /// The general location of the resource.
+ /// The base name of the resource.
+ /// The location of the resource within .
+ /// The resource prefix used to look up the resource.
+ protected virtual string GetResourcePrefix(string location, string baseName, string resourceLocation)
+ {
+ // Re-root the base name if a resources path is set
+ return location + "." + resourceLocation + TrimPrefix(baseName, location + ".");
+ }
+
+ /// Gets a from the provided .
+ /// The assembly to get a from.
+ /// The associated with the given .
+ /// This method is protected and virtual for testing purposes only.
+ protected virtual ResourceLocationAttribute GetResourceLocationAttribute(Assembly assembly)
+ {
+ return assembly.GetCustomAttribute();
+ }
+
+ private string GetResourcePath(Assembly assembly)
+ {
+ var resourceLocationAttribute = GetResourceLocationAttribute(assembly);
+
+ // If we don't have an attribute assume all assemblies use the same resource location.
+ var resourceLocation = resourceLocationAttribute == null
+ ? _resourcesRelativePath
+ : resourceLocationAttribute.ResourceLocation + ".";
+ resourceLocation = resourceLocation
+ .Replace(Path.DirectorySeparatorChar, '.')
+ .Replace(Path.AltDirectorySeparatorChar, '.');
+
+ return resourceLocation;
+ }
+
private static string TrimPrefix(string name, string prefix)
{
if (name.StartsWith(prefix, StringComparison.Ordinal))
diff --git a/src/Microsoft.Extensions.Localization/project.json b/src/Microsoft.Extensions.Localization/project.json
index 890d9c4e72..b0703ae070 100644
--- a/src/Microsoft.Extensions.Localization/project.json
+++ b/src/Microsoft.Extensions.Localization/project.json
@@ -22,7 +22,8 @@
"Microsoft.AspNetCore.Hosting.Abstractions": "1.1.0-*",
"Microsoft.Extensions.DependencyInjection.Abstractions": "1.1.0-*",
"Microsoft.Extensions.Localization.Abstractions": "1.1.0-*",
- "Microsoft.Extensions.Options": "1.1.0-*"
+ "Microsoft.Extensions.Options": "1.1.0-*",
+ "System.Reflection.Extensions": "4.0.1-*"
},
"frameworks": {
"net451": {},
diff --git a/test/LocalizationWebsite/StartupResourcesInClassLibrary.cs b/test/LocalizationWebsite/StartupResourcesInClassLibrary.cs
new file mode 100644
index 0000000000..2f4c42850b
--- /dev/null
+++ b/test/LocalizationWebsite/StartupResourcesInClassLibrary.cs
@@ -0,0 +1,70 @@
+// 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.Reflection;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Localization;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Localization;
+using Microsoft.Extensions.Logging;
+
+namespace LocalizationWebsite
+{
+ public class StartupResourcesInClassLibrary
+ {
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddLocalization(options => options.ResourcesPath = "Resources");
+ }
+
+ public void Configure(
+ IApplicationBuilder app,
+ ILoggerFactory loggerFactory,
+ IStringLocalizerFactory stringLocalizerFactory)
+ {
+ loggerFactory.AddConsole(minLevel: LogLevel.Warning);
+
+ var supportedCultures = new List()
+ {
+ new CultureInfo("en-US"),
+ new CultureInfo("fr-FR")
+ };
+
+ app.UseRequestLocalization(new RequestLocalizationOptions
+ {
+ DefaultRequestCulture = new RequestCulture("en-US"),
+ SupportedCultures = supportedCultures,
+ SupportedUICultures = supportedCultures
+ });
+
+ var noAttributeStringLocalizer = stringLocalizerFactory.Create(typeof(ResourcesClassLibraryNoAttribute.Model));
+ var withAttributeStringLocalizer = stringLocalizerFactory.Create(typeof(ResourcesClassLibraryWithAttribute.Model));
+
+ var noAttributeAssembly = typeof(ResourcesClassLibraryNoAttribute.Model).GetTypeInfo().Assembly;
+ var noAttributeName = new AssemblyName(noAttributeAssembly.FullName).Name;
+ var noAttributeNameStringLocalizer = stringLocalizerFactory.Create(
+ nameof(ResourcesClassLibraryNoAttribute.Model),
+ noAttributeName);
+
+ var withAttributeAssembly = typeof(ResourcesClassLibraryWithAttribute.Model).GetTypeInfo().Assembly;
+ var withAttributeName = new AssemblyName(withAttributeAssembly.FullName).Name;
+ var withAttributeNameStringLocalizer = stringLocalizerFactory.Create(
+ nameof(ResourcesClassLibraryWithAttribute.Model),
+ withAttributeName);
+
+ app.Run(async (context) =>
+ {
+ await context.Response.WriteAsync(noAttributeNameStringLocalizer["Hello"]);
+ await context.Response.WriteAsync(" ");
+ await context.Response.WriteAsync(noAttributeStringLocalizer["Hello"]);
+ await context.Response.WriteAsync(" ");
+ await context.Response.WriteAsync(withAttributeNameStringLocalizer["Hello"]);
+ await context.Response.WriteAsync(" ");
+ await context.Response.WriteAsync(withAttributeStringLocalizer["Hello"]);
+ });
+ }
+ }
+}
diff --git a/test/LocalizationWebsite/StartupResourcesInFolder.cs b/test/LocalizationWebsite/StartupResourcesInFolder.cs
index fa15bf8fc2..8643382dd9 100644
--- a/test/LocalizationWebsite/StartupResourcesInFolder.cs
+++ b/test/LocalizationWebsite/StartupResourcesInFolder.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Globalization;
+using System.Reflection;
using LocalizationWebsite.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
@@ -43,6 +44,9 @@ namespace LocalizationWebsite
});
var stringLocalizer = stringLocalizerFactory.Create("Test", location: null);
+ var assembly = typeof(StartupResourcesInFolder).GetTypeInfo().Assembly;
+ var assemblyName = new AssemblyName(assembly.FullName).Name;
+ var stringLocalizerExplicitLocation = stringLocalizerFactory.Create("Test", assemblyName);
app.Run(async (context) =>
{
@@ -51,6 +55,8 @@ namespace LocalizationWebsite
await context.Response.WriteAsync(stringLocalizer["Hello"]);
await context.Response.WriteAsync(" ");
await context.Response.WriteAsync(custromerStringLocalizer["Hello"]);
+ await context.Response.WriteAsync(" ");
+ await context.Response.WriteAsync(stringLocalizerExplicitLocation["Hello"]);
});
}
}
diff --git a/test/LocalizationWebsite/project.json b/test/LocalizationWebsite/project.json
index a5a4894f52..c44942c900 100644
--- a/test/LocalizationWebsite/project.json
+++ b/test/LocalizationWebsite/project.json
@@ -9,7 +9,9 @@
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0-*",
"Microsoft.Extensions.Configuration.CommandLine": "1.1.0-*",
"Microsoft.Extensions.Localization": "1.1.0-*",
- "Microsoft.Extensions.Logging.Console": "1.1.0-*"
+ "Microsoft.Extensions.Logging.Console": "1.1.0-*",
+ "ResourcesClassLibraryNoAttribute": "1.0.0-*",
+ "ResourcesClassLibraryWithAttribute": "1.0.0-*"
},
"frameworks": {
"netcoreapp1.0": {
diff --git a/test/Microsoft.AspNetCore.Localization.FunctionalTests/LocalizationTest.cs b/test/Microsoft.AspNetCore.Localization.FunctionalTests/LocalizationTest.cs
index 0130ff1f87..6ae327ef15 100644
--- a/test/Microsoft.AspNetCore.Localization.FunctionalTests/LocalizationTest.cs
+++ b/test/Microsoft.AspNetCore.Localization.FunctionalTests/LocalizationTest.cs
@@ -12,7 +12,43 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests
public class LocalizationTest
{
private static readonly string _applicationPath = Path.Combine("test", "LocalizationWebsite");
-
+
+ [ConditionalTheory]
+ [OSSkipCondition(OperatingSystems.Linux)]
+ [OSSkipCondition(OperatingSystems.MacOSX)]
+ [InlineData(RuntimeFlavor.Clr, "http://localhost:5070/", RuntimeArchitecture.x64)]
+ public Task Localization_ResourcesInClassLibrary_ReturnLocalizedValue_Windows(
+ RuntimeFlavor runtimeFlavor,
+ string applicationBaseUrl,
+ RuntimeArchitecture runtimeArchitecture)
+ {
+ var testRunner = new TestRunner(_applicationPath);
+ return testRunner.RunTestAndVerifyResponse(
+ runtimeFlavor,
+ runtimeArchitecture,
+ applicationBaseUrl,
+ "ResourcesInClassLibrary",
+ "fr-FR",
+ "Bonjour from ResourcesClassLibraryNoAttribute Bonjour from ResourcesClassLibraryNoAttribute Bonjour from ResourcesClassLibraryWithAttribute Bonjour from ResourcesClassLibraryWithAttribute");
+ }
+
+ [Theory]
+ [InlineData(RuntimeFlavor.CoreClr, "http://localhost:5071/", RuntimeArchitecture.x64)]
+ public Task Localization_ResourcesInClassLibrary_ReturnLocalizedValue_AllOS(
+ RuntimeFlavor runtimeFlavor,
+ string applicationBaseUrl,
+ RuntimeArchitecture runtimeArchitecture)
+ {
+ var testRunner = new TestRunner(_applicationPath);
+ return testRunner.RunTestAndVerifyResponse(
+ runtimeFlavor,
+ runtimeArchitecture,
+ applicationBaseUrl,
+ "ResourcesInClassLibrary",
+ "fr-FR",
+ "Bonjour from ResourcesClassLibraryNoAttribute Bonjour from ResourcesClassLibraryNoAttribute Bonjour from ResourcesClassLibraryWithAttribute Bonjour from ResourcesClassLibraryWithAttribute");
+ }
+
[ConditionalTheory]
[OSSkipCondition(OperatingSystems.Linux)]
[OSSkipCondition(OperatingSystems.MacOSX)]
@@ -30,7 +66,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests
applicationBaseUrl,
"ResourcesInFolder",
"fr-FR",
- "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder");
+ "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder Bonjour from Test in resources folder");
}
[ConditionalTheory]
@@ -50,7 +86,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests
applicationBaseUrl,
"ResourcesInFolder",
"fr-FR-test",
- "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder");
+ "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder Bonjour from Test in resources folder");
}
[ConditionalTheory]
@@ -70,7 +106,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests
applicationBaseUrl,
"ResourcesInFolder",
"fr-FR-test-again-too-deep-to-work",
- "Hello Hello Hello");
+ "Hello Hello Hello Hello");
}
[ConditionalFact]
@@ -85,7 +121,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests
"http://localhost:5072",
"ResourcesInFolder",
"fr-FR",
- "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder");
+ "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder Bonjour from Test in resources folder");
}
[ConditionalFact]
@@ -100,7 +136,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests
"http://localhost:5072",
"ResourcesInFolder",
"fr-FR-test",
- "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder");
+ "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder Bonjour from Test in resources folder");
}
[ConditionalFact]
@@ -115,7 +151,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests
"http://localhost:5073/",
"ResourcesInFolder",
"fr-FR",
- "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder");
+ "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder Bonjour from Test in resources folder");
}
[ConditionalFact]
@@ -130,7 +166,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests
"http://localhost:5073/",
"ResourcesInFolder",
"fr-FR-test",
- "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder");
+ "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder Bonjour from Test in resources folder");
}
[ConditionalTheory]
diff --git a/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs b/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs
index e06056af98..70032985a7 100644
--- a/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs
+++ b/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs
@@ -1,16 +1,78 @@
// 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.IO;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
-namespace Microsoft.Extensions.Localization.Test
+namespace Microsoft.Extensions.Localization.Tests
{
+ public class TestResourceManagerStringLocalizerFactory : ResourceManagerStringLocalizerFactory
+ {
+ private ResourceLocationAttribute _resourceLocationAttribute;
+
+ public Assembly Assembly { get; private set; }
+ public string BaseName { get; private set; }
+
+ public TestResourceManagerStringLocalizerFactory(
+ IHostingEnvironment hostingEnvironment,
+ IOptions localizationOptions,
+ ResourceLocationAttribute resourceLocationAttribute)
+ : base(hostingEnvironment, localizationOptions)
+ {
+ _resourceLocationAttribute = resourceLocationAttribute;
+ }
+
+ protected override ResourceLocationAttribute GetResourceLocationAttribute(Assembly assembly)
+ {
+ return _resourceLocationAttribute;
+ }
+
+ protected override ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(Assembly assembly, string baseName)
+ {
+ BaseName = baseName;
+ Assembly = assembly;
+
+ return base.CreateResourceManagerStringLocalizer(assembly, baseName);
+ }
+ }
+
public class ResourceManagerStringLocalizerFactoryTest
{
+ [Fact]
+ public void Create_OverloadsProduceSameResult()
+ {
+ // Arrange
+ var hostingEnvironment = new Mock();
+ hostingEnvironment.SetupGet(a => a.ApplicationName).Returns("TestApplication");
+ var locOptions = new LocalizationOptions();
+ var options = new Mock>();
+ options.Setup(o => o.Value).Returns(locOptions);
+
+ var resourceLocationAttribute = new ResourceLocationAttribute(Path.Combine("My", "Resources"));
+ var typeFactory = new TestResourceManagerStringLocalizerFactory(
+ hostingEnvironment.Object,
+ options.Object,
+ resourceLocationAttribute);
+ var stringFactory = new TestResourceManagerStringLocalizerFactory(
+ hostingEnvironment.Object,
+ options.Object,
+ resourceLocationAttribute);
+ var type = typeof(ResourceManagerStringLocalizerFactoryTest);
+ var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
+
+ // Act
+ typeFactory.Create(type);
+ stringFactory.Create(type.Name, assemblyName.Name);
+
+ // Assert
+ Assert.Equal(typeFactory.BaseName, stringFactory.BaseName);
+ Assert.Equal(typeFactory.Assembly.FullName, stringFactory.Assembly.FullName);
+ }
+
[Fact]
public void Create_FromType_ReturnsCachedResultForSameType()
{
@@ -49,6 +111,27 @@ namespace Microsoft.Extensions.Localization.Test
Assert.NotSame(result1, result2);
}
+ [Fact]
+ public void Create_FromType_ResourcesPathDirectorySeperatorToDot()
+ {
+ // Arrange
+ var hostingEnvironment = new Mock();
+ var locOptions = new LocalizationOptions();
+ locOptions.ResourcesPath = Path.Combine("My", "Resources");
+ var options = new Mock>();
+ options.Setup(o => o.Value).Returns(locOptions);
+ var factory = new TestResourceManagerStringLocalizerFactory(
+ hostingEnvironment.Object,
+ options.Object,
+ resourceLocationAttribute: null);
+
+ // Act
+ factory.Create(typeof(ResourceManagerStringLocalizerFactoryTest));
+
+ // Assert
+ Assert.Equal("Microsoft.Extensions.Localization.Tests.My.Resources." + nameof(ResourceManagerStringLocalizerFactoryTest), factory.BaseName);
+ }
+
[Fact]
public void Create_FromNameLocation_ReturnsCachedResultForSameNameLocation()
{
@@ -99,8 +182,8 @@ namespace Microsoft.Extensions.Localization.Test
var options = new Mock>();
options.Setup(o => o.Value).Returns(locOptions);
var factory = new ResourceManagerStringLocalizerFactory(hostingEnvironment.Object, localizationOptions: options.Object);
- var location1 = typeof(ResourceManagerStringLocalizer).GetTypeInfo().Assembly.FullName;
- var location2 = typeof(ResourceManagerStringLocalizerFactoryTest).GetTypeInfo().Assembly.FullName;
+ var location1 = new AssemblyName(typeof(ResourceManagerStringLocalizer).GetTypeInfo().Assembly.FullName).Name;
+ var location2 = new AssemblyName(typeof(ResourceManagerStringLocalizerFactoryTest).GetTypeInfo().Assembly.FullName).Name;
// Act
var result1 = factory.Create("baseName", location1);
@@ -109,5 +192,46 @@ namespace Microsoft.Extensions.Localization.Test
// Assert
Assert.NotSame(result1, result2);
}
+
+ [Fact]
+ public void Create_FromNameLocation_ResourcesPathDirectorySeparatorToDot()
+ {
+ // Arrange
+ var hostingEnvironment = new Mock();
+ hostingEnvironment.SetupGet(a => a.ApplicationName).Returns("Microsoft.Extensions.Localization.Tests");
+ var locOptions = new LocalizationOptions();
+ locOptions.ResourcesPath = Path.Combine("My", "Resources");
+ var options = new Mock>();
+ options.Setup(o => o.Value).Returns(locOptions);
+ var factory = new TestResourceManagerStringLocalizerFactory(
+ hostingEnvironment.Object,
+ options.Object,
+ resourceLocationAttribute: null);
+
+ // Act
+ var result1 = factory.Create("baseName", location: null);
+
+ // Assert
+ Assert.Equal("Microsoft.Extensions.Localization.Tests.My.Resources.baseName", factory.BaseName);
+ }
+
+ [Fact]
+ public void Create_FromNameLocation_NullLocationUsesApplicationPath()
+ {
+ // Arrange
+ var hostingEnvironment = new Mock();
+ hostingEnvironment.SetupGet(a => a.ApplicationName).Returns("Microsoft.Extensions.Localization.Tests");
+ var locOptions = new LocalizationOptions();
+ var options = new Mock>();
+ options.Setup(o => o.Value).Returns(locOptions);
+ var factory = new ResourceManagerStringLocalizerFactory(hostingEnvironment.Object, localizationOptions: options.Object);
+
+ // Act
+ var result1 = factory.Create("baseName", location: null);
+ var result2 = factory.Create("baseName", location: null);
+
+ // Assert
+ Assert.Same(result1, result2);
+ }
}
}
diff --git a/test/ResourcesClassLibraryNoAttribute/Model.cs b/test/ResourcesClassLibraryNoAttribute/Model.cs
new file mode 100644
index 0000000000..dc85c2440c
--- /dev/null
+++ b/test/ResourcesClassLibraryNoAttribute/Model.cs
@@ -0,0 +1,12 @@
+// 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.
+
+namespace ResourcesClassLibraryNoAttribute
+{
+ public class Model
+ {
+ public Model()
+ {
+ }
+ }
+}
diff --git a/test/ResourcesClassLibraryNoAttribute/Properties/AssemblyInfo.cs b/test/ResourcesClassLibraryNoAttribute/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..b47aa1a1fd
--- /dev/null
+++ b/test/ResourcesClassLibraryNoAttribute/Properties/AssemblyInfo.cs
@@ -0,0 +1,22 @@
+// 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.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ResourcesClassLibraryNoAttribute")]
+[assembly: AssemblyTrademark("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("34740578-d5b5-4fb4-afd4-5e87b5443e20")]
diff --git a/test/ResourcesClassLibraryNoAttribute/Resources/Model.resx b/test/ResourcesClassLibraryNoAttribute/Resources/Model.resx
new file mode 100644
index 0000000000..af89e6e6ee
--- /dev/null
+++ b/test/ResourcesClassLibraryNoAttribute/Resources/Model.resx
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Bonjour from ResourcesClassLibraryNoAttribute
+
+
\ No newline at end of file
diff --git a/test/ResourcesClassLibraryNoAttribute/project.json b/test/ResourcesClassLibraryNoAttribute/project.json
new file mode 100644
index 0000000000..1f60f0dd08
--- /dev/null
+++ b/test/ResourcesClassLibraryNoAttribute/project.json
@@ -0,0 +1,17 @@
+{
+ "version": "1.0.0-*",
+ "buildOptions": {
+ "warningsAsErrors": true,
+ "keyFile": "../../tools/Key.snk"
+ },
+ "dependencies": {
+ "NETStandard.Library": "1.6.0"
+ },
+
+ "frameworks": {
+ "net451": {},
+ "netstandard1.6": {
+ "imports": "dnxcore50"
+ }
+ }
+}
diff --git a/test/ResourcesClassLibraryWithAttribute/Model.cs b/test/ResourcesClassLibraryWithAttribute/Model.cs
new file mode 100644
index 0000000000..f0007e0139
--- /dev/null
+++ b/test/ResourcesClassLibraryWithAttribute/Model.cs
@@ -0,0 +1,12 @@
+// 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.
+
+namespace ResourcesClassLibraryWithAttribute
+{
+ public class Model
+ {
+ public Model()
+ {
+ }
+ }
+}
diff --git a/test/ResourcesClassLibraryWithAttribute/Properties/AssemblyInfo.cs b/test/ResourcesClassLibraryWithAttribute/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..143b120c3a
--- /dev/null
+++ b/test/ResourcesClassLibraryWithAttribute/Properties/AssemblyInfo.cs
@@ -0,0 +1,24 @@
+// 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.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Microsoft.Extensions.Localization;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ResourcesClassLibraryWithAttribute")]
+[assembly: AssemblyTrademark("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("f27639b9-913e-43af-9d64-bbd98d9a420a")]
+[assembly: ResourceLocation("ResourceFolder")]
diff --git a/test/ResourcesClassLibraryWithAttribute/ResourceFolder/Model.resx b/test/ResourcesClassLibraryWithAttribute/ResourceFolder/Model.resx
new file mode 100644
index 0000000000..1b10f56f23
--- /dev/null
+++ b/test/ResourcesClassLibraryWithAttribute/ResourceFolder/Model.resx
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Bonjour from ResourcesClassLibraryWithAttribute
+
+
\ No newline at end of file
diff --git a/test/ResourcesClassLibraryWithAttribute/project.json b/test/ResourcesClassLibraryWithAttribute/project.json
new file mode 100644
index 0000000000..816819bb46
--- /dev/null
+++ b/test/ResourcesClassLibraryWithAttribute/project.json
@@ -0,0 +1,18 @@
+{
+ "version": "1.0.0-*",
+ "buildOptions": {
+ "warningsAsErrors": true,
+ "keyFile": "../../tools/Key.snk"
+ },
+ "dependencies": {
+ "Microsoft.Extensions.Localization": "1.1.0-*",
+ "NETStandard.Library": "1.6.0"
+ },
+
+ "frameworks": {
+ "net451": {},
+ "netstandard1.6": {
+ "imports": "dnxcore50"
+ }
+ }
+}