I\'m using Prism V2 with a DirectoryModuleCatalog and I need the modules to be initialized in a certain order. The desired order is specified with an attribute on each IModu
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
{
}
In the AddModule() call in the Bootstrapper, you can specify a dependency. So, you can say A depends on B depends on C, and that will determine load order.
http://msdn.microsoft.com/en-us/magazine/cc785479.aspx
Had a similar problem an combined Fergus Bowns answer with the SmartDirectoryCatalog suggested by Haukinger: Multiple DirectoryModuleCatalog in a Prism application . I use this for "optional dependencies". Hope this will help someone.
PS: with the actual Prism Unity 7.2 you need to replace ModuleInfo with IModuleInfo
Bringing this back from the dead as I seem to have found a different solution that some might find useful. I tried it out and it works but I have yet to feel out all the pros and cons.
I was using DirectoryModuleCatalog to get a list of all my modules which were all placed into a single folder. But I noticed that for the most part all my "View" modules depended on my "Service" modules, and that was a pretty common pattern. No service should depend on a view. So that got me thinking, what if we just put all the service modules into a folder and all the view modules into another and created two different catalogs in the correct order. Some digging around and I found this article that mentions something called an AggregateModuleCatalog, and it's used to concatenate together a bunch of catalogs. I found the source code for this class here. And here's how I used it:
class Bootstrapper : UnityBootstrapper
{
protected override System.Windows.DependencyObject CreateShell() {...}
protected override void InitializeShell() {...}
protected override IModuleCatalog CreateModuleCatalog()
{
return new AggregateModuleCatalog();
}
protected override void ConfigureModuleCatalog()
{
((AggregateModuleCatalog)ModuleCatalog).AddCatalog(new DirectoryModuleCatalog { ModulePath = "Modules.Services" });
((AggregateModuleCatalog)ModuleCatalog).AddCatalog(new DirectoryModuleCatalog { ModulePath = "Modules.Views" });
}
}
And the AggregateModuleCatalog:
public class AggregateModuleCatalog : IModuleCatalog
{
private List<IModuleCatalog> catalogs = new List<IModuleCatalog>();
/// <summary>
/// Initializes a new instance of the <see cref="AggregateModuleCatalog"/> class.
/// </summary>
public AggregateModuleCatalog()
{
this.catalogs.Add(new ModuleCatalog());
}
/// <summary>
/// Gets the collection of catalogs.
/// </summary>
/// <value>A read-only collection of catalogs.</value>
public ReadOnlyCollection<IModuleCatalog> Catalogs
{
get
{
return this.catalogs.AsReadOnly();
}
}
/// <summary>
/// Adds the catalog to the list of catalogs
/// </summary>
/// <param name="catalog">The catalog to add.</param>
public void AddCatalog(IModuleCatalog catalog)
{
if (catalog == null)
{
throw new ArgumentNullException("catalog");
}
this.catalogs.Add(catalog);
}
/// <summary>
/// Gets all the <see cref="ModuleInfo"/> classes that are in the <see cref="ModuleCatalog"/>.
/// </summary>
/// <value></value>
public IEnumerable<ModuleInfo> Modules
{
get
{
return this.Catalogs.SelectMany(x => x.Modules);
}
}
/// <summary>
/// Return the list of <see cref="ModuleInfo"/>s that <paramref name="moduleInfo"/> depends on.
/// </summary>
/// <param name="moduleInfo">The <see cref="ModuleInfo"/> to get the</param>
/// <returns>
/// An enumeration of <see cref="ModuleInfo"/> that <paramref name="moduleInfo"/> depends on.
/// </returns>
public IEnumerable<ModuleInfo> GetDependentModules(ModuleInfo moduleInfo)
{
var catalog = this.catalogs.Single(x => x.Modules.Contains(moduleInfo));
return catalog.GetDependentModules(moduleInfo);
}
/// <summary>
/// Returns the collection of <see cref="ModuleInfo"/>s that contain both the <see cref="ModuleInfo"/>s in
/// <paramref name="modules"/>, but also all the modules they depend on.
/// </summary>
/// <param name="modules">The modules to get the dependencies for.</param>
/// <returns>
/// A collection of <see cref="ModuleInfo"/> that contains both all <see cref="ModuleInfo"/>s in <paramref name="modules"/>
/// and also all the <see cref="ModuleInfo"/> they depend on.
/// </returns>
public IEnumerable<ModuleInfo> CompleteListWithDependencies(IEnumerable<ModuleInfo> modules)
{
var modulesGroupedByCatalog = modules.GroupBy<ModuleInfo, IModuleCatalog>(module => this.catalogs.Single(catalog => catalog.Modules.Contains(module)));
return modulesGroupedByCatalog.SelectMany(x => x.Key.CompleteListWithDependencies(x));
}
/// <summary>
/// Initializes the catalog, which may load and validate the modules.
/// </summary>
public void Initialize()
{
foreach (var catalog in this.Catalogs)
{
catalog.Initialize();
}
}
/// <summary>
/// Adds a <see cref="ModuleInfo"/> to the <see cref="ModuleCatalog"/>.
/// </summary>
/// <param name="moduleInfo">The <see cref="ModuleInfo"/> to add.</param>
public void AddModule(ModuleInfo moduleInfo)
{
this.catalogs[0].AddModule(moduleInfo);
}
}
I should also mention that the article states the following:
To demonstrate multiple ways of using the ModuleCatalog, the QuickStart using Unity implements an AggregateModuleCatalog that derives from IModuleCatalog. This class is not intended to be used in a shipping application.
Why that is I'm not sure. Would love to hear any explanations as to why that might be.
You can replace the default IModuleInitializer for an instance of a custom class that instead of initializing the modules right after they are loaded, stores them in a modules list. When all modules have been loaded, you initialize them in whatever order you want.
How to achieve this:
1) In the bootstrapper, override the ConfigureContainer method to replace the default IModuleInitializer for a instance of the MyModuleInitializer class, yet maintaining the default initializer with a name (for example, defaultModuleInitializer):
protected override void ConfigureContainer()
{
base.ConfigureContainer();
var defaultContainer = Container.Resolve<IModuleInitializer>();
Container.RegisterInstance<IModuleInitializer>("defaultModuleInitializer", defaultContainer);
Container.RegisterType<IModuleInitializer, MyModuleInitializer>(new ContainerControlledLifetimeManager());
}
2) Create the MyModuleInitializer class that performs the desired storea-all-then-sort-and-initialize procedure:
public class MyModuleInitializer : IModuleInitializer
{
bool initialModuleLoadCompleted = false;
IModuleInitializer defaultInitializer = null;
List<ModuleInfo> modules = new List<ModuleInfo>();
public MyModuleInitializer(IUnityContainer container)
{
defaultInitializer = container.Resolve<IModuleInitializer>("defaultModuleInitializer");
}
public void Initialize(ModuleInfo moduleInfo)
{
if(initialModuleLoadCompleted) {
//Module loaded on demand after application startup - use the default initializer
defaultInitializer.Initialize(moduleInfo);
return;
}
modules.Add(moduleInfo);
if(AllModulesLoaded()) {
SortModules();
foreach(var module in modules) {
defaultInitializer.Initialize(module);
}
modules = null;
initialModuleLoadCompleted = true;
}
}
private bool AllModulesLoaded()
{
//Here you check whether all the startup modules have been loaded
//(perhaps by looking at the module catalog) and return true if so
}
private void SortModules()
{
//Here you sort the "modules" list however you want
}
}
Note that after all the startup modules have been loaded, this class reverts to simply invoking the default initializer. Adapt the class appropriately if this is not what you need.