From bab2a50ec1a780585ffa89756abd1d995b7f3f17 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Thu, 28 Dec 2017 11:23:36 -0800 Subject: [PATCH] Allow setting RootNamespace in ResourceLocationAttribute --- .../ResourceManagerStringLocalizerFactory.cs | 43 ++++++++--- .../RootNamespaceAttribute.cs | 35 +++++++++ ...sourceManagerStringLocalizerFactoryTest.cs | 72 ++++++++++++++++++- .../AssemblyInfo.cs | 3 +- .../ResourcesClassLibraryWithAttribute.csproj | 1 + 5 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 src/Microsoft.Extensions.Localization/RootNamespaceAttribute.cs diff --git a/src/Microsoft.Extensions.Localization/ResourceManagerStringLocalizerFactory.cs b/src/Microsoft.Extensions.Localization/ResourceManagerStringLocalizerFactory.cs index 4a8b43b..2eb737e 100644 --- a/src/Microsoft.Extensions.Localization/ResourceManagerStringLocalizerFactory.cs +++ b/src/Microsoft.Extensions.Localization/ResourceManagerStringLocalizerFactory.cs @@ -68,7 +68,7 @@ namespace Microsoft.Extensions.Localization throw new ArgumentNullException(nameof(typeInfo)); } - return GetResourcePrefix(typeInfo, new AssemblyName(typeInfo.Assembly.FullName).Name, _resourcesRelativePath); + return GetResourcePrefix(typeInfo, GetRootNamespace(typeInfo.Assembly), GetResourcePath(typeInfo.Assembly)); } /// @@ -94,9 +94,17 @@ namespace Microsoft.Extensions.Localization throw new ArgumentNullException(nameof(baseNamespace)); } - return string.IsNullOrEmpty(resourcesRelativePath) - ? typeInfo.FullName - : baseNamespace + "." + resourcesRelativePath + TrimPrefix(typeInfo.FullName, baseNamespace + "."); + if (string.IsNullOrEmpty(resourcesRelativePath)) + { + return typeInfo.FullName; + } + else + { + // This expectation is defined by dotnet's automatic resource storage. + // We have to conform to "{RootNamespace}.{ResourceLocation}.{FullTypeName - AssemblyName}". + var assemblyName = new AssemblyName(typeInfo.Assembly.FullName).Name; + return baseNamespace + "." + resourcesRelativePath + TrimPrefix(typeInfo.FullName, assemblyName + "."); + } } /// @@ -119,8 +127,9 @@ namespace Microsoft.Extensions.Localization var assemblyName = new AssemblyName(baseNamespace); var assembly = Assembly.Load(assemblyName); + var rootNamespace = GetRootNamespace(assembly); var resourceLocation = GetResourcePath(assembly); - var locationPath = baseNamespace + "." + resourceLocation; + var locationPath = rootNamespace + "." + resourceLocation; baseResourceName = locationPath + TrimPrefix(baseResourceName, baseNamespace + "."); @@ -141,11 +150,10 @@ namespace Microsoft.Extensions.Localization } var typeInfo = resourceSource.GetTypeInfo(); - var assembly = typeInfo.Assembly; - var assemblyName = new AssemblyName(assembly.FullName); - var resourcePath = GetResourcePath(assembly); - var baseName = GetResourcePrefix(typeInfo, assemblyName.Name, resourcePath); + var baseName = GetResourcePrefix(typeInfo); + + var assembly = typeInfo.Assembly; return _localizerCache.GetOrAdd(baseName, _ => CreateResourceManagerStringLocalizer(assembly, baseName)); } @@ -217,6 +225,23 @@ namespace Microsoft.Extensions.Localization return assembly.GetCustomAttribute(); } + /// 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 RootNamespaceAttribute GetRootNamespaceAttribute(Assembly assembly) + { + return assembly.GetCustomAttribute(); + } + + private string GetRootNamespace(Assembly assembly) + { + var rootNamespaceAttribute = GetRootNamespaceAttribute(assembly); + + return rootNamespaceAttribute?.RootNamespace ?? + new AssemblyName(assembly.FullName).Name; + } + private string GetResourcePath(Assembly assembly) { var resourceLocationAttribute = GetResourceLocationAttribute(assembly); diff --git a/src/Microsoft.Extensions.Localization/RootNamespaceAttribute.cs b/src/Microsoft.Extensions.Localization/RootNamespaceAttribute.cs new file mode 100644 index 0000000..f28b4ea --- /dev/null +++ b/src/Microsoft.Extensions.Localization/RootNamespaceAttribute.cs @@ -0,0 +1,35 @@ +// 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 RootNamespace of an Assembly. The RootNamespace of the assembly is used by Localization to + /// determine the resource name to look for when RootNamespace differs from the AssemblyName. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)] + public class RootNamespaceAttribute : Attribute + { + /// + /// Creates a new . + /// + /// The RootNamespace for this Assembly. + public RootNamespaceAttribute(string rootNamespace) + { + if (string.IsNullOrEmpty(rootNamespace)) + { + throw new ArgumentNullException(nameof(rootNamespace)); + } + + RootNamespace = rootNamespace; + } + + /// + /// The RootNamespace of this Assembly. The RootNamespace of the assembly is used by Localization to + /// determine the resource name to look for when RootNamespace differs from the AssemblyName. + /// + public string RootNamespace { get; } + } +} diff --git a/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs b/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs index 017110c..7a18c0e 100644 --- a/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs +++ b/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs @@ -17,16 +17,20 @@ namespace Microsoft.Extensions.Localization.Tests { private ResourceLocationAttribute _resourceLocationAttribute; + private RootNamespaceAttribute _rootNamespaceAttribute; + public Assembly Assembly { get; private set; } public string BaseName { get; private set; } public TestResourceManagerStringLocalizerFactory( IOptions localizationOptions, ResourceLocationAttribute resourceLocationAttribute, + RootNamespaceAttribute rootNamespaceAttribute, ILoggerFactory loggerFactory) : base(localizationOptions, loggerFactory) { _resourceLocationAttribute = resourceLocationAttribute; + _rootNamespaceAttribute = rootNamespaceAttribute; } protected override ResourceLocationAttribute GetResourceLocationAttribute(Assembly assembly) @@ -34,6 +38,11 @@ namespace Microsoft.Extensions.Localization.Tests return _resourceLocationAttribute; } + protected override RootNamespaceAttribute GetRootNamespaceAttribute(Assembly assembly) + { + return _rootNamespaceAttribute; + } + protected override ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(Assembly assembly, string baseName) { BaseName = baseName; @@ -58,11 +67,13 @@ namespace Microsoft.Extensions.Localization.Tests var typeFactory = new TestResourceManagerStringLocalizerFactory( options.Object, resourceLocationAttribute, - loggerFactory); + rootNamespaceAttribute: null, + loggerFactory: loggerFactory); var stringFactory = new TestResourceManagerStringLocalizerFactory( options.Object, resourceLocationAttribute, - loggerFactory); + rootNamespaceAttribute: null, + loggerFactory: loggerFactory); var type = typeof(ResourceManagerStringLocalizerFactoryTest); var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName); @@ -111,6 +122,61 @@ namespace Microsoft.Extensions.Localization.Tests Assert.NotSame(result1, result2); } + [Fact] + public void Create_ResourceLocationAttribute_RootNamespaceIgnoredWhenNoLocation() + { + // Arrange + var locOptions = new LocalizationOptions(); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var loggerFactory = NullLoggerFactory.Instance; + + var resourcePath = Path.Combine("My", "Resources"); + var rootNamespace = "MyNamespace"; + var rootNamespaceAttribute = new RootNamespaceAttribute(rootNamespace); + + var typeFactory = new TestResourceManagerStringLocalizerFactory( + options.Object, + resourceLocationAttribute: null, + rootNamespaceAttribute: rootNamespaceAttribute, + loggerFactory: loggerFactory); + + var type = typeof(ResourceManagerStringLocalizerFactoryTest); + // Act + typeFactory.Create(type); + + // Assert + Assert.Equal($"Microsoft.Extensions.Localization.Tests.ResourceManagerStringLocalizerFactoryTest", typeFactory.BaseName); + } + + [Fact] + public void Create_ResourceLocationAttribute_UsesRootNamespace() + { + // Arrange + var locOptions = new LocalizationOptions(); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var loggerFactory = NullLoggerFactory.Instance; + + var resourcePath = Path.Combine("My", "Resources"); + var rootNamespace = "MyNamespace"; + var resourceLocationAttribute = new ResourceLocationAttribute(resourcePath); + var rootNamespaceAttribute = new RootNamespaceAttribute(rootNamespace); + + var typeFactory = new TestResourceManagerStringLocalizerFactory( + options.Object, + resourceLocationAttribute, + rootNamespaceAttribute, + loggerFactory); + + var type = typeof(ResourceManagerStringLocalizerFactoryTest); + // Act + typeFactory.Create(type); + + // Assert + Assert.Equal($"MyNamespace.My.Resources.ResourceManagerStringLocalizerFactoryTest", typeFactory.BaseName); + } + [Fact] public void Create_FromType_ResourcesPathDirectorySeperatorToDot() { @@ -123,6 +189,7 @@ namespace Microsoft.Extensions.Localization.Tests var factory = new TestResourceManagerStringLocalizerFactory( options.Object, resourceLocationAttribute: null, + rootNamespaceAttribute: null, loggerFactory: loggerFactory); // Act @@ -202,6 +269,7 @@ namespace Microsoft.Extensions.Localization.Tests var factory = new TestResourceManagerStringLocalizerFactory( options.Object, resourceLocationAttribute: null, + rootNamespaceAttribute: null, loggerFactory: loggerFactory); // Act diff --git a/test/ResourcesClassLibraryWithAttribute/AssemblyInfo.cs b/test/ResourcesClassLibraryWithAttribute/AssemblyInfo.cs index f770fdb..bf01e53 100644 --- a/test/ResourcesClassLibraryWithAttribute/AssemblyInfo.cs +++ b/test/ResourcesClassLibraryWithAttribute/AssemblyInfo.cs @@ -4,4 +4,5 @@ using System.Reflection; using Microsoft.Extensions.Localization; -[assembly: ResourceLocation("ResourceFolder")] \ No newline at end of file +[assembly: ResourceLocation("ResourceFolder")] +[assembly: RootNamespace("Alternate.Namespace")] \ No newline at end of file diff --git a/test/ResourcesClassLibraryWithAttribute/ResourcesClassLibraryWithAttribute.csproj b/test/ResourcesClassLibraryWithAttribute/ResourcesClassLibraryWithAttribute.csproj index 3aa1b34..692d903 100644 --- a/test/ResourcesClassLibraryWithAttribute/ResourcesClassLibraryWithAttribute.csproj +++ b/test/ResourcesClassLibraryWithAttribute/ResourcesClassLibraryWithAttribute.csproj @@ -2,6 +2,7 @@ netstandard2.0 + Alternate.Namespace