Full Name Tutorial Part 2
How to extract the full name, including version, public token key from an assembly and then unload the assembly!
Introduction
This is part two of the Full Name extraction tutorial (Full Name Tutorial Part 1). This part will put together the bits of information from part one into a simple utility. This utility will then be extended to show how you can load and more importantly unload an assembly in a separate application domain.
Assembly Viewer
This utility can be added to the external tools list within Visual Studio so it can be available whenever required.

I will be using a factory pattern for the creation of the assembly information object for no other reason than it will make my life easier when we get to the loading/unloading from a different application domain.
I am making use of the auto property feature in .NET 3.5, and this is the only feature I am using, so if you have a previous version, then you will need to either remove {get;set;}
or add in the internal members.
public class AssemblyDetails
{
#region Properties
public string FullName { get; set; }
public string AssemblyName { get; set; }
public string Location { get; set; }
public string FileVersion { get; set; }
public string AssemblyVersion { get; set; }
public string CultureName { get; set; }
public string PublicKeyToken { get; set; }
#endregion
}
This factory has a static
method (AssemblyDetailFactory.ExtractInformation
) that takes in a single argument, the location and file name of the assembly. This method, once validated that the argument is an assembly, then returns the AssemblyDetails
to be displayed in the form.

Once the AssemblyDetails
has been returned from the factory, the utility will place the contents of FullName
into the clipboard.
AssemblyDetails _details = AssemblyDetailFactory.ExtractInformation(fileName);
Clipboard.SetDataObject(_details.FullName, true);
Application Domain
This utility will work happy if you remember to close it when done, but if you forget and then try to recompile the project you will find that an error will appear! This is to do with the fact that the utility has loaded the assembly into memory and still has a handle to it. Therefore no other process can update the assembly. This is a bit of a drawback so I am going to show you how to load and unload the assembly when done with it.
Once an assembly is loaded you cannot unload it, unless you unload the application domain that it is hosted in. Normally there is only one application domain which all assemblies are loaded in. This the one that the .NET Framework creates for you when an application is started. To gain access to it, just use AppDomain.CurrentDomain static
method.
You can create an additional application domain whenever you like. These additional application domains can be treated like sandboxes and have dangerous code running in them. For our purposes, we will require communication between both application domains. This is done with the use of serialization.
The first thing we will need to do is make the AssemblyDetails
object serializable. This is done by adding the SerializableAttribute
to the class.
[Serializable]
public class AssemblyDetails
{
.....
}
To do the actual work of processing the assembly, we need a class that can be loaded into the new application domain. This class has to inherit from System.MarshalByRefObject
so that cross domain communication can happen.
For this, I have called the class AssemblyTester
:
public class AssemblyTester
: MarshalByRefObject
{
}
As we are not going to call any methods within the assembly, to make the testing a bit more secure we will load the meta data only. There is a handy method called Assembly.ReflectionOnlyLoad
which will do this, and we use it in the LoadAssembly
:
public void LoadAssembly(string path)
{
try
{
FileName = Path.GetFileNameWithoutExtension(path); ;
Assembly ass = Assembly.ReflectionOnlyLoad(FileName);
Information = AssemblyDetailFactory.GetDetails(ass);
}
catch (FileLoadException ex)
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat(CultureInfo.CurrentCulture,
"Problem loading file ({0}) {1}{1}{2}", FileName,
Environment.NewLine, ex.Message);
if (string.IsNullOrEmpty(ex.FusionLog) == false)
{
sb.AppendFormat(CultureInfo.CurrentCulture, "{0}{0}{1}",
Environment.NewLine, ex.FusionLog);
}
LastError = sb.ToString();
}
catch (BadImageFormatException ex)
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat(CultureInfo.CurrentCulture,
"Problem loading file ({0}) {1}{1}{2}", FileName,
Environment.NewLine, ex.Message);
if (string.IsNullOrEmpty(ex.FusionLog) == false)
{
sb.AppendFormat(CultureInfo.CurrentCulture, "{0}{0}{1}",
Environment.NewLine, ex.FusionLog);
}
LastError = sb.ToString();
}
}
As you can see, we catch two exceptions that could cause us an issue :
FileLoadException
- Assembly was found but could not be loadedBadImageFormatException
- File is not an assembly or assembly was complied with a newer version of the framework!
There is an issue with loading an assembly for viewing of metadata only you cannot retrieve any of the assembly custom attributes directly. This is a bit of a pain as the only one we really want to retrieve is the AssemblyFileVersionAttribute
. To retrieve the custom attribute data, we need to iterate though all the custom attributes to find the correct one and then work out what the constructor argument was in the assembly. There is a static
class called CustomAttributeData
that allows us to retrieve all the custom attributes and the data they where constructed with.
The follow bit of code will be added to the LoadAssembly
method after Information = AssemblyDetailFactory.GetDetails(ass);
line.
foreach (CustomAttributeData data in CustomAttributeData.GetCustomAttributes(ass))
{
if(data.ToString().Contains("AssemblyFileVersionAttribute"))
{
Information.FileVersion = data.ConstructorArguments[0].Value.ToString();
}
}
So that information can be used to return to our calling domain, we will need some properties to be serialised.
public AssemblyDetails Information { get; private set; }
public string FileName { get; private set; }
public string LastError { get; private set; }
Let's add a new method to the AssemblyDetailFactory
class called ExtractInformationSafely
. This method will handle the creation of the domain and the retrieval of the AssemblyDetails
instance.
I am using the CreateInstanceFromAndUnwrap
method as it just combines two other methods in one (see MSDN documentation).
The process of using AssemblyTester
is as follows:
- Create the domain to run the
AssemblyTester
in. - Create an instance of
AssemblyTester
in the domain. - Use the
AssemblyTester.LoadAssembly
on the instance. - Unload the domain.
Create the Domain
By default, any new domain will have limited configuration when created, so it is always advisable to use the AppDomainSetup
class when creating the new domain.
By adding the path to the assembly we wish to load, the new domain will be able to load any dependency assemblies as well. Give the new domain a name and pass in the current domain security evidence as well.
private static AppDomain CreateDomain(string path)
{
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = Path.GetDirectoryName(path);
adSetup.PrivateBinPath = Path.GetDirectoryName(path);
adSetup.ShadowCopyFiles = "true";
if ( string.IsNullOrEmpty(adSetup.ApplicationBase))
{
adSetup.ApplicationBase = Path.GetDirectoryName(".\\");
}
AppDomain domain = AppDomain.CreateDomain
("AssemblyDetailsTest", AppDomain.CurrentDomain.Evidence, adSetup);
return domain;
}
Create the Instance
From the new domain, we can create the AssemblyTester
instance using the CreateInstanceFromAndUnwrap
method.
AssemblyTester tester = domain.CreateInstanceFromAndUnwrap
("AssemblyViewer.exe",
typeof(AssemblyTester).FullName) as AssemblyTester;
Use LoadAssembly Method
Use the instance to load the assembly into the new domain and return the AssemblyDetails
instance.
tester.LoadAssembly(path);
if (tester.Information != null)
{
details = tester.Information;
}
Unload the Domain
Once the details have been returned, we don't need to retain any handles to the assembly being viewed. So just unload the domain.
AppDomain.Unload(domain);
Here is the full ExtractInformationSafely
method:
public static AssemblyDetails ExtractInformationSafely(string path)
{
AssemblyDetails details = null;
LastError = string.Empty;
if (File.Exists(path))
{
AppDomain domain = CreateDomain(path);
AssemblyTester tester = domain.CreateInstanceFromAndUnwrap
("AssemblyViewer.exe",
typeof(AssemblyTester).FullName) as AssemblyTester;
if (tester != null)
{
tester.LoadAssembly(path);
if (tester.Information != null)
{
details =tester.Information;
}
}
AppDomain.Unload(domain);
}
return details;
}
The utility will now load the information and not retain any handle to the assembly it is viewing.
There were a couple of problems that appeared when writing the code. One of them was that you could not use the custom attributes directly. Another was that I had to hard code the assembly name ("AssemblyViewer.exe") in CreateInstanceFromAndUnwrap
and not use typeof(AssemblyTester).Assembly.GetName().Name
property. This is a bit of a pain as I will have to update the assembly name if it changes.
History
- 7th September, 2009 - Initial release