2009-08-18 59 views
9

我正在使用Prism V2和DirectoryModuleCatalog,我需要按特定順序對模塊進行初始化。所需的順序是通過每個IModule實現上的屬性來指定的。如何控制棱鏡模塊初始化的順序

這使得每個模塊都被初始化後,他們將其視圖添加到TabControl區域,並且選項卡的順序需要由模塊作者確定和控制。

該命令並不意味着依賴關係,而只是它們應該被初始化的順序。換句話說,模塊A,B和C可能分別具有1,2和3的優先級。 B對A沒有依賴性 - 它只需要在 A之後加載到TabControl區域中。以便我們具有確定性和可控制的選項卡順序。另外,B在運行時可能不存在;因此它們將作爲A,C加載,因爲優先級應確定順序(1,3)。如果我使用了ModuleDependency,那麼模塊「C」將無法加載所有它的依賴關係。

我可以管理如何對模塊進行排序的邏輯,但我無法弄清楚其中說放邏輯。

回答

13

我不喜歡使用ModuleDependency的主意,因爲這將意味着模塊時,模塊B是不存在一個不會加載,當實際上沒有依賴關係。相反,我創建了一個優先級屬性來裝飾模塊:

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

我不知道如果與裝配加載的東西是做事情的最好方法,但它似乎工作...

+0

+1我一直想知道如何做到這一點。非常非常感謝你。 – Phil 2011-02-16 22:46:53

+0

優秀的解決方案,正是我所期待的! – Lance 2012-10-02 08:20:33

0

在引導程序中的AddModule()調用中,可以指定依賴關係。所以,你可以說A依賴於B取決於C,並且這將決定加載順序。

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

+0

我沒有調用「AddModule()」;我使用的DirectoryModuleCatalog可以找到給定路徑中的所有IModules。 – 2009-08-24 17:57:22

3

您可以使用您的模塊類ModuleDependency屬性來告訴你模塊依賴於其他模塊裝載機:

[ModuleDependency("SomeModule")] 
[ModuleDependency("SomeOtherModule")] 
public class MyModule : IModule 
{ 
} 
+0

模塊不依賴於;在問題 – 2009-08-24 17:57:59

+0

中增加了更多詳細信息從MyModule使用SomeModule和SomeOtherModule中的某些內容的意義上說,依賴關係不是物理的,而是它是邏輯的,因爲MyModule的加載取決於正在加載的其他兩個模塊。 Prism並不關心模塊之間的依賴關係,並且可以使用ModuleDependency屬性來強制執行任何類型的依賴關係。 – 2009-08-24 18:43:20

+0

在我最近添加的A,B,C的示例中,B可能不存在;所以他們會加載爲A,C,因爲訂單仍然是正確的(1,3)。 如果我使用了ModuleDependency,那麼模塊「C」將無法加載所有它的依賴關係。 – 2009-08-24 19:59:29

2

您可以替換默認IModuleInitializer的實例一個自定義類,它不是在加載後立即初始化模塊,而是將它們存儲在模塊列表中。當所有的模塊都被加載後,你可以按你想要的順序初始化它們。

如何實現這一點:

1)在引導程序,覆蓋ConfigureContainer方法來替代默認IModuleInitializerMyModuleInitializer類的實例,但有一個名字保持默認初始值(例如,defaultModuleInitializer):


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


2)創建MyModuleInitializer類執行所需storea-全然後-排序和初始化過程:


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

注意,所有的啓動模塊已被加載之後,這個類還原爲簡單地調用默認的初始化器。如果這不是你需要的,適當地調整班級。

+0

這是一個很好的解決方案。唯一棘手的部分是知道什麼時候「AllModulesLoaded」。由於我使用的是DirectoryModuleCatalog,因此我並不知道這個簡單的方法。 感謝您的回答;我已經用完全不同的方式解決了這個問題。 – 2009-09-22 16:23:44

+0

+1我比接受的答案更喜歡這一點:我不喜歡這樣的想法,即模塊必須知道它們的加載順序,更不用說這些模塊的名稱。 – stijn 2011-12-30 15:33:56

1

我使用ModuleDependency屬性解決了這個和它的工作就像一個魅力

0

瞻從死裏這回,我似乎已經找到了不同的解決方案,有些可能會有用。我試了一下,它的工作原理,但我還沒有感覺到所有的優點和缺點。

我正在使用DirectoryModuleCatalog獲取所有模塊的列表,這些模塊都放置在一個文件夾中。但我注意到,大部分我的「視圖」模塊都依賴於我的「服務」模塊,這是一個很常見的模式。沒有服務應該取決於視圖。所以這讓我想到了,如果我們只將所有服務模塊放到一個文件夾中,並將所有視圖模塊放入另一個模塊中,並以正確的順序創建兩個不同的目錄,該怎麼辦?一些挖掘,我發現這article,提到了一種稱爲AggregateModuleCatalog的東西,它用來連接在一起的一堆目錄。我找到了這個類here的源代碼。這裏是我如何使用它:

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的QuickStart實現了一個AggregateModuleCatalog, 從IModuleCatalog派生。本課程不適用於 運輸應用程序。

爲什麼這是我不確定。希望聽到任何解釋,爲什麼可能。