views:

513

answers:

1

I have a custom MSBuild task that peeks inside an assembly to get some attribute meta-data.

Assembly assembly = Assembly.ReflectionOnlyLoadFrom(AssemblyFile)

This is used by our automated build/release process, and has been working perfectly against assemblies used by and referenced from class libraries, console apps and web projects. The MSBuild task is called after another MSBuild process has compiled the projects.

It stopped working yesterday when I added a WPF project which referenced this particular assembly - a .NET 3.5 class library.

System.IO.FileLoadException: API restriction: The assembly 'file:///bogus.dll' has already loaded from a different location. 
It cannot be loaded from a new location within the same appdomain. 
at System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) 
at System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) 
at System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) 
at System.Reflection.Assembly.InternalLoadFrom(String assemblyFile, Evidence securityEvidence, Byte[] hashValue, AssemblyHashAlgorithm hashAlgorithm, Boolean forIntrospection, StackCrawlMark& stackMark) 
at System.Reflection.Assembly.ReflectionOnlyLoadFrom(String assemblyFile) 
at RadicaLogic.MSBuild.Tasks.GetAssemblyAttribute.Execute() 
at Microsoft.Build.BuildEngine.TaskEngine.ExecuteInstantiatedTask(EngineProxy engineProxy, ItemBucket bucket, TaskExecutionMode howToExecuteTask, ITask task, Boolean& taskResult)

I know it's WPF related because no exception is thrown if I change the AssemblyFile to point to another assembly in the same solution which isn't referenced by the WPF project.

The exception message mentions that

... already loaded from a different location.

It cannot be loaded from a new location within the same appdomain.

Note the part about the same appdomain.

So I modified the code to catch this particular exception and look in CurrentDomain:

Assembly assembly = null;
try
{
    assembly = Assembly.ReflectionOnlyLoadFrom(AssemblyFile);
}
catch (FileLoadException)
{
    List<string> searched = new List<string>();
    foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
    {
        if (Path.GetFileName(asm.CodeBase).Equals(Path.GetFileName(AssemblyFile), 
            StringComparison.OrdinalIgnoreCase))
        {
            message = string.Format("Found assembly {0} in current domain", 
                asm.CodeBase);
            MSBuildHelper.Log(this, message, MessageImportance.High);
            assembly = asm;
            break;
        }
        else
        {
            searched.Add(Path.GetFileName(asm.CodeBase));
        }
    }
    if (assembly == null)
    {
        message = string.Format(
            "Unable to find {0} after looking in current domain assemblies {1}",
            Path.GetFileName(AssemblyFile), string.Join(", ", searched.ToArray()));
        MSBuildHelper.Log(this, message, MessageImportance.High);                    
    }
}

It goes without saying that the assembly in question wasn't in the current domain (which may make sense, since another MSBuild process is spawned which does the compilation), so assuming the error message is true, how do I go about figuring out where it lives? It's confusing because the error message to me suggests that it should be CurrentDomain.

Or can someone with more WPF experience explain why this assembly is still floating around in an app domain after a successful build?

Here's another question from someone else who has hit this exception.

A: 

My solution was to go open source :) Using Cecil to get Attribute from AssemblyFile:

bool found = false;
string value = string.Empty;
Type attributeType = Type.GetType(Attribute);
AssemblyDefinition assembly = AssemblyFactory.GetAssembly(AssemblyFile);
foreach (CustomAttribute attribute in assembly.CustomAttributes)
{
    if (attribute.Constructor.DeclaringType.Name == attributeType.Name)
    {
        value = attribute.ConstructorParameters[0].ToString();
        found = true;
    }
}
Si