tags:

views:

933

answers:

8

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.

A: 

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

Erik Mork
I'm not calling "AddModule()"; i'm using the DirectoryModuleCatalog which finds all IModules in a given path.
Robert Taylor
+1  A: 

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
{
}
Bojan Resnik
The modules are not dependencies; more detail added to the question
Robert Taylor
The dependency is not physical in the sense that MyModule uses something from SomeModule and SomeOtherModule, but rather it is logical as the loading of MyModule depends on these other two modules being loaded. Prism doesn't care about the kind of dependencies between modules, and ModuleDependency attribute can be used to enforce any kind of dependency.
Bojan Resnik
in my example of A, B, C that i recently added - B might not exist; so they would load as A, C because the order is still correct (1, 3).If i used the ModuleDependency, then module "C" will not be able to load w/o all of it's dependencies.
Robert Taylor
+1  A: 

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.

Konamiman
This is a pretty good solution. The only tricky part is knowing when "AllModulesLoaded". Since i'm using the DirectoryModuleCatalog, i don't really have an easy way of knowing that.Thanks for the answer; i've solved the problem a completely different way.
Robert Taylor
A: 

Robert,

How did you solve it, without the custom intializer that konamiman described? I am stuck at the very same thing.

Anand Subramanian
I realized that what I needed was not for the modules to be initialized in a certain order, but for the Views to be loaded into a TabControl Region in a certain order. I did that by removing the views from the region, sorting them, and adding them back. This is done right after base.InitializeModules() in the bootstrapper.
Robert Taylor
A: 

I resolved this by using the ModuleDependency attribute and it worked like a charm

+4  A: 

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

Fergus Bown
A: 

I can't use ModuleDependency because my modules are in separate XAP files. The solution mentioned in here doesn't seem to work for Silverlight PRISM.

Menard
A: 

Very nice article on the topic: http://www.aspiringcraftsman.com/2009/05/enhancing-prism-module-initialization/

Shrike