11

我将 Prism V2 与 DirectoryModuleCatalog 一起使用,我需要按特定顺序初始化模块。所需的顺序由每个 IModule 实现上的属性指定。

这样一来,在初始化每个模块时,它们会将其视图添加到 TabControl 区域中,并且选项卡的顺序需要确定并由模块作者控制。

该顺序并不意味着依赖关系,而只是它们应该被初始化的顺序。换句话说:模块 A、B 和 C 的优先级可能分别为 1、2 和 3。B 不依赖于 A - 它只需要在 A之后加载到 TabControl 区域。这样我们就有了一个确定性和可控的选项卡顺序。此外,B 在运行时可能不存在;因此它们将加载为 A、C,因为优先级应确定顺序 (1, 3)。如果我使用了 ModuleDependency,那么模块“C”将无法加载它的所有依赖项。

我可以管理如何对模块进行排序的逻辑,但我不知道将所述逻辑放在哪里。

4

7 回答 7

16

我不喜欢使用 ModuleDependency 的想法,因为这意味着当模块 b 不存在时模块 a 不会加载,而实际上没有依赖关系。相反,我创建了一个优先级属性来装饰模块:

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

然后我像这样装饰模块:

[Priority(200)]
[Module(ModuleName = "MyModule")]
public class MyModule : IModule

我创建了 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;
    }
}

最后,我更改了引导程序以使用这个新目录:

    /// <summary>Where are the modules located</summary>
    /// <returns></returns>
    protected override IModuleCatalog GetModuleCatalog()
    {
        return new PrioritizedDirectoryModuleCatalog() { ModulePath = @".\Modules" };
    }

我不确定带有程序集加载的东西是否是最好的做事方式,但它似乎工作......

于 2010-06-16T10:16:39.280 回答
3

您可以使用ModuleDependency模块类上的属性来告诉加载器您的模块依赖于其他模块:

[ModuleDependency("SomeModule")]
[ModuleDependency("SomeOtherModule")]
public class MyModule : IModule
{
}
于 2009-08-23T19:31:06.587 回答
2

您可以为自定义类的实例替换默认的IModuleInitializer ,而不是在加载模块后立即对其进行初始化,而是将它们存储在模块列表中。加载所有模块后,您可以按照您想要的任何顺序初始化它们。

如何实现这一点:

1) 在引导程序中,重写ConfigureContainer方法以替换MyModuleInitializer类实例的默认IModuleInitializer,但使用名称维护默认初始化程序(例如defaultModuleInitializer):


protected override void ConfigureContainer()
{
    base.ConfigureContainer();
    var defaultContainer = Container.Resolve<IModuleInitializer>();
    Container.RegisterInstance<IModuleInitializer>("defaultModuleInitializer", defaultContainer);
    Container.RegisterType<IModuleInitializer, MyModuleInitializer>(new ContainerControlledLifetimeManager());
}


2) 创建执行所需的 storea-all-then-sort-and-initialize 过程的MyModuleInitializer类:


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

请注意,在加载了所有启动模块后,此类将恢复为简单地调用默认初始化程序。如果这不是您需要的,请适当调整课程。

于 2009-09-17T12:43:59.317 回答
1

我通过使用 ModuleDependency 属性解决了这个问题,它就像一个魅力

于 2010-06-11T18:57:32.537 回答
0

在 Bootstrapper 的 AddModule() 调用中,您可以指定依赖项。因此,您可以说 A 取决于 B 取决于 C,这将决定加载顺序。

http://msdn.microsoft.com/en-us/magazine/cc785479.aspx

于 2009-08-23T16:16:56.073 回答
0

与 Haukinger 建议的 SmartDirectoryCatalog 组合的 Fergus Bowns 答案也有类似的问题:Multiple DirectoryModuleCatalog in a Prism application 。我将其用于“可选依赖项”。希望这会对某人有所帮助。

PS:使用实际的 Prism Unity 7.2,您需要将 ModuleInfo 替换为 IModuleInfo

于 2020-04-25T00:23:53.200 回答
0

把它从死里复活,因为我似乎找到了一个不同的解决方案,有些人可能会觉得有用。我试过了,它有效,但我还没有感觉到所有的利弊。

我正在使用 DirectoryModuleCatalog 来获取所有模块的列表,这些模块都放置在一个文件夹中。但我注意到,在大多数情况下,我所有的“视图”模块都依赖于我的“服务”模块,这是一种非常常见的模式。任何服务都不应该依赖于视图。所以这让我开始思考,如果我们只是将所有服务模块放入一个文件夹并将所有视图模块放入另一个文件夹并以正确的顺序创建两个不同的目录会怎样。经过一番挖掘,我发现这篇文章提到了一个叫做 AggregateModuleCatalog 的东西,它用于将一堆目录连接在一起。我在这里找到了这个类的源代码。这是我使用它的方式:

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" });
    }
}

和 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);
    }
}

我还应该提到这篇文章指出以下内容:

为了演示使用 ModuleCatalog 的多种方式,使用 Unity 的快速入门实现了一个派生自 IModuleCatalog 的 AggregateModuleCatalog。此类不打算在运输应用程序中使用。

为什么会这样,我不确定。很想听听关于为什么会这样的任何解释。

于 2017-11-30T16:51:40.797 回答