MEF recursive plugin search - c#

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);
}
}
}

Related

SSIS Script to Delete and Create Folders

I have a loop that runs and creates a bunch of files per folder. The folders are uniquely named based on the order range, so they can change. And the folder must exist or the loop crashes.
I want to create a script that will delete all subfolders and files in each subfolder, from a root dir.
ie
Root = C:\Output\
SubFolder = C:\Output\T1-500\
SubFolder = C:\Output\T501-1010\
SubFolder = C:\Output\T1011-3076\
Then have it create folders as needed on the fly.
I tried:
public void Main()
{
// Deletes subfolders and files in the main folder
EmptyFolder(Dts.Variables["User::FolderName"]);
// Creates new folder on the fly
if (Directory.Exists(Dts.Variables["User::FolderName"].Value = 0))
Dts.TaskResult = Directory.CreateDirectory(Dts.Variables["User::FolderName"]);
}
private void EmptyFolder(DirectoryInfo directoryInfo)
{
foreach (FileInfo file in directoryInfo.GetFiles())
{
file.Delete();
}
foreach (DirectoryInfo subfolder in directoryInfo.GetDirectories())
{
EmptyFolder(subfolder);
}
}
It doesn't seem to pick up my package level variables, and won't let me add new ones.
I get the following when I try to use my folder variable:
The error means you are passing in the wrong data type. Try this.
Instead of this:
EmptyFolder(Dts.Variables["User::FolderName"]);
Use this:
String folderName = (string)Dts.Variables["User::FolderName"].Value;
DirectoryInfo di = new DirectoryInfo(folderName);
EmptyFolder(di);
There is probably a way to put all of it on one line but start with that.
As mentioned in comments below, the (string) cast may not be necessary if you just use the Value property - please also try that.
You will probably also need to use your new folderName string variable in other parts of your code.
As I mentioned in the comments you can also just run a command line like this to remove a folder and all subfolders
RD <yourfolder> /S /Q

How to copy dll file dependency to Temp compilation folder for Azure Function App?

I am working with an azure function app that uses a third-party DLL, that has a dependency on an XML mapping file being present in a folder relative to the current execution. When I publish and run my function on my Azure stack, I run into an exception that the dll cannot load the XML file. I have the XML present in my bin directory with the dll, but Azure appears to be moving the compiled dlls to a temporary folder without the required XML, and proceeds to be looking for the XML relative to that temporary path based on the following exception message:
"Could not find a part of the path 'D:\\local\\Temporary ASP.NET Files\\root\\da2a6178\\25f43073\\assembly\\dl3\\28a13679\\d3614284_4078d301\\Resources\\RepresentationSystem.xml'."
Is there any way I can make sure these additional files are also copied to the temporary folder that Azure is running? Alternatively, can I just force it to run from bin rather than temp?
Update: Unfortunately I am not permitted to share any info on the dll. What I can say is that everything is published to my wwwroot folder, however when outputing some debug info, I can see that the execution is happening from the "Temporary ASP.NET Files" folder. Each dll is copied to its own seperate folder. D:\local\Temporary ASP.NET Files\root\da2a6178\25f43073\assembly\dl3\28a13679\d3614284_4078d301\ThirdParty.dll is that path were the dll in question is, and it lines up with where it expects the xml to be.
While this isn't a true answer to the issue, a workaround for this problem was to have a function in code before the dll functions run, that searches for the dll in question in the Temp ASP.Net folder, and then copies the xml files from a known location to that directory.
// Work Around Begin Here
string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
// Check if we are in temp dir
if (assemblyFolder.Contains("Temporary ASP.NET Files"))
{
DirectoryInfo dir = new DirectoryInfo(assemblyFolder);
// Go up 2 dirs
DirectoryInfo top = dir.Parent.Parent;
DirectoryInfo[] dirs = top.GetDirectories();
foreach (DirectoryInfo child in dirs)
{
DirectoryInfo[] dirs2 = child.GetDirectories();
foreach (DirectoryInfo child2 in dirs2)
{
// Find out if this is the Rep
if (File.Exists(child2.FullName + "\\ThirdParty.Representation.dll"))
{
// Look to see if resource folder is there
if (!Directory.Exists(child2.FullName + "\\Resources"))
{
child2.CreateSubdirectory("Resources");
}
DirectoryInfo resDir = new DirectoryInfo(child2.FullName + "\\Resources");
if (File.Exists(resourceDir + "RepresentationSystem.xml"))
{
if(!File.Exists(resDir.FullName + "\\RepresentationSystem.xml"))
{
File.Copy(resourceDir + "RepresentationSystem.xml", resDir.FullName + "\\RepresentationSystem.xml");
}
}
if (File.Exists(resourceDir + "UnitSystem.xml"))
{
if (!File.Exists(resDir.FullName + "\\UnitSystem.xml"))
{
File.Copy(resourceDir + "UnitSystem.xml", resDir.FullName + "\\UnitSystem.xml");
}
}
}
}
}
}
Thank you DoubleHolo for this workaround. It run fine.
I have changed the code adding only Path.Combine to simplify the code.
private void CopyResourcesToTemporaryFolder()
{
// Work Around Begin Here
string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string resourceDir = Path.Combine(FileUtils.WebProjectFolder, "Resources");
// Check if we are in temp dir
if (assemblyFolder.Contains("Temporary ASP.NET Files"))
{
DirectoryInfo dir = new DirectoryInfo(assemblyFolder);
// Go up 2 dirs
DirectoryInfo top = dir.Parent.Parent;
DirectoryInfo[] dirs = top.GetDirectories();
foreach (DirectoryInfo child in dirs)
{
DirectoryInfo[] dirs2 = child.GetDirectories();
foreach (DirectoryInfo child2 in dirs2)
{
// Find out if this is the Rep
if (File.Exists(Path.Combine(child2.FullName, "AgGateway.ADAPT.Representation.DLL")))
{
// Look to see if resource folder is there
if (!Directory.Exists(Path.Combine(child2.FullName, "Resources")))
{
child2.CreateSubdirectory("Resources");
}
DirectoryInfo resDir = new DirectoryInfo(Path.Combine(child2.FullName, "Resources"));
if (File.Exists(Path.Combine(resourceDir, "RepresentationSystem.xml")))
{
if (!File.Exists(Path.Combine(resDir.FullName, "RepresentationSystem.xml")))
{
File.Copy(Path.Combine(resourceDir, "RepresentationSystem.xml"), Path.Combine(resDir.FullName, "RepresentationSystem.xml"));
}
}
if (File.Exists(Path.Combine(resourceDir, "UnitSystem.xml")))
{
if (!File.Exists(Path.Combine(resDir.FullName, "UnitSystem.xml")))
{
File.Copy(Path.Combine(resourceDir, "UnitSystem.xml"), Path.Combine(resDir.FullName, "UnitSystem.xml"));
}
}
}
}
}
}
}

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 load plugin from directory

