2009-10-08 10 views
3

J'ai une tâche MSBuild personnalisée qui apparaît à l'intérieur d'un assembly pour obtenir des métadonnées d'attribut.Weird FileLoadException lors du chargement de l'assembly référencé par le projet WPF à l'aide de Assembly.ReflectionOnlyLoadFrom

Assembly assembly = Assembly.ReflectionOnlyLoadFrom(AssemblyFile) 

Ceci est utilisé par notre processus build/version automatisée et fonctionne parfaitement contre les assemblées utilisées et référencées à partir des bibliothèques de classes, des applications de la console et des projets web. La tâche MSBuild est appelée après qu'un autre processus MSBuild a compilé les projets.

Il a cessé de fonctionner hier lorsque j'ai ajouté un projet WPF qui faisait référence à cet assembly particulier - une bibliothèque de classe .NET 3.5.

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) 

Je sais qu'il est WPF lié parce qu'aucune exception est levée si je change le AssemblyFile pour pointer vers un autre ensemble dans la même solution qui est non référencé par le projet WPF.

Le message d'exception mentionne que

... already loaded from a different location.

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

Notez la partie de la même appdomain.

Je modifié le code pour attraper cette exception particulière et regarder dans 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);      
    } 
} 

Il va sans dire que l'assemblée en question n'a pas été dans le domaine actuel (qui peut donner un sens, car une autre Processus MSBuild est généré qui fait la compilation), donc en supposant que le message d'erreur est vrai, comment puis-je savoir où il vit? C'est déroutant parce que le message d'erreur me suggère qu'il devrait être CurrentDomain.

Ou quelqu'un avec plus d'expérience WPF peut-il expliquer pourquoi cet assembly est toujours en train de flotter dans un domaine d'application après une compilation réussie?

Voici another question de quelqu'un d'autre qui a touché cette exception.

+0

Tout comme en aparté, vous devriez probablement faire cette recherche dans un AppDomain distinct. Cela facilitera l'évitement de ce genre de problèmes. –

+0

@Jamie - La seule fois que j'ai touché ceci est avec le projet référencé WPF, et ceci est déclenché après qu'un autre processus MSBuild (séparé) a terminé la compilation. Alors, un nouvel AppDomain vous aidera-t-il?Considérez également que l'assembly * n'a pas été trouvé dans CurrentDomain. – si618

+0

@Si je voulais juste dire en général. Le chargement dynamique des assemblages a tendance à exploser lorsque vous n'avez pas le contrôle de ce qui est chargé dans l'AppDomain actuel. Les futurs développeurs peuvent inclure un nouvel assembly qui charge une version incompatible d'un assembly partagé ou un assembly attendu peut être supprimé. Rien de tout cela n'est vérifié au moment de la compilation, donc vous voulez vraiment un AppDomain nouveau où vous pouvez explicitement définir quels assemblys doivent être chargés. De plus, lorsque vous avez terminé de vérifier les assemblages, vous pouvez supprimer l'AppDomain et libérer de la mémoire. Mais cela ne s'applique qu'aux processus de longue durée. –

Répondre

1

Ma solution était d'aller open source :) En utilisant Cecil pour obtenir Attribute de 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; 
    } 
} 

Mise à jour pour Jays commentaire:

En général, j'utiliser: AssemblyFileVersion comme la valeur de la propriété d'attribut, et ont la logique du setter remplit les pièces manquantes :)

Voici comment la propriété est définie:

string attribute; 
[Required] 
public string Attribute 
{ 
    get { return attribute; } 
    set 
    { 
     string tempValue = value; 
     if (!tempValue.StartsWith("System.Reflection.")) 
     { 
      tempValue = "System.Reflection." + tempValue; 
     } 
     if (!value.EndsWith("Attribute")) 
     { 
      tempValue += "Attribute"; 
     } 
     attribute = tempValue; 
    } 
} 

test unitaire montrant la valeur de propriété d'attribut sans préfixe ou suffixe requis:

[Test] 
public void Execute_WithoutSystemReflectionPrefixOrAttributeSuffix_ReturnsExpectedResult() 
{ 
    string version = getAssemblyFileVersion(); 
    Assert.IsNotNull(version, "Expected AssemblyFileVersionAttribute to contain value"); 

    task.AssemblyFile = assemblyFile; 
    task.Attribute = "AssemblyFileVersion"; 
    task.Value = "Bogus"; 

    result = task.Execute(); 

    Assert.IsTrue(result, "Expected execute to pass on valid assembly and attribute name"); 

    Assert.AreEqual(task.Value, version, 
     "Expected task value to match assembly file version attribute value"); 
} 
+0

@Si: Je sais que c'est une vieille réponse, mais que passiez-vous pour la chaîne "Attribute" dans la ligne: Type attributeType = Type.GetType (Attribute); J'essaie de déterminer ce que vous avez comparé à plus tard dans votre code. – Jay

+0

Hey Jay, j'ai ajouté des informations supplémentaires pour vous. – si618