how to load all assemblies from within your /bin directory - c#

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()

Related

Application: Application Launcher, can't Move directory, it's being used by another process

I'm writing application launcher as a Window Application in C#, VS 2017. Currently, having problem with this piece of code:
if (System.IO.Directory.Exists(extractPath))
{
string[] files = System.IO.Directory.GetFiles(extractPath);
string[] dirs = Directory.GetDirectories(extractPath);
// Copy the files and overwrite destination files if they already exist.
foreach (string s in files)
{
// Use static Path methods to extract only the file name from the path.
var fileName = System.IO.Path.GetFileName(s);
var destFile = System.IO.Path.Combine(oldPath, fileName);
System.IO.File.Move(s, destFile);
}
foreach (string dir in dirs)
{
//var dirSplit = dir.Split('\\');
//var last = dirSplit.Last();
//if (last != "Resources")
//{
var fileName = System.IO.Path.GetFileName(dir);
var destFile = System.IO.Path.Combine(oldPath, fileName);
System.IO.Directory.Move(dir, destFile);
//}
}
}
I'm getting well known error
"The process cannot access the file 'XXX' because it is being used by another process."
I was looking for solution to fix it, found several on MSDN and StackOvervflow, but my problem is quite specific. I cannot move only 1 directory to another, which is Resources folder of my main application:
Here is my explanation why problem is specific:
I'm not having any issues with moving other files from parent directory. Error occurs only when loop reaches /Resources directory.
At first, I was thinking that it's beeing used by VS instantion, in which I've had main app opened. Nothing have changed after closing VS and killing process.
I've copied and moved whole project to another directory. Never opened it in VS nor started via *.exe file, to make sure that none of files in new, copied directory, is used by any process.
Finally, I've restarted PC.
I know that this error is pretty common when you try to Del/Move files, but in my case, I'm sure that it's being used only by my launcher app. Here is a little longer sample code to show what files operation I'm actually doing:
private void RozpakujRepo()
{
string oldPath = #"path\Debug Kopia\Old";
string extractPath = #"path\Debug Kopia";
var tempPath = #"path\ZipRepo\RexTempRepo.zip";
if (System.IO.File.Exists(tempPath) == true)
{
System.IO.File.Delete(tempPath);
}
System.IO.Compression.ZipFile.CreateFromDirectory(extractPath, tempPath);
if (System.IO.Directory.Exists(oldPath))
{
DeleteDirectory(oldPath);
}
if (!System.IO.Directory.Exists(oldPath))
{
System.IO.Directory.CreateDirectory(oldPath);
}
if (System.IO.Directory.Exists(extractPath))
{
string[] files = System.IO.Directory.GetFiles(extractPath);
string[] dirs = Directory.GetDirectories(extractPath);
// Copy the files and overwrite destination files if they already exist.
foreach (string s in files)
{
// Use static Path methods to extract only the file name from the path.
var fileName = System.IO.Path.GetFileName(s);
var destFile = System.IO.Path.Combine(oldPath, fileName);
System.IO.File.Move(s, destFile);
}
foreach (string dir in dirs)
{
//var dirSplit = dir.Split('\\');
//var last = dirSplit.Last();
//if (last != "Resources")
//{
var fileName = System.IO.Path.GetFileName(dir);
var destFile = System.IO.Path.Combine(oldPath, fileName);
System.IO.Directory.Move(dir, destFile);
//}
}
}
string zipPath = #"path\ZipRepo\RexRepo.zip";
ZipFile.ExtractToDirectory(zipPath, extractPath);
}
And now, my questions:
Can it be related to file types (.png, .ico, .bmp) ?
Can it be related to fact, that those resources files are being used like, as, for example .exe file icon in my main application? Or just because those are resources files?
Is there anything else what I'm missing and what can cause the error?
EDIT:
To clarify:
There are 2 apps:
Main Application
Launcher Application (to launch Main Application)
And Resources folder is Main Application/Resources, I'm moving it while I'm doing application version update.
It appeared that problem is in different place than in /Resources directory. Actually problem was with /Old directory, because it caused inifinite recurrence.

How to get assembly path at runtime, running either in webserver, service or win app

I have a Resource project I use for various parts of our products. To make it more flexible I decided to place the .resx externally from the dll processed with the resgen tool, to allow users to add their own language files on the fly. Since the project will be accessed from various places, web, service or stand-alone winForm, how can I get the path of where the .dll is located so that I can supply the correct path for
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
temp = ResourceManager.CreateFileBasedResourceManager("Resources", cwd, null);
resourceMan = temp;
}
return resourceMan;
}
}
I can hardcode the path and it works great, though I would rather have it figure out the path at runtime.
I tried something like this, however it did not work
string fullPath = System.Reflection.Assembly.GetAssembly(typeof(Resources)).Location;
//get the folder that's in
string theDirectory = Path.GetDirectoryName(fullPath); ResourceManager temp =
"Could not find any resources appropriate for the specified culture (or the neutral culture) on disk.
baseName: Resources locationInfo: fileName: Resources.resources"
Found the following to work from
How do I get the path of the assembly the code is in?
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
var path = Uri.UnescapeDataString(uri.Path);
var theDirectory = Path.GetDirectoryName(path);