I work with MEF and I am looking how to change the url of the location of plugins by another means that MEF find the plugins, I want to change this line
Assembly.LoadFrom(#"C:\julia\project\project.Plugin.Nav\bin\Debug\NavPlugin.dll")));
I want to delete this url because I need to deploy my application in another machine
This is my function :
public void AssembleCalculatorComponents()
{
try
{
//var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
//var container = new CompositionContainer(catalog);
//container.ComposeParts(this);
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.LoadFrom(#"C:\yosra\project\project.Plugin.Nav\bin\Debug\NavPlugin.dll")));
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
catch (Exception ex)
{
throw ex;
}
}
Can you please help me?
Thanks
You can use the DirectoryCatalog class to have MEF scan a particular directory for assemblies that satisfy your imports.
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog("."));
var container = new CompositionContainer(catalog);
The above will add the base directory the AppDomain uses for locating assemblies (usually the directory holding the executable unless changed with configuration) to the aggregate catalog. You will also probably want to add the current executing assembly, though it is not required.
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
catalog.Catalogs.Add(new DirectoryCatalog("."));
var container = new CompositionContainer(catalog);
More information on MSDN for DirectoryCatalog: http://msdn.microsoft.com/en-us/library/system.componentmodel.composition.hosting.directorycatalog.aspx
Hello again and thanks for your response, so my problem was to load the plugin directly, so i create a directory and i place my plugins in this folder, so i find this solution
public void AssembleCalculatorComponents()
{
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
Console.WriteLine(path);
//Check the directory exists
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
Console.WriteLine(path);
string assemblyName = Assembly.GetEntryAssembly().FullName;
Console.WriteLine(assemblyName);
//Create an assembly catalog of the assemblies with exports
var catalog = new AggregateCatalog(
new AssemblyCatalog(Assembly.GetExecutingAssembly().Location),
new AssemblyCatalog(Assembly.Load(assemblyName)),
new DirectoryCatalog(path, "*.dll"));
//Create a composition container
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
this is my solution , thinks for all
Two options.
Use the current directory as the root for your plugins. Environment.CurrentDirectory should point you in the right direction.
Use an app.config to specify a directory for your plugins to be stored in.
If your method knows of the type to be instantiated, you can use the Assembly property of the Type which is typical for my *Domain* libraries; otherwise, you can use Assembly.GetExecutingAssembly(). I am not particularly fond of GetExecutingAssembly() or GetCallingAssembly() ...:
public void AssembleCalculatorComponents() {
try {
var asmCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
// OR
asmCatalog = new AssemblyCatalog(typeof(TObject).Assembly); // replace TObject with object's actual type
var aggregateCatalog = new AggregateCatalog(asmCatalog);
//
AddDirectoryCatalogs(aggregateCatalog.Catalogs));
var container = new CompositionContainer(catalog);
// assuming this class has the member(s) to be composed.
container.ComposeParts(this);
} catch (Exception ex) {
throw ex;
}
}
Aside from that, you can add DirectoryCatalogs - either by the app.config file, serialized list of directories, etc. Starting off, though, you can designate a default directory in your app.config - which is what I recommend. Then, assuming you are using Settings:
private readonly Settings settings = Settings.Default;
void AddDirectoryCatalogs(ICollection<ComposablePartCatalog> Catalogs agrCatalogs ) {
agrCatalogs.Add(new DirectoryCatalog(settings.DefaultPath, settings.DefaultPattern));
// add more directory catalogs
}
While using "." as the search path is a legitimate shortcut to the executing assembly's directory, keep in mind that all assemblies will be searched for fulfilling parts - i.e., matching Import/Export objects. Use of specific patterns is my recommendation. Microsoft's examples are not known for being the best practices. If you expect any plugin to be suffixed with Plugin.dll, make that part of your search pattern.

how to load all assemblies from within your /bin directory

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

Categories