How to control the order of module initialization in Prism

Robert Taylor picture Robert Taylor · Aug 18, 2009 · Viewed 11.6k times · Source

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 IModule implementation.

This is so that as each module is initialized, they add their View into a TabControl region and the order of the tabs needs to be deterministic and controlled by the module author.

The order does not imply a dependency, but rather just an order that they should be initialized in. In other words: modules A, B, and C may have priorities of 1, 2, and 3 respectively. B does not have a dependency on A - it just needs to get loaded into the TabControl region after A. So that we have a deterministic and controllable order of the tabs. Also, B might not exist at runtime; so they would load as A, C because the priority should determine the order (1, 3). If i used the ModuleDependency, then module "C" will not be able to load w/o all of it's dependencies.

I can manage the logic of how to sort the modules, but i can't figure out where to put said logic.

Answer

Fergus Bown picture Fergus Bown · Jun 16, 2010

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...