In my C# application, I've a plugin mechanism that loads plugin DLLs from different pathes as specified in a configuration XML file. My application is localizable. The main assembly (the *.exe) has satellite assemblies for the localized languages next to the exe in the standard .NET way (e.g. .\en\en-US\main.resources.dll; .\de\de_DE\main.resources.dll; etc.).
I started localizing a plugin and had to discover that the satellite assembly has to be put in the folders next to the exe. When putting it next to the plugin DLL, the resource manager doesn't find it.
However, since my plugins are interchangable and potentially in different folders, I would highly prefer to put the localized resource assemblies next to the plugins and not to the exe.
Is this possible?!?!
An alternative I could live with would be to embed the localized resources into the DLLs. Is this possible??
Cheers,
Felix
I ran into this issue when working on a product for our company. I didn't find an answer anywhere, so I'm going to post my solution to it here in case someone else finds themselves in the same situation.
As of .NET 4.0 there is a solution to this issue, because satellite assemblies now get passed to the AssemblyResolve handler. If you already have a plugin system where assemblies can be loaded from remote directories, you'll probably already have an assembly resolve handler in place, you just need to extend it to use a different search behaviour for satellite resource assemblies. If you don't have one, the implementation is non-trivial since you basically take responsibility for all assembly search behaviour. I'll post the complete code for a working solution so either way you'd be covered. First of all, you need to hook your AssemblyResolve handler somewhere, like this:
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssemblyReference;
Then assuming you've got a couple of variables to hold path information for your main application and your plugin directories, like this:
string _processAssemblyDirectoryPath;
List<string> _assemblySearchPaths;
Then you need a little helper method that looks a little 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 need the all important AssemblyResolve event handler, which looks a little 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 that event handler deals with satellite resource assemblies, then the search behaviour I use for regular assemblies follows that. This should be enough to help anyone get a system like this working from scratch.
Ok If you want "detach" yoursefl from standart Localization resource binding, and want to have freedom to load an assembly from any location, one of the options is to
a) implement an interface to interact with translations within that assembly
b) use Assembly.Load function to load .NET assembly you want from location you want
Related
I have an application that can be customized by writing custom extensions. All of them are in proj\Extensions folder. At runtime my Core project loads every extension from the folder and executes code. The problem is when one of the extensions uses additional libraries, because the Core project cannot find reference to these additional libraries.
So for example in my Core project I have:
public void Preview(IFileDescription fileDescription)
{
var extension = Path.GetExtension(fileDescription.FilePath);
var reader = _readerFactory.Get(extension);
Data = reader.GetPreview(fileDescription);
}
In one of my extensions I have
public DataTable GetPreview(IFileDescription options)
{
var data = new DataTable();
using (var stream = new StreamReader(options.FilePath))
{
var reader = new CsvReader(stream); // <- This is from external library and because of this Core throws IO exception
}
/*
...
*/
return data;
}
Core only knows about the interface, so when one of readers uses for example CsvHelper.dll I get exception of FileNotFound, because Core cannot find CsvHelper.dll. Is there any way to tell the compiler to look for additional libraries in specific folder? I used Reference Paths, but it didn't solve the problem. It still threw the same exception.
Yes, it's possible. You can attach to the AppDomain.AssemblyResolve event and manually load the required DLLs from your add-in directory. Execute the following code before executing any add-in code:
var addinFolder = ...;
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) =>
{
var missing = new AssemblyName(e.Name);
var missingPath = Path.Combine(addinFolder, missing.Name + ".dll");
// If we find the DLL in the add-in folder, load and return it.
if (File.Exists(missingPath))
return Assembly.LoadFrom(missingPath);
// nothing found, let .NET search the common folders
return null;
};
Here's a small class I'm using to probe for a list of available plugins:
internal static class PluginDirectoryLoader
{
public static PluginInfo[] ListPlugins(string path)
{
var name = Path.GetFileName(path);
var setup = new AppDomainSetup
{
ApplicationBase = path,
ShadowCopyFiles = "true"
};
var appdomain = AppDomain.CreateDomain("PluginDirectoryLoader." + name, null, setup);
var exts = (IServerExtensionDiscovery)appdomain.CreateInstanceAndUnwrap("ServerX.Common", "ServerX.Common.ServerExtensionDiscovery");
PluginInfo[] plugins = null;
try
{
plugins = exts.ListPlugins(); // <-- BREAK HERE
}
catch
{
// to do
}
finally
{
AppDomain.Unload(appdomain);
}
return plugins ?? new PluginInfo[0];
}
}
The path parameter points to a subdirectory containing the plugin assemblies to load. The idea is to load them using a separate AppDomain with shadow copying enabled.
In this case, shadow copying isn't really necessary seeing as the AppDomain is unloaded quickly, but when I actually load the plugins in the next block of code I intend to write, I want to use shadow copying so the binaries can be updated on the fly. I have enabled shadow copying in this class as a test to make sure I'm doing it right.
Apparently I'm not doing it right because when I break in the debugger on the commented line in the code sample (i.e. plugins = exts.ListPlugins()), the original plugin assemblies are locked by the application!
Seeing as I'm specifying that assemblies loaded by the AppDomain should be shadow copied, why are they being locked by the application?
I figured it out. There was one property I missed in AppDomainSetup. The property was ShadowCopyDirectories.
var setup = new AppDomainSetup
{
ApplicationBase = path,
ShadowCopyFiles = "true",
ShadowCopyDirectories = path
};
When breaking on the line mentioned in my question, I can now delete the plugin assemblies even without unloading the AppDomain.
I am building a program that uses a very simple plugin system. This is the code I'm using to load the possible plugins:
public interface IPlugin
{
string Name { get; }
string Description { get; }
bool Execute(System.Windows.Forms.IWin32Window parent);
}
private void loadPlugins()
{
int idx = 0;
string[] pluginFolders = getPluginFolders();
Array.ForEach(pluginFolders, folder =>
{
string[] pluginFiles = getPluginFiles(folder);
Array.ForEach(pluginFiles, file =>
{
try
{
System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFile(file);
Array.ForEach(assembly.GetTypes(), type =>
{
if(type.GetInterface("PluginExecutor.IPlugin") != null)
{
IPlugin plugin = assembly.CreateInstance(type.ToString()) as IPlugin;
if(plugin != null)
lista.Add(new PluginItem(plugin.Name, plugin.Description, file, plugin));
}
});
}
catch(Exception) { }
});
});
}
When the user selects a particular plugin from the list, I launch the plugin's Execute method. So far, so good! As you can see the plugins are loaded from a folder, and within the folder are several dll's that are needed but the plugin. My problem is that I can't get the plugin to 'see' the dlls, it just searches the launching applications startup folder, but not the folder where the plugin was loaded from.
I have tried several methods:
1. Changing the Current Directory to the plugins folder.
2. Using an inter-op call to SetDllDirectory
3. Adding an entry in the registry to point to a folder where I want it to look (see code below)
None of these methods work. What am I missing? As I load the dll plugin dynamically, it does not seem to obey any of the above mentioned methods. What else can I try?
Regards,
MartinH.
//HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths
Microsoft.Win32.RegistryKey appPaths = Microsoft.Win32.Registry.LocalMachine.CreateSubKey(
string.Format(
#"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{0}",
System.IO.Path.GetFileName(Application.ExecutablePath)),
Microsoft.Win32.RegistryKeyPermissionCheck.ReadWriteSubTree);
appPaths.SetValue(string.Empty, Application.ExecutablePath);
object path = appPaths.GetValue("Path");
if(path == null)
appPaths.SetValue("Path", System.IO.Path.GetDirectoryName(pluginItem.FileName));
else
{
string strPath = string.Format("{0};{1}", path, System.IO.Path.GetDirectoryName(pluginItem.FileName));
appPaths.SetValue("Path", strPath);
}
appPaths.Flush();
Use Assembly.LoadFrom not Assembly.LoadFile
Whenever I dynamically load plugins like this, I usually create an app domain and load the assembly in the new app domain. When creating an app domain, you can specify the base directory. Dependent assemblies will be loaded from this base directory.
I have a simple Windows Forms (C#, .NET 2.0) application, built with Visual Studio 2008.
I would like to support multiple UI languages, and using the "Localizable" property of the form, and culture-specific .resx files, the localization aspect works seamlessly and easily. Visual Studio automatically compiles the culture-specific resx files into satellite assemblies, so in my compiled application folder there are culture-specific subfolders containing these satellite assemblies.
I would like to have the application be deployed (copied into place) as a single assembly, and yet retain the ability to contain multiple sets of culture-specific resources.
Using ILMerge (or ILRepack), I can merge the satellite assemblies into the main executable assembly, but the standard .NET ResourceManager fallback mechanisms do not find the culture-specific resources that were compiled into the main assembly.
Interestingly, if I take my merged (executable) assembly and place copies of it into the culture-specific subfolders, then everything works! Similarly, I can see the main and culture-specific resources in the merged assemby when I use Reflector (or ILSpy). But copying the main assembly into culture-specific subfolders defeats the purpose of the merging anyway - I really need there to be just a single copy of the single assembly...
I'm wondering whether there is any way to hijack or influence the ResourceManager fallback mechanisms to look for the culture-specific resources in the same assembly rather than in the GAC and culture-named subfolders. I see the fallback mechanism described in the following articles, but no clue as to how it would be modified: BCL Team Blog Article on ResourceManager.
Does anyone have any idea? This seems to be a relatively frequent question online (for example, another question here on Stack Overflow: "ILMerge and localized resource assemblies"), but I have not found any authoritative answer anywhere.
UPDATE 1: Basic Solution
Following casperOne's recommendation below, I was finally able to make this work.
I'm putting the solution code here in the question because casperOne provided the only answer, I don't want to add my own.
I was able to get it to work by pulling the guts out of the Framework resource-finding fallback mechanisms implemented in the "InternalGetResourceSet" method and making our same-assembly search the first mechanism used. If the resource is not found in the current assembly, then we call the base method to initiate the default search mechanisms (thanks to #Wouter's comment below).
To do this, I derived the "ComponentResourceManager" class, and overrode just one method (and re-implemented a private framework method):
class SingleAssemblyComponentResourceManager :
System.ComponentModel.ComponentResourceManager
{
private Type _contextTypeInfo;
private CultureInfo _neutralResourcesCulture;
public SingleAssemblyComponentResourceManager(Type t)
: base(t)
{
_contextTypeInfo = t;
}
protected override ResourceSet InternalGetResourceSet(CultureInfo culture,
bool createIfNotExists, bool tryParents)
{
ResourceSet rs = (ResourceSet)this.ResourceSets[culture];
if (rs == null)
{
Stream store = null;
string resourceFileName = null;
//lazy-load default language (without caring about duplicate assignment in race conditions, no harm done);
if (this._neutralResourcesCulture == null)
{
this._neutralResourcesCulture =
GetNeutralResourcesLanguage(this.MainAssembly);
}
// if we're asking for the default language, then ask for the
// invariant (non-specific) resources.
if (_neutralResourcesCulture.Equals(culture))
culture = CultureInfo.InvariantCulture;
resourceFileName = GetResourceFileName(culture);
store = this.MainAssembly.GetManifestResourceStream(
this._contextTypeInfo, resourceFileName);
//If we found the appropriate resources in the local assembly
if (store != null)
{
rs = new ResourceSet(store);
//save for later.
AddResourceSet(this.ResourceSets, culture, ref rs);
}
else
{
rs = base.InternalGetResourceSet(culture, createIfNotExists, tryParents);
}
}
return rs;
}
//private method in framework, had to be re-specified here.
private static void AddResourceSet(Hashtable localResourceSets,
CultureInfo culture, ref ResourceSet rs)
{
lock (localResourceSets)
{
ResourceSet objA = (ResourceSet)localResourceSets[culture];
if (objA != null)
{
if (!object.Equals(objA, rs))
{
rs.Dispose();
rs = objA;
}
}
else
{
localResourceSets.Add(culture, rs);
}
}
}
}
To actually use this class, you need to replace the System.ComponentModel.ComponentResourceManager in the "XXX.Designer.cs" files created by Visual Studio - and you will need to do this every time you change the designed form - Visual Studio replaces that code automatically. (The problem was discussed in "Customize Windows Forms Designer to use MyResourceManager", I did not find a more elegant solution - I use fart.exe in a pre-build step to auto-replace.)
UPDATE 2: Another Practical Consideration - more than 2 languages
At the time I reported the solution above, I was actually only supporting two languages, and ILMerge was doing a fine job of merging my satellite assembly into the final merged assembly.
Recently I started working on a similar project where there are multiple secondary languages, and therefore multiple satellite assemblies, and ILMerge was doing something very strange: Instead of merging the multiple satellite assemblies I had requested, it was merging the first satellite assembly in multiple times!
eg command-line:
"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1InputProg.exe %1es\InputProg.resources.dll %1fr\InputProg.resources.dll
With that command-line, I was getting the following sets of resources in the merged assembly (observed with ILSpy decompiler):
InputProg.resources
InputProg.es.resources
InputProg.es.resources <-- Duplicated!
After some playing around, I ended up realizing this is just a bug in ILMerge when it encounters multiple files with the same name in a single command-line call. The solution is simply to merge each satellite assembly in a different command-line call:
"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1TempProg.exe %1InputProg.exe %1es\InputProg.resources.dll
"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1TempProg.exe %1fr\InputProg.resources.dll
When I do this, the resulting resources in the final assembly are correct:
InputProg.resources
InputProg.es.resources
InputProg.fr.resources
So finally, in case this helps clarify, here's a complete post-build batch file:
"%ProgramFiles%\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1TempProg.exe %1InputProg.exe %1es\InputProg.resources.dll
IF %ERRORLEVEL% NEQ 0 GOTO END
"%ProgramFiles%\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1TempProg.exe %1fr\InputProg.resources.dll
IF %ERRORLEVEL% NEQ 0 GOTO END
del %1InputProg.exe
del %1InputProg.pdb
del %1TempProg.exe
del %1TempProg.pdb
del %1es\*.* /Q
del %1fr\*.* /Q
:END
UPDATE 3: ILRepack
Another quick note - One of the things that bothered me with ILMerge was that it is an additional proprietary Microsoft tool, not installed by default with Visual Studio, and therefore an extra dependency that makes it that little bit harder for a third party to get started with my open-source projects.
I recently discovered ILRepack, an open-source (Apache 2.0) equivalent that so far works just as well for me (drop-in replacement), and can be freely distributed with your project sources.
I hope this helps someone out there!
The only way I can see this working is by creating a class that derives from ResourceManager and then overriding the InternalGetResourceSet and GetResourceFileName methods. From there, you should be able to override where resources are obtained, given a CultureInfo instance.
A different approach:
1) add your resource.DLLs as embededed resources in your project.
2) add an event handler for AppDomain.CurrentDomain.ResourceResolve
This handler will fire when a resource cannot be found.
internal static System.Reflection.Assembly CurrentDomain_ResourceResolve(object sender, ResolveEventArgs args)
{
try
{
if (args.Name.StartsWith("your.resource.namespace"))
{
return LoadResourcesAssyFromResource(System.Threading.Thread.CurrentThread.CurrentUICulture, "name of your the resource that contains dll");
}
return null;
}
catch (Exception ex)
{
return null;
}
}
3) Now you have to implement LoadResourceAssyFromResource something like
private Assembly LoadResourceAssyFromResource( Culture culture, ResourceName resName)
{
//var x = Assembly.GetExecutingAssembly().GetManifestResourceNames();
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resName))
{
if (stream == null)
{
//throw new Exception("Could not find resource: " + resourceName);
return null;
}
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
var ass = Assembly.Load(assemblyData);
return ass;
}
}
Posted as answer since comments didn't provide enough space:
I couldn't find resources for neutral cultures (en instead of en-US) with the OPs solution. So I extended InternalGetResourceSet with a lookup for neutral cultures which did the job for me. With this you can now also locate resources which do not define the region. This is actually the same behaviour that the normal resourceformatter will show when not ILMerging the resource files.
//Try looking for the neutral culture if the specific culture was not found
if (store == null && !culture.IsNeutralCulture)
{
resourceFileName = GetResourceFileName(culture.Parent);
store = this.MainAssembly.GetManifestResourceStream(
this._contextTypeInfo, resourceFileName);
}
This results in the following code for the SingleAssemblyComponentResourceManager
class SingleAssemblyComponentResourceManager :
System.ComponentModel.ComponentResourceManager
{
private Type _contextTypeInfo;
private CultureInfo _neutralResourcesCulture;
public SingleAssemblyComponentResourceManager(Type t)
: base(t)
{
_contextTypeInfo = t;
}
protected override ResourceSet InternalGetResourceSet(CultureInfo culture,
bool createIfNotExists, bool tryParents)
{
ResourceSet rs = (ResourceSet)this.ResourceSets[culture];
if (rs == null)
{
Stream store = null;
string resourceFileName = null;
//lazy-load default language (without caring about duplicate assignment in race conditions, no harm done);
if (this._neutralResourcesCulture == null)
{
this._neutralResourcesCulture =
GetNeutralResourcesLanguage(this.MainAssembly);
}
// if we're asking for the default language, then ask for the
// invariant (non-specific) resources.
if (_neutralResourcesCulture.Equals(culture))
culture = CultureInfo.InvariantCulture;
resourceFileName = GetResourceFileName(culture);
store = this.MainAssembly.GetManifestResourceStream(
this._contextTypeInfo, resourceFileName);
//Try looking for the neutral culture if the specific culture was not found
if (store == null && !culture.IsNeutralCulture)
{
resourceFileName = GetResourceFileName(culture.Parent);
store = this.MainAssembly.GetManifestResourceStream(
this._contextTypeInfo, resourceFileName);
}
//If we found the appropriate resources in the local assembly
if (store != null)
{
rs = new ResourceSet(store);
//save for later.
AddResourceSet(this.ResourceSets, culture, ref rs);
}
else
{
rs = base.InternalGetResourceSet(culture, createIfNotExists, tryParents);
}
}
return rs;
}
//private method in framework, had to be re-specified here.
private static void AddResourceSet(Hashtable localResourceSets,
CultureInfo culture, ref ResourceSet rs)
{
lock (localResourceSets)
{
ResourceSet objA = (ResourceSet)localResourceSets[culture];
if (objA != null)
{
if (!object.Equals(objA, rs))
{
rs.Dispose();
rs = objA;
}
}
else
{
localResourceSets.Add(culture, rs);
}
}
}
}
I have a suggestion for part of your problem. Specifically, a solution to the step of updating .Designer.cs files to replace ComponentResourceManager with SingleAssemblyComponentResourceManager.
Move the InitializeComponent() method out of .Designer.cs and into the implementation file (include the #region). Visual Studio will continue to auto generate that section, with no problems as far as I can tell.
Use a C# alias at the top of the implementation file so that ComponentResourceManager is aliased to SingleAssemblyComponentResourceManager.
Unfortunately, I didn't get to test this fully. We found a different solution to our problem and so moved on. I hope it helps you though.
Just a thought.
You did the step and created your SingleAssemblyComponentResourceManager
So why do you take the pain to include your satellite assemblies in the ilmerged Assembly?
You could add the ResourceName.es.resx itself as a binary file to another resource in your project.
Than you could rewrite your code
store = this.MainAssembly.GetManifestResourceStream(
this._contextTypeInfo, resourceFileName);
//If we found the appropriate resources in the local assembly
if (store != null)
{
rs = new ResourceSet(store);
with this code (not tested but should work)
// we expect the "main" resource file to have a binary resource
// with name of the local (linked at compile time of course)
// which points to the localized resource
var content = Properties.Resources.ResourceManager.GetObject("es");
if (content != null)
{
using (var stream = new MemoryStream(content))
using (var reader = new ResourceReader(stream))
{
rs = new ResourceSet(reader);
}
}
This should render the effort to include the sattelite assembiles in the ilmerge process obsolete.
In a web application, I want to load all assemblies in the /bin directory.
Since this can be installed anywhere in the file system, I can't gaurantee a specific path where it is stored.
I want a List<> of Assembly assembly objects.
Well, you can hack this together yourself with the following methods, initially use something like:
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
to get the path to your current assembly. Next, iterate over all DLL's in the path using the Directory.GetFiles method with a suitable filter. Your final code should look like:
List<Assembly> allAssemblies = new List<Assembly>();
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
foreach (string dll in Directory.GetFiles(path, "*.dll"))
allAssemblies.Add(Assembly.LoadFile(dll));
Please note that I haven't tested this so you may need to check that dll actually contains the full path (and concatenate path if it doesn't)
To get the bin directory, string path = Assembly.GetExecutingAssembly().Location; does NOT always work (especially when the executing assembly has been placed in an ASP.NET temporary directory).
Instead, you should use string binPath = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "bin");
Further, you should probably take the FileLoadException and BadImageFormatException into consideration.
Here is my working function:
public static void LoadAllBinDirectoryAssemblies()
{
string binPath = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "bin"); // note: don't use CurrentEntryAssembly or anything like that.
foreach (string dll in Directory.GetFiles(binPath, "*.dll", SearchOption.AllDirectories))
{
try
{
Assembly loadedAssembly = Assembly.LoadFile(dll);
}
catch (FileLoadException loadEx)
{ } // The Assembly has already been loaded.
catch (BadImageFormatException imgEx)
{ } // If a BadImageFormatException exception is thrown, the file is not an assembly.
} // foreach dll
}
You can do it like this, but you should probably not load everything into the current appdomain like this, since assemblies might contain harmful code.
public IEnumerable<Assembly> LoadAssemblies()
{
DirectoryInfo directory = new DirectoryInfo(#"c:\mybinfolder");
FileInfo[] files = directory.GetFiles("*.dll", SearchOption.TopDirectoryOnly);
foreach (FileInfo file in files)
{
// Load the file into the application domain.
AssemblyName assemblyName = AssemblyName.GetAssemblyName(file.FullName);
Assembly assembly = AppDomain.CurrentDomain.Load(assemblyName);
yield return assembly;
}
yield break;
}
EDIT: I have not tested the code (no access to Visual Studio at this computer), but I hope that you get the idea.
I know this is a old question but...
System.AppDomain.CurrentDomain.GetAssemblies()