How to get list of directories containing particular file using c#

I wish to get list of all the folders/directories that has a particular file in it. How do I do this using C# code.
Eg: Consider I have 20 folders of which 7 of them have a file named "abc.txt". I wish to know all folders that has the file "abc.txt".
I know that we can do this by looking thru all the folders in the path and for each check if the File.Exists(filename); But I wish to know if there is any other way of doing the same rather than looping through all the folder (which may me little time consuming in the case when there are many folders).
Thanks
-Nayan
I would use the method EnumerateFiles of the Directory class with a search pattern and the SearchOption to include AllDirectories. This will return all files (full filename including directory) that match the pattern.
Using the Path class you get the directory of the file.
string rootDirectory = //your root directory;
var foundFiles = Directory.EnumerateFiles(rootDirectory , "abc.txt", SearchOption.AllDirectories);
foreach (var file in foundFiles){
Console.WriteLine(System.IO.Path.GetDirectoryName(file));
}
EnumerateFiles is only available since .NET Framework 4. If you are working with an older version of the .NET Framework then you could use GetFiles of the Directory class.
Update (see comment from PLB):
The code above will fail if the access to a directory in denied. In this case you will need to search each directory one after one to handle exceptions.
public static void SearchFilesRecursivAndPrintOut(string root, string pattern)
{
//Console.WriteLine(root);
try
{
var childDireactory = Directory.EnumerateDirectories(root);
var files = Directory.EnumerateFiles(root, pattern);
foreach (var file in files)
{
Console.WriteLine(System.IO.Path.GetDirectoryName(file));
}
foreach (var dir in childDireactory)
{
SearchRecursiv(dir, pattern);
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
The following shows how to narrow down your search by specific criteria (i.e. include only DLLs that contain "Microsoft", "IBM" or "nHibernate" in its name).
var filez = Directory.EnumerateFiles(#"c:\MLBWRT", "*.dll", SearchOption.AllDirectories)
.Where(
s => s.ToLower().Contains("microsoft")
&& s.ToLower().Contains("ibm")
&& s.ToLower().Contains("nhibernate"));
string[] allFiles = filez.ToArray<string>();
for (int i = 0; i < allFiles.Length; i++) {
FileInfo fInfo = new FileInfo(allFiles[i]);
Console.WriteLine(fInfo.Name);
}

MEF recursive plugin search

Let's say that I have a few applications in a folder (each application has subfolders where plugins can be located):
Clients
Application A
...
Application B
...
Application C
...
...
Some files in these applications have an Export-attribute applied, others don't. Now, I want to be able to load these plugins in some of these applications. Is there a proper way to let MEF search recursively in every subfolder of a specified folder?
No, you will need to recurse through the directories yourself creating a DirectoryCatalog for each. Then, combine all of the DirectoryCatalogs with an AggregateCatalog to create the container.
Another way is to get all the DLL files under a specified directory (recursively) and load them one by one using the assembly catalog.`
var catalog = new AggregateCatalog();
var files = Directory.GetFiles("Parent Directory", "*.dll", SearchOption.AllDirectories);
foreach (var dllFile in files)
{
try
{
var assembly = Assembly.LoadFile(dllFile);
var assemblyCatalog = new AssemblyCatalog(assembly);
catalog.Catalogs.Add(assemblyCatalog);
}
catch (Exception e)
{
// this happens if the given dll file is not a .NET framework file or corrupted.
}
}
There's a MEFContrib project available that has a RecursiveDirectoryCatalog for just this purpose...
https://www.nuget.org/packages/MefContrib/
I created an implementation based in Nicholas Blumhardt answer, I hope that code helps others in future.
private void RecursivedMefPluginLoader(AggregateCatalog catalog, string path)
{
Queue<string> directories = new Queue<string>();
directories.Enqueue(path);
while (directories.Count > 0)
{
var directory = directories.Dequeue();
//Load plugins in this folder
var directoryCatalog = new DirectoryCatalog(directory);
catalog.Catalogs.Add(directoryCatalog);
//Add subDirectories to the queue
var subDirectories = Directory.GetDirectories(directory);
foreach (string subDirectory in subDirectories)
{
directories.Enqueue(subDirectory);
}
}
}

c# path of localized resources for plugin DLLs

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

Categories