2009-08-18 5 views
9

Ich verwende Prism V2 mit einem DirectoryModuleCatalog und ich brauche die Module in einer bestimmten Reihenfolge initialisiert werden. Die gewünschte Reihenfolge wird mit einem Attribut für jede IModule-Implementierung angegeben.So steuern Sie die Reihenfolge der Modulinitialisierung in Prism

Dies ist so, dass, wenn jedes Modul initialisiert wird, sie ihre Ansicht in eine TabControl-Region hinzufügen und die Reihenfolge der Registerkarten deterministisch sein und vom Autor des Moduls gesteuert werden muss.

Die Reihenfolge impliziert keine Abhängigkeit, sondern nur eine Reihenfolge, in der sie initialisiert werden sollen. Mit anderen Worten: Die Module A, B und C können Prioritäten von 1, 2 bzw. 3 haben. B hat keine Abhängigkeit von A - es muss nur in den TabControl-Bereich nach A geladen werden. Damit haben wir eine deterministische und kontrollierbare Reihenfolge der Registerkarten. Außerdem ist B möglicherweise nicht zur Laufzeit vorhanden. sie würden also als A, C geladen, da die Priorität die Reihenfolge bestimmen sollte (1, 3). Wenn ich die ModuleDependency verwendet, kann das Modul "C" nicht ohne alle Abhängigkeiten geladen werden.

Ich kann die Logik verwalten, wie die Module zu sortieren, aber ich kann nicht herausfinden, wo, um die Logik zu setzen.

Antwort

13

ich nicht die Idee der Verwendung von ModuleDependency gefallen hat, weil dies das Modul eine würde nicht geladen bedeuten würde, wenn das Modul b nicht vorhanden war, wenn In der Tat gab es keine Abhängigkeit.Stattdessen habe ich ein Prioritätsattribut, das Modul zu dekorieren:

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

ich dann die Module wie folgt eingerichtet:

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

ich ein neues Nachkomme DirectoryModuleCatalog erstellt:

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

Schließlich Ich änderte den Bootstrapper, um diesen neuen Katalog zu verwenden:

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

Ich bin mir nicht sicher, ob die Sachen mit Montage Laden der beste Weg ist, die Dinge zu tun, aber es scheint zu funktionieren ...

+0

+1 Ich wollte schon lange wissen, wie das geht. Vielen vielen Dank. – Phil

+0

Ausgezeichnete Lösung, genau das, was ich gesucht habe! – Lance

0

Im AddModule() - Aufruf im Bootstrapper können Sie eine Abhängigkeit angeben. Man kann also sagen, A hängt von B ab, hängt von C ab, und das bestimmt die Ladereihenfolge.

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

+0

Ich rufe nicht "AddModule()"; Ich benutze den DirectoryModuleCatalog, der alle IModule in einem bestimmten Pfad findet. –

3

Sie können das ModuleDependency Attribut auf dem Modul Klasse den Lader sagen, dass Ihr Modul auf anderen Modulen abhängt:

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

Die Module sind keine Abhängigkeiten; mehr Details zur Frage hinzugefügt –

+0

Die Abhängigkeit ist nicht physisch in dem Sinne, dass MyModule etwas von SomeModule und SomeOtherModule verwendet, aber es ist logisch, da das Laden von MyModule abhängig von diesen anderen zwei Modulen geladen wird. Prism kümmert sich nicht um die Art von Abhängigkeiten zwischen Modulen, und das ModuleDependency-Attribut kann verwendet werden, um jede Art von Abhängigkeit zu erzwingen. –

+0

in meinem Beispiel von A, B, C, die ich kürzlich hinzugefügt habe - B könnte nicht existieren; Also würden sie als A, C laden, weil die Reihenfolge immer noch korrekt ist (1, 3). Wenn ich die ModuleDependency verwendet, dann Modul "C" wird nicht in der Lage, ohne alle Abhängigkeiten laden. –

2

Sie können den Standard ersetzen IModuleInitializer für eine Instanz eine benutzerdefinierte Klasse, die die Module direkt nach dem Laden nicht initialisiert, sondern in einer Modulliste speichert. Wenn alle Module geladen sind, initialisieren Sie sie in der von Ihnen gewünschten Reihenfolge.

Wie dies zu erreichen:

1) In dem Bootstrap-Programm, überschreiben den ConfigureContainer Methode, um den Standard IModuleInitializer für eine Instanz der MyModuleInitializer Klasse zu ersetzen, noch die Aufrechterhaltung des Standard initializer mit einem Namen (beispielsweise defaultModuleInitializer):


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


2) Erstellen Sie die MyModuleInitializer Klasse, die die gewünschte storea-all-then-Art-und-initialize Prozedur führt:


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

Beachten Sie, dass, nachdem alle Start Module geladen wurden, diese Klasse einfach kehrt Aufruf der Standardinitialisierer. Passen Sie die Klasse entsprechend an, wenn Sie dies nicht benötigen.

+0

Das ist eine ziemlich gute Lösung. Der einzige knifflige Teil ist das Wissen, wann "AllModulesLoaded". Da ich den DirectoryModuleCatalog verwende, habe ich nicht wirklich eine einfache Möglichkeit, das zu wissen. Danke für die Antwort; Ich habe das Problem auf eine ganz andere Art und Weise gelöst. –

+0

+1 Ich mag dieses eine mehr als die angenommene Antwort: da mag ich nicht die Idee, dass Module über die Reihenfolge, in der sie geladen werden, wissen müssen, geschweige denn über die Namen dieser Module. – stijn

1

ich beschlossen dies durch das ModuleDependency Attribut und es funktionierte wie ein Zauber

0

bringen diese von den Toten zurück, wie ich scheinen eine andere Lösung gefunden zu haben, Einige könnten nützlich finden. Ich habe es ausprobiert und es funktioniert, aber ich muss noch alle Vor- und Nachteile heraushören.

Ich habe DirectoryModuleCatalog verwendet, um eine Liste aller meiner Module zu erhalten, die alle in einem einzigen Ordner abgelegt wurden. Aber ich bemerkte, dass meine "View" -Module zum größten Teil von meinen "Service" -Modulen abhingen, und das war ein ziemlich übliches Muster. Kein Service sollte von einer Ansicht abhängen. Das brachte mich dazu, darüber nachzudenken, was passiert, wenn wir alle Servicemodule in einen Ordner und alle Anzeigemodule in einen anderen legen und zwei verschiedene Kataloge in der richtigen Reihenfolge erstellen. Einige graben herum und ich habe dieses article gefunden, das etwas nennt, das ein AggregateModuleCatalog genannt wird, und es wird verwendet, um eine Reihe von Katalogen zusammen zu verketten. Ich habe den Quellcode für diese Klasse here gefunden. Und hier ist, wie ich es benutzt:

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

und die 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); 
    } 
} 

Ich sollte auch erwähnen, dass der Artikel besagt Folgendes:

Um mehrere Möglichkeiten, zu demonstrieren die ModuleCatalog mit Die QuickStart mit Unity implementiert einen AggregateModuleCatalog, der von IModuleCatalog ableitet. Diese Klasse ist nicht für die Verwendung in einer Versandanwendung in vorgesehen.

Warum das ist, bin ich mir nicht sicher. Würde gerne irgendwelche Erklärungen hören, warum das so sein könnte.