As some of you may know, I have been working on a code generator for my Cinch MVVM framework, which I am pleased to say I am nearly done with. The last stumbling block has been that I need to extract a bunch of Namespaces from Assemblies that the main code referenced, which I want to do by the use of Reflection which is very easy. But I also wanted the Assemblies that I would need to examine loaded into a new AppDomain
so that I could unload the newly created AppDomain
when I am finished Reflecting out the Namespaces from the Assemblies.
After many failed attempts, and messing around with:
Asembly.ReflectionOnlyLoad
AppDomain.Load(Byte[] rawAssembly)
- Fusion paths
- Probing in my App.Config
AppDomain
Evidence and AppDomainSetup
I finally found nirvana and hit the sweet spot, which is as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Globalization;
using System.Security.Policy;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
namespace ConsoleApplication1
{
public class SeperateAppDomainAssemblyLoader
{
#region Public Methods
public List<String> LoadAssembly(FileInfo assemblyLocation)
{
List<String> namespaces = new List<String>();
if (string.IsNullOrEmpty(assemblyLocation.Directory.FullName))
{
throw new InvalidOperationException(
"Directory can't be null or empty.");
}
if (!Directory.Exists(assemblyLocation.Directory.FullName))
{
throw new InvalidOperationException(
string.Format(CultureInfo.CurrentCulture,
"Directory not found {0}",
assemblyLocation.Directory.FullName));
}
AppDomain childDomain = BuildChildDomain(
AppDomain.CurrentDomain);
try
{
Type loaderType = typeof(AssemblyLoader);
if (loaderType.Assembly != null)
{
var loader =
(AssemblyLoader)childDomain.
CreateInstanceFrom(
loaderType.Assembly.Location,
loaderType.FullName).Unwrap();
loader.LoadAssembly(
assemblyLocation.FullName);
namespaces =
loader.GetNamespaces(
assemblyLocation.Directory.FullName);
}
return namespaces;
}
finally
{
AppDomain.Unload(childDomain);
}
}
#endregion
#region Private Methods
private AppDomain BuildChildDomain(AppDomain parentDomain)
{
Evidence evidence = new Evidence(parentDomain.Evidence);
AppDomainSetup setup = parentDomain.SetupInformation;
return AppDomain.CreateDomain("DiscoveryRegion",
evidence, setup);
}
#endregion
class AssemblyLoader : MarshalByRefObject
{
#region Private/Internal Methods
[SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic")]
internal List<String> GetNamespaces(string path)
{
List<String> namespaces = new List<String>();
DirectoryInfo directory = new DirectoryInfo(path);
ResolveEventHandler resolveEventHandler =
(s,e)=> {
return OnReflectionOnlyResolve(
e, directory);
};
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve
+= resolveEventHandler;
Assembly reflectionOnlyAssembly =
AppDomain.CurrentDomain.
ReflectionOnlyGetAssemblies().First();
foreach (Type type in reflectionOnlyAssembly.GetTypes())
{
if (!namespaces.Contains(type.Namespace))
namespaces.Add(type.Namespace);
}
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve
-= resolveEventHandler;
return namespaces;
}
private Assembly OnReflectionOnlyResolve(
ResolveEventArgs args, DirectoryInfo directory)
{
Assembly loadedAssembly =
AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies()
.FirstOrDefault(
asm => string.Equals(asm.FullName, args.Name,
StringComparison.OrdinalIgnoreCase));
if (loadedAssembly != null)
{
return loadedAssembly;
}
AssemblyName assemblyName =
new AssemblyName(args.Name);
string dependentAssemblyFilename =
Path.Combine(directory.FullName,
assemblyName.Name + ".dll");
if (File.Exists(dependentAssemblyFilename))
{
return Assembly.ReflectionOnlyLoadFrom(
dependentAssemblyFilename);
}
return Assembly.ReflectionOnlyLoad(args.Name);
}
[SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic")]
internal void LoadAssembly(String assemblyPath)
{
try
{
Assembly.ReflectionOnlyLoadFrom(assemblyPath);
}
catch (FileNotFoundException)
{
}
}
#endregion
}
}
}
I think this code is pretty useful and I hope you find as much use for it as I have. It took me long enough to figure this out, and many Google searches were done and much consulting of APIs/picking friends' knowledge was done to bring you this code, so enjoy it. It nearly killed me getting that one to work.
As usual, here is a small demo app: