C # localized resource path for DLL modules

In my C # application, I have a plugin mechanism that loads plugin DLL modules from different patches, as indicated in the configuration configuration file. My application is localized. The main assembly (* .exe) has satellite assemblies for localized languages ​​next to exe in the standard .NET method (for example .\en\en-US\main.resources.dll .\de\de_DE\main.resources.dll and etc.).

I started the localization of the plugin and should have discovered that the satellite assembly should be placed in folders next to exe. When placed next to a DLL plugin, the resource manager does not find it.

However, since my plugins are interchangeable and potentially located in different folders, I would prefer to place localized resource assemblies next to the plugins rather than exe.

Is it possible?!?!

An alternative I could live with would be to include localized resources in DLLs. Is it possible?

Cheers, Felix

+4
source share
2 answers

Ok If you want to "detach" yoursefl from the standard localization resource binding and want to have the freedom to download the assembly from anywhere, one option is

a) implement an interface for interacting with translations within this assembly

b) use the Assembly.Load function to load the .NET assembly from the desired location

0
source

I encountered this problem while working on a product for our company. I haven’t found an answer anywhere, so I’m going to post my decision here if someone else is in the same situation.

.NET 4.0 has a solution to this problem, as satellite collections are now passed to the AssemblyResolve handler. If you already have a plugin system where assemblies can be downloaded from remote directories, you probably already have an assembly solution handler, you just need to expand it to use a different search behavior for assemblies of satellite resources. If you don't have one, the implementation is non-trivial, since you are mainly responsible for all assembly search behavior. I will lay out the complete code for a working solution, so that you will be covered. First of all, you need to bind the AssemblyResolve handler somewhere, for example:

 AppDomain.CurrentDomain.AssemblyResolve += ResolveAssemblyReference; 

Then, assuming that you have a couple of variables to store the path information for the main application and your plugin directories, for example:

 string _processAssemblyDirectoryPath; List<string> _assemblySearchPaths; 

