Loading more dlls into code fails

One of the requirements of the system that I help in the development is the ability to import files, and we have a set of adapters for processing various types of files (csv, xml, etc.) that we expect to meet. At an early stage of development, we were hard-coded by data adapters through referencing and using commands. Obviously, when this happens, we will need a situation where we can just write a new adapter and throw the dll in the folder and start the procedure without recompiling the code.

To implement this, I adapted the code from this question . This code is in the constructor as follows

string dllLocation = @"C:MyLocation\dllLocation"; DirectoryInfo dir = new DirectoryInfo(dllLocation); var tempfiles = dir.GetFiles("*Adapter*.dll", SearchOption.AllDirectories); // This will need to be changed when we go live foreach (var file in tempfiles) { Assembly tempAssembly = null; //Before loading the assembly, check all current loaded assemblies in case already loaded //has already been loaded as a reference to another assembly //Loading the assembly twice can cause major issues foreach (Assembly loadedAssembly in AppDomain.CurrentDomain.GetAssemblies()) { //Check the assembly is not dynamically generated as we are not interested in these if (loadedAssembly.ManifestModule.GetType().Namespace != "System.Reflection.Emit") { //Get the loaded assembly filename string loadedFilename = loadedAssembly.CodeBase.Substring(loadedAssembly.CodeBase.LastIndexOf('/') + 1); //If the filenames match, set the assembly to the one that is already loaded if (loadedFilename.ToUpper() == file.Name.ToUpper()) { tempAssembly = loadedAssembly; break; } } } //If the assembly is not aleady loaded, load it manually if (tempAssembly == null) { tempAssembly = Assembly.LoadFrom(file.FullName); } Assembly a = tempAssembly; 

Later, when the method starts, we have this

 private IEnumerable<IUniversalDataAdapter> DataAdapters { get { foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { foreach (var type in asm.GetTypes().Where(x => x.GetInterfaces().Contains(typeof(IUniversalDataAdapter)))) { if (type.IsAbstract) continue; // can't create abstract classes if (!dataAdapters.Any(y => y.GetType().Equals(type))) { IUniversalDataAdapter adapter = (IUniversalDataAdapter)Activator.CreateInstance(type); dataAdapters.Add(adapter); } } } return dataAdapters; } } 

which successfully loads data adapters and allows you to use them as I expected.

Now for the question in the first bit of code, we have a line

 var tempfiles = dir.GetFiles("*Adapter*.dll", SearchOption.AllDirectories); 

If I change it to

 var tempfiles = dir.GetFiles("*.dll", SearchOption.AllDirectories); 

procedure crashes before the second bit of code is run in this routine

 private IEnumerable<IDataValidator> DataValidators { get { if (validators.Count == 0) { foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { foreach (var type in asm.GetTypes().Where(x => x.GetInterfaces().Contains(typeof(IDataValidator)))) { if (!type.IsAbstract) { var validator = (IDataValidator)Activator.CreateInstance(type, context); validators.Add(validator); } } } } return validators; } } 

Edit: added exception

 System.Reflection.ReflectionTypeLoadException was unhandled HResult=-2146232830 Message=Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information. Source=mscorlib StackTrace: at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module) at System.Reflection.RuntimeModule.GetTypes() at System.Reflection.Assembly.GetTypes() at TTi.Data.Pipeline.Server.Common.DataPipeline.get_DataValidators() in C:\Users\anorcross\Source\Workspaces\Universal System\Data\Main\TTi.Data\TTi.Data.Pipeline.Server.Common\DataPipeline.cs:line 124 at TTi.Data.Pipeline.Server.Common.DataPipeline.CheckConfiguration(DataConfiguration config) in C:\Users\anorcross\Source\Workspaces\Universal System\Data\Main\TTi.Data\TTi.Data.Pipeline.Server.Common\DataPipeline.cs:line 528 at TTi.Data.Pipeline.Server.Common.DataPipeline.ProcessDataSource(IDataSource dataSource, DataConfiguration config) in C:\Users\anorcross\Source\Workspaces\Universal System\Data\Main\TTi.Data\TTi.Data.Pipeline.Server.Common\DataPipeline.cs:line 213 at TTi.Data.Test.Program.ImportTest(String testFolders) in C:\Users\anorcross\Source\Workspaces\Universal System\Data\Main\TTi.Data\TTi.Data.Test\Program.cs:line 362 at TTi.Data.Test.Program.Main(String[] args) in C:\Users\anorcross\Source\Workspaces\Universal System\Data\Main\TTi.Data\TTi.Data.Test\Program.cs:line 48 at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() InnerException: 

EndEdit:

DataValidators definitely executed before the DataAdapters routine. DataValidators are just bits of code contained in the main code base, and check that the imported data is in the expected format. At this point, we simply load them so that we can verify that the required ones exist.

Looking at the loaded assemblies, both versions of the code load the adapters as needed, but the second version loads more than the first, as I expected.

So, why is the second version of tempfiles broken into what looks like a completely unrelated piece of code? And if we add enough data adapters, will this lead to code failure?

+6
source share
2 answers

This exception indicates that the required dll was not found, so it cannot be loaded. Now, what dll was he looking for and why didn’t it fail to compile? To answer the first question, you will need to investigate deeper, and eventually you will find that your code loads some strange dll that you are not using or needing. This may be due to code that iterates over all types from ALL loaded assemblies. You can use a dll as a reference, which provides a type that uses some types from another DLL that you are not referring to. And this is normal if you are not using this type.

To fix this, make your cycle more specific. Obviously, your adapter will not be declared in any system DLL, so why do you repeat these steps?

 foreach (var asm in AppDomain.CurrentDomain.GetAssemblies().Where(IsMyAssembly)) { foreach (var type in asm.GetTypes().Where(x => x.GetInterfaces().Contains(typeof(IDataValidator)))) { //... 

Your first scan of a file with Adapter better than without it, because it is more specific. I would advise making your code even more specific, for example, by explicitly registering the types in some configuration file, which will completely eliminate the dll check.

+1
source

We had exactly the same problem when we wrote a function to get all the corresponding types from a dll. Note the catch attempt.

  private static IEnumerable<Type> MatchingTypesFromDll<TBaseType>(string dllPath) { try { return Assembly .LoadFrom(dllPath) .GetExportedTypes() .Where(TypeSatisfies<TBaseType>); } catch (Exception e) { Debug.WriteLine($"Exception {e} when trying to load from {dllPath}"); return new Type[] {}; } } 

We just ignored the exceptions. Even nuget packages have some native DLLs or, possibly, files with the name .dll that you don't expect. It is less effort to just catch the exception and ignore the broken file than to try to figure out which managed dlls and what not.

+1
source

Source: https://habr.com/ru/post/1014614/


All Articles