Assembly cannot find referenced assembly when compiled to memory with CodeDomProvider
Asked Answered
V

1

5

i am trying to compile some code to memory at runtime using a CodeDomProvider.

The code I am compiling have a reference to an external assembly which I include in the parameters used when compiling the code.

When I compile to memory and try to use reflection on the assembly generated in a Visual Studio Add-In it throws an exception saying that it can't find the referenced assembly.

(Exception)
"Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information."

(LoaderException)
"{"Could not load file or assembly 'Dynamo.Jiss.Task, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.":"Dynamo.Jiss.Task, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"}"

I have tried referencing the Assembly from different places using the absolute path.

The exact same code works fine if it is run from a Console application, and it also works fine in the Add-In if I do not compile to memory.
Removing the reference to the external assembly and the code that reference it also works when compiling to memory, so it probably as the exception describes is a problem with loading the referenced assembly.

Do anyone have an idea why compiling to memory and referencing an assembly isnt working in an Add-In ?

Is there some restrictions within the AppDomain it is running or something that I should be aware of? (my best guess currently)

Should it be in a specific folder? referenced using a relative path? security settings? needs to be signed? any ideas?


What i am trying to achieve is a way to put files with a specific extension in a project and let the addin automatically compile it and if it implements an ITask interface (from the external assembly) it will call a Setup() method that makes it possible for the code to hook into the Visual Studio events and execute tasks/scripts while listening to the different events. This way I can easily execute Text templates if another file is changed, or Combine and Minify files on different events (document saved, build etc.).

Does something like this already exist (to relieve me from the pain) ? :)

Voltaism answered 24/7, 2011 at 0:15 Comment(0)
K
9

This is most likely happening because you're telling CodeDom to generate an in-memory assembly (which is really a lie since it's generating temporarily to disk, loading it, then deleting the file). The point is, the compile directory for the CodeDom assembly is not the same as the one you're using to compile it. That is, if you're running in bin\Debug, the CodeDom assembly is being generated to %temp%.

You could solve this in one of two ways I can think of:

  1. Compile the CodeDom assembly to the same path as your executing assembly.

    myCodeProvider.GenerateInMemory = false; // may not be necessary...haven't tried this in a while
    myCodeProvider.OutputAssembly = string.Format(@"{0}\{1}", Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location, "mydll.dll");
    
  2. Handle the AssemblyResolve event and provide the CodeDom assembly the referenced assembly it's requested.

    AppDomain.CurrentDomain.AssemblyResolve += OnCurrentDomainAssemblyResolve
    
    private static Assembly OnCurrentDomainAssemblyResolve(object sender, ResolveEventArgs args)
    {
                    // this is absurdly expensive...don't do this more than once, or load the assembly file in a more efficient way
                    // also, if the code you're using to compile the CodeDom assembly doesn't/hasn't used the referenced assembly yet, this won't work
                    // and you should use Assembly.Load(...)
                    foreach (Assembly @assembly in AppDomain.CurrentDomain.GetAssemblies())
                    {
                        if (@assembly.FullName.Equals(args.Name, StringComparison.OrdinalIgnoreCase))
                        {
                            return @assembly;
                        }
                    }
    }
    
Kraus answered 24/7, 2011 at 0:30 Comment(8)
Thanks for the very quick reply :) I want to compile to memory. It works fine if i dont compile to memory. It sounds weird that it should able to find and load the referenced assembly when run from a Console Application, but not from an Add-In (both compiled to memory). I use the absolute path for the referenced assembly when compiling, and it compiles the assembly without any errors (which it wont if it cant find the reference). I have tried using Assembly.LoadFrom() but it doesnt make a differenceVoltaism
Issue is most likely that your add-in doesn't have an entry assembly and thus the runtime won't pick-up the entry assembly (because it's launched from another non .NET process like Outlook) as a base directory to scan for assemblies. You can verify by checking if Assembly.GetEntryAssembly is null from your add-in but not from Console mode.Kraus
And since you want to do in memory, I'd suggest using the assembly resolve event as suggested above.Kraus
You are correct, there is no EntryAssembly for the add-in, but there is one for the console application. When looking through the loaded assemblies for the current appdomain, the referenced assembly is already loaded, but it apparently doesnt make a difference. Ill try if i can make the manual assembly resolving work as you described above.Voltaism
Perfect. It is working when using the AssemblyResolve event to find the correct assembly. You mention that it is absurdly expensive. Do you know of a better way to handle it?Voltaism
Registering the event as late as possible and doing something like this seems to work ok - if (args.Name == typeof(ITask).Assembly.FullName) { return typeof(ITask).Assembly; } - But it still hits for several other assemblies that i dont want to handle. But i assume returning null will make it try to resolve the assembly the "traditional" way?Voltaism
Yes, if you return null, it will try the regular way. When I say it's absurdly expensive, I mean that calling GetAssemblies is expensive (can be in the area of 100ms)...so don't do it a lot. If you're checking the args.Name as described above, that indeed eliminates the overhead of calling GetAssemblies().Kraus
Hi Jeff, I am also facing same issue. I have tried above solution but it did not work out for me.Do we need to add this assembly resolver code inside codedom code or where we call methodinfo.invoke() ?Pelagias

© 2022 - 2024 — McMap. All rights reserved.