Load OnDemand modules in specific order ?

Topics: Prism v4 - WPF 4
Nov 15, 2011 at 12:23 PM

Is it possible to load all the onDemand modules in a specific order ? Indeed, i've found this code but it works only for modules that are not loaded at runtime: http://stackoverflow.com/questions/1296642/how-to-control-the-order-of-module-initialization-in-prism

 

Any ideas ?

 

Thanks !

Developer
Nov 15, 2011 at 6:32 PM

Hi,

Based on my understanding of your scenario, if you are loading modules on Demand, then you will need to write the code in your application that requests the module to be loaded. Hence you could define your own logic to make this requests in the desired order. To make this request you can use the following method:

moduleManager.LoadModule("ModuleX");

On the other hand if you want to ensure that a module is initialized before another one, you could try specifying module dependencies.

You might find more information about this in Chapter 4: Modular Application Development of the Prism documentation at MSDN:

Please let us know if we misunderstood your scenario.

Thanks,

Agustin Adami
http://blogs.southworks.net/aadami


Nov 15, 2011 at 9:10 PM

Hi Agustin,

Thanks for your answer ! In fact, I already know how to load a module using the moduleManager. Here is my exact scenario: I have a Shell window that is composed by a Ribbon control. Each module in the Modules folder will populate the Ribbon with custom tab. By default, the ModuleCatalog contains all the module sort by name. So if i have the modules Invoices and Orders and i load them on demand, they will be loaded in the same order:

// Load all the modules except the "Login" module (the only module with InitializationMode set to 'WhenAvailable').
foreach (var module in this.ModuleCatalog.Modules.Where(m => m.InitializationMode != InitializationMode.WhenAvailable))
{
    this.ModuleManager.LoadModule(module.ModuleName);
}

But i would like to change that order and load Orders in first then Invoices. Of course, i could use the ModuleDependency attribute but i would like to avoid it because it must be used with the module name (and my developers are not sure to know the name of the others modules, developed by other developers).

The solution i've found at Stackoverflow works fine but only for modules that are loaded when available, not on demand :(

Is it more clear ?

 

Thanks !

Nov 16, 2011 at 1:32 PM

For those who are interested, i've managed to create a custom DirectoryCatalog that load all the modules, in ModuleCatalog, in the specify order :)

Here is the code:

