Странное исключение FileLoadException при загрузке сборки, на которую ссылается проект WPF с использованием Assembly.ReflectionOnlyLoadFrom
У меня есть пользовательская задача MSBuild, которая заглядывает внутрь сборки, чтобы получить некоторые метаданные атрибута.
Assembly assembly = Assembly.ReflectionOnlyLoadFrom(AssemblyFile)
Это используется нашим автоматическим процессом сборки / выпуска и отлично работает против сборок, используемых и на которые ссылаются библиотеки классов, консольные приложения и веб-проекты. Задача MSBuild вызывается после того, как другой процесс MSBuild скомпилировал проекты.
Он перестал работать вчера, когда я добавил проект WPF, который ссылался на эту конкретную сборку - библиотеку классов.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)
Я знаю, что это связано с WPF, потому что не возникает исключение, если я изменяю AssemblyFile так, чтобы он указывал на другую сборку в том же решении, на которое не ссылается проект WPF.
В сообщении об исключении упоминается, что
... already loaded from a different location.
It cannot be loaded from a new location within the same appdomain.
Обратите внимание на часть о том же домене приложения.
Поэтому я изменил код, чтобы перехватить это конкретное исключение и посмотреть в 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);
}
}
Само собой разумеется, что рассматриваемая сборка не была в текущем домене (что может иметь смысл, так как порождается другой процесс MSBuild, который выполняет компиляцию), поэтому, если сообщение об ошибке истинно, как мне выяснить, где оно живое? Это сбивает с толку, потому что сообщение об ошибке мне подсказывает, что это должен быть CurrentDomain.
Или кто-то с большим опытом работы с WPF может объяснить, почему эта сборка все еще остается в домене приложения после успешной сборки?
Вот еще один вопрос от кого-то еще, кто ударил это исключение.
1 ответ
Мое решение состояло в том, чтобы пойти с открытым исходным кодом:) Используя Cecil, чтобы получить Attribute
от 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;
}
}
Обновление для комментария Джейса:
Обычно я использую: AssemblyFileVersion
в качестве значения свойства Attribute, и заставьте логику установщика заполнить недостающие фрагменты:)
Вот как определяется свойство:
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]
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");
}