The approach I would take would be to switch to external DI and then implement a custom IStringLocalizer class that can read the resources from another assembly. Here is a working example. I have created a demo application on GitHub as well.
using System;
using System.Collections.Specialized;
using System.Resources;
namespace MvcSiteMapProvider.Globalization
{
public class ResourceManagerStringLocalizer
: IStringLocalizer
{
public ResourceManagerStringLocalizer(
ResourceManager resourceManager
)
{
if (resourceManager == null)
throw new ArgumentNullException("resourceManager");
this.resourceManager = resourceManager;
}
protected readonly ResourceManager resourceManager;
/// <summary>
/// Gets the localized text for the supplied attributeName.
/// </summary>
/// <param name="attributeName">The name of the attribute (as if it were in the original XML file).</param>
/// <param name="value">The current object's value of the attribute.</param>
/// <param name="enableLocalization">True if localization has been enabled, otherwise false.</param>
/// <param name="classKey">The resource key from the ISiteMap class.</param>
/// <param name="implicitResourceKey">The implicit resource key.</param>
/// <param name="explicitResourceKeys">A <see cref="T:System.Collections.Specialized.NameValueCollection"/> containing the explicit resource keys.</param>
/// <returns></returns>
public virtual string GetResourceString(string attributeName, string value, bool enableLocalization, string classKey, string implicitResourceKey, NameValueCollection explicitResourceKeys)
{
if (attributeName == null)
{
throw new ArgumentNullException("attributeName");
}
if (enableLocalization)
{
string result = string.Empty;
if (explicitResourceKeys != null)
{
string[] values = explicitResourceKeys.GetValues(attributeName);
if ((values == null) || (values.Length <= 1))
{
result = value;
}
else if (this.resourceManager.BaseName.Equals(values[0]))
{
try
{
result = this.resourceManager.GetString(values[1]);
}
catch (MissingManifestResourceException)
{
if (!string.IsNullOrEmpty(value))
{
result = value;
}
}
}
}
if (!string.IsNullOrEmpty(result))
{
return result;
}
}
if (!string.IsNullOrEmpty(value))
{
return value;
}
return string.Empty;
}
}
}
Then you can inject it into your DI configuration module (StructureMap example shown, but any DI container will do).
First of all, you need to specify not to register the IStringLocalizer interface automatically by adding it to the excludeTypes variable.
var excludeTypes = new Type[] {
// Use this array to add types you wish to explicitly exclude from convention-based
// auto-registration. By default all types that either match I[TypeName] = [TypeName] or
// I[TypeName] = [TypeName]Adapter will be automatically wired up as long as they don't
// have the [ExcludeFromAutoRegistrationAttribute].
//
// If you want to override a type that follows the convention, you should add the name
// of either the implementation name or the interface that it inherits to this list and
// add your manual registration code below. This will prevent duplicate registrations
// of the types from occurring.
// Example:
// typeof(SiteMap),
// typeof(SiteMapNodeVisibilityProviderStrategy)
typeof(IStringLocalizer)
};
Then provide an explicit registration of the ResourceManagerStringLocalizer (and its dependencies) instead.
// Configure localization
// Fully qualified namespace.resourcefile (.resx) name without the extension
string resourceBaseName = "SomeAssembly.Resources.Resource1";
// A reference to the assembly where your resources reside.
Assembly resourceAssembly = typeof(SomeAssembly.Class1).Assembly;
// Register the ResourceManager (note that this is application wide - if you are
// using ResourceManager in your DI setup already you may need to use a named
// instance or SmartInstance to specify a specific object to inject)
this.For<ResourceManager>().Use(() => new ResourceManager(resourceBaseName, resourceAssembly));
// Register the ResourceManagerStringLocalizer (uses the ResourceManger)
this.For<IStringLocalizer>().Use<ResourceManagerStringLocalizer>();
Then it is just a matter of specifying the resources appropriately. You need to start them with the Base Name (in this case SomeAssembly.Resources.Resource1
), and then specify the key of the resource as the second argument.
<mvcSiteMapNode title="$resources:SomeAssembly.Resources.Resource1,ContactTitle" controller="Home" action="Contact"/>
Or
[MvcSiteMapNode(Title = "$resources:SomeAssembly.Resources.Resource1,ContactTitle", Controller = "Home", Action = "Contact)]
Note that getting the BaseName right is the key to making it work. See the following MSDN documentation: http://msdn.microsoft.com/en-us/library/yfsz7ac5(v=vs.110).aspx