How to control the order of module initialization in Prism
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:
/// <summary>
/// Allows the order of module loading to be controlled. Where dependencies
/// allow, module loading order will be controlled by relative values of priority
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class PriorityAttribute : Attribute
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="priority">the priority to assign</param>
public PriorityAttribute(int priority)
{
this.Priority = priority;
}
/// <summary>
/// Gets or sets the priority of the module.
/// </summary>
/// <value>The priority of the module.</value>
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:
/// <summary>
/// ModuleCatalog that respects PriorityAttribute for sorting modules
/// </summary>
[SecurityPermission(SecurityAction.InheritanceDemand), SecurityPermission(SecurityAction.LinkDemand)]
public class PrioritizedDirectoryModuleCatalog : DirectoryModuleCatalog
{
/// <summary>
/// local class to load assemblies into different appdomain which is then discarded
/// </summary>
private class ModulePriorityLoader : MarshalByRefObject
{
/// <summary>
/// Get the priorities
/// </summary>
/// <param name="modules"></param>
/// <returns></returns>
[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)
{
//retrieve the priorities of each module, so that we can use them to override the
//sorting - but only so far as we don't mess up the dependencies
var priorities = new Dictionary<string, int>();
var assemblies = new Dictionary<string, Assembly>();
foreach (ModuleInfo module in modules)
{
if (!assemblies.ContainsKey(module.Ref))
{
//LoadFrom should generally be avoided appently due to unexpected side effects,
//but since we are doing all this in a separate AppDomain which is discarded
//this needn't worry us
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;
}
}
/// <summary>
/// Get the priorities that have been assigned to each module. If a module does not have a priority
/// assigned (via the Priority attribute) then it is assigned a priority of 0
/// </summary>
/// <param name="modules">modules to retrieve priorities for</param>
/// <returns></returns>
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);
}
}
/// <summary>
/// Sort modules according to dependencies and Priority
/// </summary>
/// <param name="modules">modules to sort</param>
/// <returns>sorted modules</returns>
protected override IEnumerable<ModuleInfo> Sort(IEnumerable<ModuleInfo> modules)
{
Dictionary<string, int> priorities = GetModulePriorities(modules);
//call the base sort since it resolves dependencies, then re-sort
var result = new List<ModuleInfo>(base.Sort(modules));
result.Sort((x, y) =>
{
string xModuleName = x.ModuleName;
string yModuleName = y.ModuleName;
//if one depends on other then non-dependent must come first
//otherwise base on priority
if (x.DependsOn.Contains(yModuleName))
return 1; //x after y
else if (y.DependsOn.Contains(xModuleName))
return -1; //y after x
else
return priorities[xModuleName].CompareTo(priorities[yModuleName]);
});
return result;
}
}
Finally, I changed the bootstrapper to use this new catalog:
/// <summary>Where are the modules located</summary>
/// <returns></returns>
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...
You can use the ModuleDependency
attribute on your module class to tell the loader that your module depends on other modules:
[ModuleDependency("SomeModule")]
[ModuleDependency("SomeOtherModule")]
public class MyModule : IModule
{
}