Then you need a little helper method that looks something like this:

 static Assembly LoadAssembly(string assemblyPath) { // If the target assembly is already loaded, return the existing assembly instance. Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); Assembly targetAssembly = loadedAssemblies.FirstOrDefault((x) => !x.IsDynamic && String.Equals(x.Location, assemblyPath, StringComparison.OrdinalIgnoreCase)); if (targetAssembly != null) { return targetAssembly; } // Attempt to load the target assembly return Assembly.LoadFile(assemblyPath); } 

And finally, you will need the entire important AssemblyResolve event handler, which looks something like this:

 Assembly ResolveAssemblyReference(object sender, ResolveEventArgs args) { // Obtain information about the requested assembly AssemblyName targetAssemblyName = new AssemblyName(args.Name); string targetAssemblyFileName = targetAssemblyName.Name + ".dll"; // Handle satellite assembly load requests. Note that prior to .NET 4.0, satellite assemblies didn't get // passed to AssemblyResolve handlers. When this was changed, there is a specific guarantee that if null is // returned, normal load procedures will be followed for the satellite assembly, IE, it will be located and // loaded in the same manner as if this event handler wasn't registered. This isn't sufficient for us // though, as the normal load behaviour doesn't correctly locate satellite assemblies where the owning // assembly has been loaded using Assembly.LoadFile where the assembly is located in a different folder to // the process assembly. We handle that here by performing the satellite assembly search process ourselves. // Also note that satellite assemblies are formally documented as requiring the file name extension of // ".resources.dll", so detecting satellite assembly load requests by comparing with this known string is a // valid approach. if (targetAssemblyFileName.EndsWith(".resources.dll")) { // Retrieve the owning assembly which is requesting the satellite assembly string owningAssemblyName = targetAssemblyFileName.Replace(".resources.dll", ".dll"); Assembly owningAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((x) => x.Location.EndsWith(owningAssemblyName)); if (owningAssembly == null) { return null; } // Retrieve the directory containing the owning assembly string owningAssemblyDirectory = Path.GetDirectoryName(owningAssembly.Location); // Search for the required satellite assembly in resource subdirectories, and load it if found. CultureInfo searchCulture = System.Threading.Thread.CurrentThread.CurrentCulture; while (searchCulture != CultureInfo.InvariantCulture) { string resourceAssemblyPath = Path.Combine(owningAssemblyDirectory, searchCulture.Name, targetAssemblyFileName); if (File.Exists(resourceAssemblyPath)) { Assembly resourceAssembly = LoadAssembly(resourceAssemblyPath); if (resourceAssembly != null) { return resourceAssembly; } } searchCulture = searchCulture.Parent; } return null; } // If the target assembly exists in the same directory as the requesting assembly, attempt to load it now. string requestingAssemblyPath = (args.RequestingAssembly != null) ? args.RequestingAssembly.Location : String.Empty; if (!String.IsNullOrEmpty(requestingAssemblyPath)) { string callingAssemblyDirectory = Path.GetDirectoryName(requestingAssemblyPath); string targetAssemblyInCallingDirectoryPath = Path.Combine(callingAssemblyDirectory, targetAssemblyFileName); if (File.Exists(targetAssemblyInCallingDirectoryPath)) { try { return LoadAssembly(targetAssemblyInCallingDirectoryPath); } catch (Exception ex) { // Log an error return null; } } } // If the target assembly exists in the same directory as the process executable, attempt to load it now. string processDirectory = _processAssemblyDirectoryPath; string targetAssemblyInProcessDirectoryPath = Path.Combine(processDirectory, targetAssemblyFileName); if (File.Exists(targetAssemblyInProcessDirectoryPath)) { try { return LoadAssembly(targetAssemblyInProcessDirectoryPath); } catch (Exception ex) { // Log an error return null; } } // Build a list of all assemblies with the requested name in the defined list of assembly search paths Dictionary<string, AssemblyName> assemblyVersionInfo = new Dictionary<string, AssemblyName>(); foreach (string assemblyDir in _assemblySearchPaths) { // If the target assembly doesn't exist in this path, skip it. string assemblyPath = Path.Combine(assemblyDir, targetAssemblyFileName); if (!File.Exists(assemblyPath)) { continue; } // Attempt to retrieve detailed information on the name and version of the target assembly AssemblyName matchAssemblyName; try { matchAssemblyName = AssemblyName.GetAssemblyName(assemblyPath); } catch (Exception) { continue; } // Add this assembly to the list of possible target assemblies assemblyVersionInfo.Add(assemblyPath, matchAssemblyName); } // Look for an exact match of the target version string matchAssemblyPath = assemblyVersionInfo.Where((x) => x.Value == targetAssemblyName).Select((x) => x.Key).FirstOrDefault(); if (matchAssemblyPath == null) { // If no exact target version match exists, look for the highest available version. Dictionary<string, AssemblyName> assemblyVersionInfoOrdered = assemblyVersionInfo.OrderByDescending((x) => x.Value.Version).ToDictionary((x) => x.Key, (x) => x.Value); matchAssemblyPath = assemblyVersionInfoOrdered.Select((x) => x.Key).FirstOrDefault(); } // If no matching assembly was found, log an error, and abort any further processing. if (matchAssemblyPath == null) { return null; } // If the target assembly is already loaded, return the existing assembly instance. Assembly loadedAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((x) => String.Equals(x.Location, matchAssemblyPath, StringComparison.OrdinalIgnoreCase)); if (loadedAssembly != null) { return loadedAssembly; } // Attempt to load the target assembly try { return LoadAssembly(matchAssemblyPath); } catch (Exception ex) { // Log an error } return null; } 

The first part of this event handler is related to assemblies of satellite resources, followed by the search behavior that I use for regular assemblies. This should be enough to help anyone get a system like this from scratch.

0
source

All Articles