I didn't like the idea of using ModuleDependency because this would mean that module a would not load when module b was not present, when in fact there was no dependency. Instead I created a priority attribute to decorate the module:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class PriorityAttribute : Attribute
{
public PriorityAttribute(int priority)
{
this.Priority = priority;
}
public int Priority { get; private set; }
}
I then decorated the modules like this:
[Priority(200)]
[Module(ModuleName = "MyModule")]
public class MyModule : IModule
I created a new descendent of DirectoryModuleCatalog:
[SecurityPermission(SecurityAction.InheritanceDemand), SecurityPermission(SecurityAction.LinkDemand)]
public class PrioritizedDirectoryModuleCatalog : DirectoryModuleCatalog
{
private class ModulePriorityLoader : MarshalByRefObject
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")]
public Dictionary<string, int> GetPriorities(IEnumerable<ModuleInfo> modules)
{
var priorities = new Dictionary<string, int>();
var assemblies = new Dictionary<string, Assembly>();
foreach (ModuleInfo module in modules)
{
if (!assemblies.ContainsKey(module.Ref))
{
assemblies.Add(module.Ref, Assembly.LoadFrom(module.Ref));
}
Type type = assemblies[module.Ref].GetExportedTypes()
.Where(t => t.AssemblyQualifiedName.Equals(module.ModuleType, StringComparison.Ordinal))
.First();
var priorityAttribute =
CustomAttributeData.GetCustomAttributes(type).FirstOrDefault(
cad => cad.Constructor.DeclaringType.FullName == typeof(PriorityAttribute).FullName);
int priority;
if (priorityAttribute != null)
{
priority = (int)priorityAttribute.ConstructorArguments[0].Value;
}
else
{
priority = 0;
}
priorities.Add(module.ModuleName, priority);
}
return priorities;
}
}
private Dictionary<string, int> GetModulePriorities(IEnumerable<ModuleInfo> modules)
{
AppDomain childDomain = BuildChildDomain(AppDomain.CurrentDomain);
try
{
Type loaderType = typeof(ModulePriorityLoader);
var loader =
(ModulePriorityLoader)
childDomain.CreateInstanceFrom(loaderType.Assembly.Location, loaderType.FullName).Unwrap();
return loader.GetPriorities(modules);
}
finally
{
AppDomain.Unload(childDomain);
}
}
protected override IEnumerable<ModuleInfo> Sort(IEnumerable<ModuleInfo> modules)
{
Dictionary<string, int> priorities = GetModulePriorities(modules);
var result = new List<ModuleInfo>(base.Sort(modules));
result.Sort((x, y) =>
{
string xModuleName = x.ModuleName;
string yModuleName = y.ModuleName;
if (x.DependsOn.Contains(yModuleName))
return 1;
else if (y.DependsOn.Contains(xModuleName))
return -1;
else
return priorities[xModuleName].CompareTo(priorities[yModuleName]);
});
return result;
}
}
Finally, I changed the bootstrapper to use this new catalog:
protected override IModuleCatalog GetModuleCatalog()
{
return new PrioritizedDirectoryModuleCatalog() { ModulePath = @".\Modules" };
}
I'm not sure if the stuff with assembly loading is the best way to do things, but it seems to work...