0

I have a WCF service that exposes a method. When a client calls this method, the following occurs:

  1. the method does some processing
  2. it tries to load an assembly if its already there
  3. if the above dll isn't there, it generates C# code, compiles it using CSharpCodeProvider's CompileAssemblyFromSource api
  4. It then loads the just generated assembly

Now the problem. The first time the method it called, it generates the assembly at point 3), and when it tries to return the assembly reference via CompilerResults.CompiledAssembly it throws a file not found exception. However, I can clearly see that the assembly has been generated at the specified location and I can open it with other applications.

If I call the method again via a client, it is able to load the assembly (it was successfully generated as a result of the previous call) and goes ahead to do the remaining set of tasks. Only when the assembly isnt there and it generates it and goes ahead to load it immediately, I get that exception. Any ideas? I have tried playing around with the web.config, changing impersonation to true/false. I have a seperate app pool to run this web application and I tried changing the identity of the app pool from local service to local system, even gave my windows logon credentials which has admin rights but no luck.

Any help would be much appreciated.

Andrea
  • 11,801
  • 17
  • 65
  • 72

2 Answers2

1

Are you sure it's generating the assembly? I have the same problem, except I can't find the generated .dll. I first suspected it was unable to write to the folder, so it now calls CreateDirectory and drops a text file to demonstrate that the folder is writeable.

Anyway, same problem, no success. Is it really the case that nobody else has had this issue??

I'm going to remote debug the server & see if I can step through Microsoft's PDBs...

-- EDIT --

No need to step through Microsoft's code. I had a look at the Errors collection of the CompilerResults and there was 1 item in there: "Metadata file 'c:\Windows\System32\aaclient.dll' could not be opened -- 'An attempt was made to load a program with an incorrect format. '"

When I get Directory.GetCurrentDirectory() to pick up the other DLLs it's usign the Windows System32 directory...

-- EDIT --

Resolved this by adding references from the executing assembly's folder:

CompilerParameters compilerParameters = new CompilerParameters
    {
        OutputAssembly = Path.Combine(GeneratedAssembliesFolder, string.Format("{0}.Generated.dll", typeName))
    };
string executingDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string[] dllFiles = Directory.GetFiles(executingDirectory, "*.dll");
compilerParameters.ReferencedAssemblies.AddRange(dllFiles.Select(f => Path.Combine(executingDirectory, f)).ToArray());
IEnumerable<string> exeFiles =Directory.GetFiles(executingDirectory, "*.exe").Where(f => !f.Contains(".vshost."));
compilerParameters.ReferencedAssemblies.AddRange(exeFiles.Select(f => Path.Combine(executingDirectory, f)).ToArray());

For greater robustness one aught to add checks for the binaries being valid managed code assemblies. This code could also be shortened by using a Linq .Union between the two GetFiles calls.

To find a suitable folder to write to:

private static string generatedAssembliesFolder;

private static string GeneratedAssembliesFolder
{
    get
    {
        if (generatedAssembliesFolder == null)
        {
            string[] candidateFolders = new[]
                {
                    Environment.GetEnvironmentVariable("TEMP", EnvironmentVariableTarget.Process),
                    Environment.GetEnvironmentVariable("TMP", EnvironmentVariableTarget.Process),
                    Environment.GetEnvironmentVariable("TEMP", EnvironmentVariableTarget.User),
                    Environment.GetEnvironmentVariable("TMP", EnvironmentVariableTarget.User),
                    Environment.GetEnvironmentVariable("TEMP", EnvironmentVariableTarget.Machine),
                    Environment.GetEnvironmentVariable("TMP", EnvironmentVariableTarget.Machine),
                    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                    Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
                };
            foreach (string candidateFolder in candidateFolders)
            {
                try
                {
                    if (!Directory.Exists(candidateFolder)) Directory.CreateDirectory(candidateFolder);
                    string testFileName = Path.Combine(candidateFolder, Path.GetRandomFileName());
                    File.WriteAllBytes(testFileName, new byte[0]);
                    File.Delete(testFileName);
                }
                catch (Exception ex)
                {
                    continue;
                }
                generatedAssembliesFolder = candidateFolder;

                break;
            }
        }
        return generatedAssembliesFolder;
    }
}
HughPH
  • 73
  • 6
0

Thanks user1796307 for your input. I had resolved this issue, but forgot to update it. Sharing it for everyone's benefit. It was the .NET Fusion at play. It caches assembly load path and won't even try to load an assembly if a previous attempt to load from same location had failed. In other words:

if (Assembly.Load("C:\xyz.dll") == null)
 {
    Compile("C:\xyz.dll"); // Now the dll exists
    Assembly.Load("C:\xyz.dll"); // this will still fail
 }


The solution is to change it as:

    if (!File.Exists("C:\xyz.dll")
{
  Compile("C:\xyz.dll"); // Now the dll exists
  Assembly.Load("C:\xyz.dll"); // this will now work

}