How to control the order of module initialization in Prism

后端 未结 7 829
暖寄归人
暖寄归人 2020-12-28 21:34

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

相关标签:
7条回答
  • 2020-12-28 22:00

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

    0 讨论(0)
  • 2020-12-28 22:00

    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
    {
    }
    
    0 讨论(0)
  • 2020-12-28 22:00

    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

    0 讨论(0)
  • 2020-12-28 22:06

    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

    0 讨论(0)
  • 2020-12-28 22:17

    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.

    0 讨论(0)
  • 2020-12-28 22:21

    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.

    0 讨论(0)
提交回复
热议问题