public class PrioritizedDirectoryModuleCatalog : DirectoryModuleCatalog
    {
        protected override void InnerLoad()
        {
            if (string.IsNullOrEmpty(this.ModulePath))
                throw new InvalidOperationException("The ModulePath cannot contain a null value or be empty");

            if (!Directory.Exists(this.ModulePath))
                throw new InvalidOperationException(
                    string.Format(CultureInfo.CurrentCulture, "Directory {0} was not found.", this.ModulePath));

            AppDomain childDomain = this.BuildChildDomain(AppDomain.CurrentDomain);

            try
            {
                List<string> loadedAssemblies = new List<string>();

                var assemblies = (
                                     from Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()
                                     where !(assembly is System.Reflection.Emit.AssemblyBuilder)
                                        && assembly.GetType().FullName != "System.Reflection.Emit.InternalAssemblyBuilder"
                                        && !String.IsNullOrEmpty(assembly.Location)
                                     select assembly.Location
                                 );

                loadedAssemblies.AddRange(assemblies);

                Type loaderType = typeof(ModulePriorityLoader);

                if (loaderType.Assembly != null)
                {
                    var loader =
                        (ModulePriorityLoader)
                        childDomain.CreateInstanceFrom(loaderType.Assembly.Location, loaderType.FullName).Unwrap();
                    loader.LoadAssemblies(loadedAssemblies);
                    this.Items.AddRange(this.Sort(loader.GetModuleInfos(this.ModulePath)));
                }
            }
            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 = GetPriorities(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;
        }

        /// <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>
        /// Local class to load assemblies into different appdomain which is then discarded
        /// </summary>
        private class ModulePriorityLoader : MarshalByRefObject
        {
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
            internal void LoadAssemblies(IEnumerable<string> assemblies)
            {
                foreach (string assemblyPath in assemblies)
                {
                    try
                    {
                        Assembly.ReflectionOnlyLoadFrom(assemblyPath);
                    }
                    catch (FileNotFoundException)
                    {
                        // Continue loading assemblies even if an assembly can not be loaded in the new AppDomain
                    }
                }
            }

            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
            internal ModuleInfo[] GetModuleInfos(string path)
            {
                DirectoryInfo directory = new DirectoryInfo(path);

                ResolveEventHandler resolveEventHandler =
                    delegate(object sender, ResolveEventArgs args) { return OnReflectionOnlyResolve(args, directory); };

                AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveEventHandler;

                Assembly moduleReflectionOnlyAssembly =
                    AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().First(
                        asm => asm.FullName == typeof(IModule).Assembly.FullName);
                Type IModuleType = moduleReflectionOnlyAssembly.GetType(typeof(IModule).FullName);

                IEnumerable<ModuleInfo> modules = GetNotAllreadyLoadedModuleInfos(directory, IModuleType);

                var array = modules.ToArray();
                AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= resolveEventHandler;
                return array;
            }

            private static IEnumerable<ModuleInfo> GetNotAllreadyLoadedModuleInfos(DirectoryInfo directory, Type IModuleType)
            {
                List<FileInfo> validAssemblies = new List<FileInfo>();
                Assembly[] alreadyLoadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();

                var fileInfos = directory.GetFiles("*.dll")
                    .Where(file => alreadyLoadedAssemblies
                                       .FirstOrDefault(
                                       assembly =>
                                       String.Compare(Path.GetFileName(assembly.Location), file.Name,
                                                      StringComparison.OrdinalIgnoreCase) == 0) == null);

                foreach (FileInfo fileInfo in fileInfos)
                {
                    Assembly assembly = null;
                    try
                    {
                        assembly = Assembly.ReflectionOnlyLoadFrom(fileInfo.FullName);
                        validAssemblies.Add(fileInfo);
                    }
                    catch (BadImageFormatException)
                    {
                        // skip non-.NET Dlls
                    }
                }

                return validAssemblies.SelectMany(file => Assembly.ReflectionOnlyLoadFrom(file.FullName)
                                            .GetExportedTypes()
                                            .Where(IModuleType.IsAssignableFrom)
                                            .Where(t => t != IModuleType)
                                            .Where(t => !t.IsAbstract)
                                            .Select(type => CreateModuleInfo(type)));
            }

            private static Assembly OnReflectionOnlyResolve(ResolveEventArgs args, DirectoryInfo directory)
            {
                Assembly loadedAssembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault(
                    asm => string.Equals(asm.FullName, args.Name, StringComparison.OrdinalIgnoreCase));
                if (loadedAssembly != null)
                {
                    return loadedAssembly;
                }
                AssemblyName assemblyName = new AssemblyName(args.Name);
                string dependentAssemblyFilename = Path.Combine(directory.FullName, assemblyName.Name + ".dll");
                if (File.Exists(dependentAssemblyFilename))
                {
                    return Assembly.ReflectionOnlyLoadFrom(dependentAssemblyFilename);
                }
                return Assembly.ReflectionOnlyLoad(args.Name);
            }

            private static ModuleInfo CreateModuleInfo(Type type)
            {
                string moduleName = type.Name;
                List<string> dependsOn = new List<string>();
                bool onDemand = false;
                var moduleAttribute =
                    CustomAttributeData.GetCustomAttributes(type).FirstOrDefault(
                        cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleAttribute).FullName);

                if (moduleAttribute != null)
                {
                    foreach (CustomAttributeNamedArgument argument in moduleAttribute.NamedArguments)
                    {
                        string argumentName = argument.MemberInfo.Name;
                        switch (argumentName)
                        {
                            case "ModuleName":
                                moduleName = (string)argument.TypedValue.Value;
                                break;

                            case "OnDemand":
                                onDemand = (bool)argument.TypedValue.Value;
                                break;

                            case "StartupLoaded":
                                onDemand = !((bool)argument.TypedValue.Value);
                                break;
                        }
                    }
                }

                var moduleDependencyAttributes =
                    CustomAttributeData.GetCustomAttributes(type).Where(
                        cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleDependencyAttribute).FullName);

                foreach (CustomAttributeData cad in moduleDependencyAttributes)
                {
                    dependsOn.Add((string)cad.ConstructorArguments[0].Value);
                }

                ModuleInfo moduleInfo = new ModuleInfo(moduleName, type.AssemblyQualifiedName)
                {
                    InitializationMode =
                        onDemand
                            ? InitializationMode.OnDemand
                            : InitializationMode.WhenAvailable,
                    Ref = type.Assembly.CodeBase,
                };
                moduleInfo.DependsOn.AddRange(dependsOn);
                return moduleInfo;
            }
        }
    }

