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;
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?