Problem
If you need to manipulate .NET assembly files (rename, move, etc.) and at the same time load assembly in memory, you may be unpleasantly surprised by the fact that assembly file becomes locked by your application.
Using:
Assembly.LoadFrom(assemblyPath);
or:
Assembly.ReflectionOnlyLoadFrom(assemblyPath);
causes the assembly file to be locked.
I faced this problem creating an Assembly Strong Name Sign Tool when I needed to read assembly information and then rename assembly file.
Thanks to the CodeProject community feedback to this tip, I have found the 3 following solutions.
Solutions
- Using Assembly.Load(Byte[]) method
(Proposed by SpArtA)
Assembly.Load(File.ReadAllBytes(assemblyPath));
In this way, assembly is loaded from new byte array in memory and no actual file lock is made.
Pros: The simplest solution, requires a single line of code.
Cons: The assembly location is unknown. You can't use Assembly.Codebase
and Assembly.Location
- Loading assembly in a separate
AppDomain
(Also described in Sacha Barber's Blog in 2009)
The only way to unload an assembly from an application domain is by unloading the Application Domain
Application Domain is a unit of isolation in .NET that is like a subprocess in the process of application.
The solution is to create a temporary AppDomain
, read assembly data in it and then unload the temporary domain from memory.
First, we need to create a class that holds assembly information.
Class must be marked as [Serializable]
since it travels between domain boundaries.
[Serializable]
public class AssemblyInfo
{
public string Name { get; set; }
public string Version { get; set; }
public string RuntimeVersion { get; set; }
public string Platform { get; set; }
public AssemblyInfo()
{
}
public AssemblyInfo(AssemblyName assemblyName)
{
Name = assemblyName.Name;
Version = assemblyName.Version.ToString();
RuntimeVersion = "";
Platform = assemblyName.ProcessorArchitecture.ToString();
}
public AssemblyInfo(Assembly assembly)
: this(assembly.GetName())
{
RuntimeVersion = assembly.ImageRuntimeVersion;
}
}
Then, we need to create a proxy class that will do stuff in another domain and return data to the current domain.
It must inherit from MarshalByRefObject
.
Only classes inherited from MarshalByRefObject
can be accessed between different application domain boundaries.
public class AssemblyLoader : MarshalByRefObject
{
public AssemblyInfo LoadAssemlyInfo(string assemblyPath)
{
Assembly assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
return new AssemblyInfo(assembly);
}
}
Then, you create a temporary app domain, load the assembly in it and unload the domain:
public static AssemblyInfo GetAssemblyInfo(string assemblyPath, bool lite = false)
{
AssemblyInfo assemblyInfo = null;
AppDomain tempDomain = AppDomain.CreateDomain("TempDomain");
var asmLoader = (AssemblyLoader)tempDomain.CreateInstanceAndUnwrap(
typeof(AssemblyLoader).Assembly.FullName, typeof(AssemblyLoader).FullName);
assemblyInfo = asmLoader.LoadAssemlyInfo(assemblyPath);
AppDomain.Unload(tempDomain);
return assemblyInfo;
}
Pros: You have access to full assembly info including Assembly.Codebase
and Assembly.Location
Cons: Method requires much more code including 3 separate classes.
- Configuring Current
AppDomain
to enable Shadow Copying Assemblies
var setup = AppDomain.CurrentDomain.SetupInformation;
setup.ApplicationBase = _basePath;
setup.ShadowCopyFiles = "true";
var domain = AppDomain.CreateDomain("MyDomainName" + Guid.NewGuid(), null, setup);
(Proposed by SpArtA)
This seems a "natural" way provided by .NET.
As said in MSDN article:
This setting causes all assemblies in the application path to be copied to a download cache before they are loaded.
The copy is locked, but the original assembly file is unlocked and can be updated.
The Common Language Runtime automatically deletes the files when they are no longer needed.
Optionally set a custom location for shadow copied files by using the CachePath property and the ApplicationName property.
The base path for the location is formed by concatenating the ApplicationName property to the CachePath property as a subdirectory. Assemblies are shadow copied to subdirectories of this path, not to the base path itself.
Pros: Requires much less code than method 2 but a bit more than method 1
Cons: You need to consider Startup Performance issues mentioned in MSDN article
Hope this tip will help you in your projects.