2009-08-18 20 views
9

Estoy usando Prism V2 con DirectoryModuleCatalog y necesito que los módulos se inicialicen en un cierto orden. El orden deseado se especifica con un atributo en cada implementación de IModule.Cómo controlar el orden de inicialización del módulo en Prism

Esto es por lo que a medida que cada módulo se ha inicializado, añaden su punto de vista en una región TabControl y el orden de las pestañas tiene que ser determinista y controlado por el autor del módulo.

La orden no implica una dependencia, sino simplemente un orden en el que se deben inicializar. En otras palabras: los módulos A, B y C pueden tener prioridades de 1, 2 y 3 respectivamente. B no tiene una dependencia en A, solo necesita cargarse en la región de TabControl después de A. Para que tengamos un orden determinista y controlable de las pestañas. Además, B podría no existir en tiempo de ejecución; entonces cargarían como A, C porque la prioridad debería determinar el orden (1, 3). Si utilicé ModuleDependency, entonces el módulo "C" no podrá cargar sin todas sus dependencias.

que puede manejar la lógica de la manera de ordenar los módulos, pero no puedo averiguar donde poner dicha lógica.

Respuesta

13

no me gustaba la idea de usar ModuleDependency porque esto significaría que un módulo no se carga cuando el módulo B no estaba presente, cuando de hecho no hubo dependencia.En lugar de ello he creado un atributo de prioridad para decorar el módulo:

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

entonces he decorado los módulos de este tipo:

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

He creado un nuevo descendiente de 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; 
    } 
} 

Por último, Cambié el programa de arranque para usar este nuevo catálogo:

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

No estoy seguro de si la materia con carga montaje es la mejor manera de hacer las cosas, pero parece que funciona ...

+0

+1 He querido saber cómo hacer esto por bastante tiempo. Muchas muchas gracias. – Phil

+0

¡Excelente solución, exactamente lo que estaba buscando! – Lance

0

En la llamada AddModule() en Bootstrapper, puede especificar una dependencia. Entonces, puedes decir que A depende de B depende de C, y eso determinará el orden de carga.

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

+0

No estoy llamando "AddModule()"; Estoy usando DirectoryModuleCatalog que encuentra todos los IModules en una ruta determinada. –

3

Usted puede utilizar el atributo ModuleDependency en su clase del módulo de decirle al cargador que su módulo depende de otros módulos:

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

Los módulos no son dependencias; más detalle añadido a la pregunta –

+0

La dependencia no es física, en el sentido de que MyModule utiliza algo de SomeModule y SomeOtherModule, sino más bien es lógico como la carga de MyModule depende de estos otros dos módulos están cargando. A Prism no le importa el tipo de dependencias entre módulos, y el atributo ModuleDependency se puede usar para imponer cualquier tipo de dependencia. –

+0

en mi ejemplo de A, B, C que agregué recientemente - B podría no existir; entonces cargarían como A, C porque el orden sigue siendo correcto (1, 3). Si usara el ModuleDependency, a continuación del módulo "C" no va a ser capaz de cargar w/o todas sus dependencias. –

2

Puede reemplazar el valor por defecto IModuleInitializer para una instancia de una clase personalizada que, en lugar de inicializar los módulos inmediatamente después de cargarlos, los almacena en una lista de módulos. Cuando se hayan cargado todos los módulos, los inicializa en el orden que desee.

cómo lograr esto:

1) En el programa previo, anular el ConfigureContainer método para reemplazar el valor por defecto IModuleInitializer para una instancia de la clase MyModuleInitializer, pero manteniendo la inicialización por defecto con un nombre (por ejemplo, defaultModuleInitializer):


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


2) Crear el MyModuleInitializer clase que lleva a cabo el procedimiento de la deseada TiendaA-toda-entonces-tipo-y-initialize:


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

Tenga en cuenta que después de todos los módulos de puesta en marcha se han cargado, esta clase vuelve a invocar simplemente el inicializador predeterminado. Adapte la clase apropiadamente si esto no es lo que necesita.

+0

Esta es una solución bastante buena. La única parte difícil es saber cuándo "AllModulesLoaded". Dado que estoy usando DirectoryModuleCatalog, realmente no tengo una manera fácil de saber eso. Gracias por la respuesta; He resuelto el problema de una manera completamente diferente. –

+0

+1 Me gusta esta más que la respuesta aceptada: no me gusta la idea de que los módulos tengan que saber sobre el orden en que se cargan, y menos sobre los nombres de esos módulos. – stijn

1

Me resolvieron mediante el uso del atributo ModuleDependency y funcionó como un encanto

0

Llevar esto de vuelta de entre los muertos, ya que parecen haber encontrado una solución diferente que algunos pueden ser útiles. Lo probé y funciona, pero todavía tengo que sentir todos los pros y contras.

Estaba usando DirectoryModuleCatalog para obtener una lista de todos mis módulos que estaban ubicados en una sola carpeta. Pero noté que la mayoría de mis módulos de "Vista" dependían de mis módulos de "Servicio", y ese era un patrón bastante común. Ningún servicio debería depender de una vista. Eso me hizo pensar, ¿y si simplemente pusiéramos todos los módulos de servicio en una carpeta y todos los módulos de vista en otra y creáramos dos catálogos diferentes en el orden correcto? Algunos explorando alrededor y encontré este article que menciona algo llamado AggregateModuleCatalog, y se usa para concatenar juntos un montón de catálogos. Encontré el código fuente para esta clase here. Y así es como lo utilicé:

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

y la 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); 
    } 
} 

También debería mencionar que el artículo establece lo siguiente:

Para demostrar múltiples maneras de utilizar el ModuleCatalog , el inicio rápido usando Unity implementa un AggregateModuleCatalog que deriva de IModuleCatalog. Esta clase no está destinada a ser utilizada en una aplicación de envío.

¿Por qué? No estoy seguro. Me encantaría escuchar cualquier explicación sobre por qué podría ser.

Cuestiones relacionadas