And here is the code of the PriorityAttribute:

[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; }
}

Feel free to share any feedbacks/comments about the above code :)

 

Thanks !

Developer
Nov 16, 2011 at 1:35 PM

Hi,

Based on my understanding, you could benefit from the LoadModuleCompleted event provided by the ModuleManager service, which will allow you to track when a module loads or fails to load, this way you can react when a module completed its loading. You can find more information about this in the following link of the Prism documentation.

Also, you could try decorating the views in your modules with the ViewSortHint attribute, this will allow you to define how views will appear in controls that displays multiple active views, instead of showing views in the order they were registered and added into the region.
You can find more information about this in the following chapter of the Prism documentation at MSDN:

I hope you find this handy

Agustin Adami
http://blogs.southworks.net/aadami


Developer
Nov 16, 2011 at 1:44 PM

Hi,

Thank you for sharing you solution with the rest of the community! At first sight it seems like a reasonable approach to fulfill your scenario, and it can also be re used on different occasions.

Thanks,

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Nov 16, 2011 at 1:48 PM
aadami wrote:

Hi,

Based on my understanding, you could benefit from the LoadModuleCompleted event provided by the ModuleManager service, which will allow you to track when a module loads or fails to load, this way you can react when a module completed its loading. You can find more information about this in the following link of the Prism documentation.

I'm sorry but i'm not sure to understand how the event could help me to load my modules in a particular order.... Could you provide me an example ?

aadami wrote:

Also, you could try decorating the views in your modules with the ViewSortHint attribute, this will allow you to define how views will appear in controls that displays multiple active views, instead of showing views in the order they were registered and added into the region.
You can find more information about this in the following chapter of the Prism documentation at MSDN:

I hope you find this handy

Agustin Adami
http://blogs.southworks.net/aadami


Indeed, that might be useful.

 

Thanks !

Nov 16, 2011 at 1:49 PM
GuidoMaliandi wrote:

Hi,

Thank you for sharing you solution with the rest of the community! At first sight it seems like a reasonable approach to fulfill your scenario, and it can also be re used on different occasions.

Thanks,

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

That's good news, thanks ! :)

Developer
Nov 16, 2011 at 1:55 PM

Hi,

If you know the order in which you will load your modules, you can load the first one, listen to the LoadModuleCompleted event, and load the following one after the first one has been loaded, and so forth until all your modules are loaded in order. However, in this case following the approach you mentioned in your previous post, or using the ViewSortHint attribute might be a better option for you. This is because, in my opinion, it's better to have each of your modules define itself its priority (using your custom PriorityAttribute) or the order of its views (through the ViewSortHint attribute), instead of a global component (e.g. your bootstrapper) managing the priorities in a centralized manner.

I hope you find this useful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Nov 16, 2011 at 2:15 PM
GuidoMaliandi wrote:

Hi,

If you know the order in which you will load your modules, you can load the first one, listen to the LoadModuleCompleted event, and load the following one after the first one has been loaded, and so forth until all your modules are loaded in order. However, in this case following the approach you mentioned in your previous post, or using the ViewSortHint attribute might be a better option for you. This is because, in my opinion, it's better to have each of your modules define itself its priority (using your custom PriorityAttribute) or the order of its views (through the ViewSortHint attribute), instead of a global component (e.g. your bootstrapper) managing the priorities in a centralized manner.

I hope you find this useful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

I agree: in my mind, each module should define itself its priority and other information and should not depend of a global component.