I'm trying to put together a small helper tool to encrypt web.config files.
Linqpad example:
var path = #"F:\project\";
var wcfm = new WebConfigurationFileMap();
wcfm.VirtualDirectories.Add("/",new VirtualDirectoryMapping(path,true));
var config = Configuration.WebConfigurationManager.OpenMappedWebConfiguration(wcfm,"/");
var c = config.Sections["customGroup"];
c.SectionInformation.ProtectSection(null);
config.Save();
This throws the exception:
Could not load file or assembly 'customGroupAssembly' or one of its dependencies.
The system cannot find the file specified
Adding the assembly to LinqPad fixes the errpr. I would assume this is because it's now a compile time reference.
Moving the code to a winforms application, reintroduces the issue.
I'm trying to load the needed assembly at runtime with this:
if (this.openFileDialog1.ShowDialog() == DialogResult.OK)
{
byte[] assemblyBuffer = File.ReadAllBytes(this.openFileDialog1.FileName);
AppDomain.CurrentDomain.Load(assemblyBuffer);
MessageBox.Show("Assembly loaded!");
}
However it still doesn't seem to find the file.
Is there a way to load the custom assembly into the runtime app domain so that the web config can be loaded properly?
Try attaching assembly resolve listener, I've had such a problem where runtime-loaded assembly was not resolved properly and needed to be manually resolved:
AppDomain.CurrentDomain.AssemblyResolve += (s,arg)=>{ ... }
with your code at the (...) returning the assembly you want resolved based the name you got in arg
Related
== compile command ==
csc -r:"../Newtonsoft.Json.dll" test.cs
== exec command ==
mono test.exe
== exec result : dependency error ==
System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=11.0.0.0, Culture=neutral,
PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies.
"Newtonsoft.Json.dll" this file is located in parent path. so I added a reference about dll and compile succeeded, but when I executed the exe file, it failed to get dll reference I added.
And when I put cs file and dll file together in the same directory, it worked very well, but that's not what I wanted.
Is there a solution to add a reference from dll file which is located in parent path using command line interface?
I used csc for compiler and mono for execution.
Thanks.
References are pathless. What that means is that wherever the assembly resides, all your program knows is that it has a reference to Newtonsoft.Json, Version=x.x.x.x, Culture=... and so on. You can do some things with the application configuration (application.config or myprogram.exe.config) to organize things into subfolders (using the probing setting) or specify a URL location for the file (using the codebase setting). You can set up the environment to change the search path and so on.
Or you can add some runtime code that allows you to override the default behavior and the call Assembly.LoadFrom to provide a full path to the file. You can either do it as part of your initialization or in a handler for the AppDomain.AssemblyResolve event - which is generally better method since it will only be called when the assembly is actually needed.
For example:
using System.IO;
using System.Reflection;
static class ParentPathResolver
{
public static void Init()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(Resolve);
}
private static Assembly? Resolve(object? sender, ResolveEventArgs args)
{
var filename = new AssemblyName(args.Name).Name + ".dll";
var fullname = Path.GetFullPath(Path.Combine("..", filename));
if (File.Exists(fullname))
return Assembly.LoadFrom(fullname);
return null;
}
}
Of course you can add your own code to the Resolve method to search pretty much anywhere, just as long as you don't get caught in a resolution loop. I've used the AssemblyResolve event to do fun things like loading assemblies from compressed resources.
Problem
CSharpCodeProvider can be used to compile source .cs files into an assembly.
However, the assembly is automatically loaded into the AppDomain.CurrentDomain by default. In my case, this is a problem because I need to be able to re-compile the assembly again during runtime, and since it's already loaded in the CurrentDomain, I can't unload that, so I'm stuck.
I have looked through the docs and there seems to be no way to set the target app domain. I have also tried searching it on Google and only found answers where Assembly.Load was used, which I don't think I can use because I need to compile from raw source code, not a .dll
How would one go about doing this? Are there any alternatives or workarounds?
Main program
using (var provider = new CSharpCodeProvider())
{
param.OutputAssembly = "myCompiledMod"
var classFileNames = new DirectoryInfo("C:/sourceCode").GetFiles("*.cs", SearchOption.AllDirectories).Select(fi => fi.FullName).ToArray();
CompilerResults result = provider.CompileAssemblyFromFile(param, classFileNames);
Assembly newAssembly = result.CompiledAssembly // The assembly is already in AppDomain.CurrentDomain!
// If you try compile again, you'll get an error; that class Test already exists
}
C:/sourceCode/test.cs
public class Test {}
What I tried already
I already tried creating a new AppDomain and loading it in there. What happens is the assembly ends up being loaded in both domains.
// <snip>compile code</snip>
Evidence ev = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("NewDomain", ev);
domain.Load(newAssembly);
The answer was to use CSharpCodeProvider().CreateCompiler() instead of just CSharpCodeProvider, and to set param.GenerateInMemory to false. Now I'm able to see line numbers and no visible assembly .dll files are being created, and especially not being locked. This allows for keeping an assembly in memory and reloading it when needed.
for an auto update logic, I'd like to load a specific version of an assembly. I'm trying to use Assembly.Load method with either assemlyName string or AssemblyName class parameter. For example:
string aname = "MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
var asm = Assembly.Load(aname);
This code happily loads v1.0.0.0 of MyAssembly, which I've referenced normally.
The challenge here is to load a specific version of MyAssembly. My strategy for this has been to wire up AssemblyResolve event (making sure that assembly loading doesn't happen in main method since I've read that it doesn't work there). Then I'd be debugging thru and manually changing the version number of the string from "1.0.0.0" to "2.0.0.0", expecting AssemblyResolve to fire. Surprisingly the CLR happily loads version 1.0.0.0 and fires no events.
Does anyone have a simple and working way to load a specific version of an assembly at runtime?
EDIT:
Thanks for all the answers so far. I haven't gotten it working yet the way I'd like, so your help is still needed. What I did get working was the AssemlyResolve event, like this:
int loadedAssemblies = getAsmCount(); // = 18
// LOAD V1.1 *UNREFERENCED*
var asm1_1 = Assembly.Load("_MyAssembly"); // AssemblyResolve fires behind the scene...
loadedAssemblies = getAsmCount(); // = 19
// USE V1.0 *REFERENCED IN VS SOLUTION*
// Note: seems that this Type's assembly has already been loaded!
var asm1_0 = new Class1().GetType().Assembly;
loadedAssemblies = getAsmCount(); // = 19
It seems that I have 18 assemblies loaded coming this piece of code, and to my surprise Class1 Type's Assembly (called MyAssembly, version 1.0.0.0) has already been loaded. That Assembly is referenced in visual studio normally.
When I manually load v1.1.0.0 of the same assembly, I need to use a little trick, a misspelled name, with underscore to get the AssemblyResolve event firing. Then it loads and I have 19 assemblies loaded, MyAssembly two times, once for v1.0.0.0 and once for 1.1.0.0. All fine except using 1.1.0.0. is a pain, I need to use reflection for that.
What I'd like to have to have direct access to v1.1 (the manually loaded one) of MyAssembly with this command:
var class1 = new Class1();
But now CLR gives me v1.0, the one referenced in visual studio.
How to fix this?
EDIT 2:
This question ended up morping too much, I made a compacter question here: New instance from a manually loaded assembly
you can load a specific assembly from a specific directory using:
Assembly.LoadFrom(string path)
and learn more about it here
now, you said it's your assembly, then if you decided to use reflection you can put it in a specific place (using to post-build for example to move it there) and in that way you don't have to change your code much. you can also investigate the location in order to know what files are there and load them and no loading writing the whole path hard-coded
Directory.GetFiles(string folderPath)
and then
foreach(string curr in filePaths)
{
Assembly.LoadFrom(curr )
}
You should check that there is no assembly already loaded with the same name as the one you want to load. That will prevent the loading of the second assembly.
If that isn't the case, you could use AssemblyResolve to get the assembly when .NET can't resolve it itself.
Here a small sample console app:
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad;
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
string aname = "MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
var asm = Assembly.Load(aname);
Console.ReadKey();
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
Console.WriteLine("Resolving " + args.Name);
return Assembly.LoadFrom(#"C:\path\MyAssembly.6.0.dll");
}
static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
Console.WriteLine("Loading " + args.LoadedAssembly.FullName);
}
When you disable the two event handlers, you will see no assembly can be loaded. If you enable it, you will see it uses the assembly you provided.
Apologies for the dodgy question - happy to rephrase if someone has a better suggestion.
I'm trying to create an object by dynamically invoking an assembly belonging to another application.
The following PowerShell code is working nicely for me:
[Reflection.Assembly]::LoadFrom("C:\Program Files\Vendor\Product\ProductAPI.dll")
$bobject = new-object ProductAPI.BasicObject
$bobject.AddName("Some Name")
I'm struggling to do the same thing in C#. Based on other posts on StackOverflow I currently have this:
System.Reflection.Assembly myDllAssembly =
System.Reflection.Assembly.LoadFile("C:\\Program Files\\Vendor\\Product\\ProductAPI.dll");
System.Type BasicObjectType = myDllAssembly.GetType("ProductAPI.BasicObject");
var basicObjectInstance = Activator.CreateInstance(BasicObjectType);
The final line results in a TargetInvocationException.
{"Could not load file or assembly 'AnotherObject, Version=1.2.345.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified."
It appears that the BasicObject constructor is trying to invoke AnotherObject (from AnotherObject.dll in the same folder) but can't find it.
Any tips on how to get around this?
If it can't find a dependent assembly in the usual places, you'll need to manually specify how to find them.
The two easiest ways I'm aware of for doing this:
manually load the dependent assemblies in advance with
Assembly.Load.
handle the AssemblyResolve event for the domain which is loading the
assembly with additional assembly dependencies.
Both essentially require you to know the dependencies for the assembly you're trying to load in advance but I don't think that's such a big ask.
If you go with the first option, it would also be worthwhile looking into the difference between a full Load and a reflection-only Load.
If you would rather go with 2 (which I'd recommend), you can try something like this which has the added benefit of working with nested dependency chains (eg MyLib.dll references LocalStorage.dll references Raven.Client.dll references NewtonSoft.Json.dll) and will additionally give you information about what dependencies it can't find:
AppDomain.CurrentDomain.AssemblyResolve += (sender,args) => {
// Change this to wherever the additional dependencies are located
var dllPath = #"C:\Program Files\Vendor\Product\lib";
var assemblyPath = Path.Combine(dllPath,args.Name.Split(',').First() + ".dll");
if(!File.Exists(assemblyPath))
throw new ReflectionTypeLoadException(new[] {args.GetType()},
new[] {new FileNotFoundException(assemblyPath) });
return Assembly.LoadFrom(assemblyPath);
};
var processAssembly = Assembly.LoadFile(baseLocationCCDebugFolder+"\\CC.dll");
var processType = processAssembly.GetType("CC.Executor.CCProcess",true,true);
AppDomainSetup appDSetup = new AppDomainSetup()
{
ApplicationBase = baseLocationCCDebugFolder,
//PrivateBinPath = processAssembly.CodeBase
};
StrongName fullTrustAssembly = processAssembly.Evidence.GetHostEvidence<StrongName>();
AppDomain executionDomain = AppDomain.CreateDomain("ExecutionDomain",null,appDSetup,internetPS,fullTrustAssembly);
CCProcess process =(CCProcess)executionDomain.CreateInstanceAndUnwrap(processAssembly.ManifestModule.FullyQualifiedName, processType.FullName);
im encountering an error on the last line of this code which goes like this.
Could not load file or assembly 'D:\\work\\compilerCom\\CompileCom_Build_4_newArchitecture\\CompilerCom\\CC\\bin\\Debug\\CC.dll' or one of its dependencies. The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047)<br/>
im still figuring my way in app domain.
what am i doing wrong?
EDIT:-
the value of baseLocationCCDebugFolder variable is D:\\work\\compilerCom\\CompileCom_Build_4_newArchitecture\\CompilerCom\\CC\\bin\\Debug
My guess is that it is not the Assembly not being found but one of the referenced assemblies not loading correctly. You may need to handle the resolve assembly event in the parent appdomain. There is a good post here describing it.
http://social.msdn.microsoft.com/forums/en-US/clr/thread/0a18ed66-6995-4e7c-baab-61c1e528fb82/