Confessions of a Dangerous Mind

Brain.FlushBuffer()
posts - 176, comments - 234, trackbacks - 93

WPF Prism Technical Concepts: Modules

Il concetto di Modulo è uno dei pilastri portanti della logica di Prism. Il Modulo rappresenta l’unità logica fondamentale della nostra Composite Application, e pertanto deve obbligatoriamente essere compreso a fondo. Effettivamente, per implementare un modulo “compatibile” con Prism, le azioni da intraprendere non sono poi molte, ovvero si tratta semplicemente di crare una classe ed implementare un’interfaccia; per la precisione è l’interfaccia IModule, che si può vedere nel seguente snippet:

   1: public interface IModule
   2: {
   3:     void Initialize();
   4: }

Semplice ed immediata: un solo metodo da implementare, il quale verrà richiamato nella fase di inizializzazione dei moduli presenti nel catalogo dell’applicazione.

Ma cosa può “fare” o “contenere” un modulo? Praticamente qualunque cosa:

  • Può offrire un’interfaccia utente con cui interagire;
  • Può registrare una serie di servizi condivisi con altri moduli;
  • Può collaborare comunicando con altri moduli, registrandosi in ascolto di eventi condivisi
  • Può modellare un intero sottosistema della nostra applicazione o esporre adattatori per l’integrazione con sistemi di terze parti

I Moduli sono strettamente legati con il ciclo di vita dell’applicazione; essi seguono sempre questo flusso:

  1. Definizione dei moduli: i “meta-dati” dei moduli vengono caricati nel catalogo
  2. Caricamento dei moduli: gli assembly contenenti i moduli vengono caricati in memoria
  3. Inizializzazione dei moduli: le classi rappresentanti i moduli vengono create e viene invocato il metodo Initialize

Vediamo in dettaglio le varie fasi del ciclo di vita di un modulo.

Definizione dei moduli

I moduli possono essere definiti in vari metodi, tra i quali si possono individuare i seguenti:

  • Definizione da codice: il catalogo viene popolato da codice, ovvero ciascun modulo viene esplicitamente aggiunto al catalogo;
  • Definizione da XAML: un file XAML funge da repository di definizione dei moduli e nella chiamata GetCatalog deve essere utilizzato il metodo CreateCatalogFromXAML(…);
  • Definizione da file .config: è possibile creare un catalogo che vada a leggere le definizioni dei moduli dal file di configurazione dell’applicazione
  • Definizione da directory (percorso): in alcuni casi può essere utile indicare un percorso dal quale caricare i moduli. La directory in oggetto verrà scansionata e verranno caricati gli assembly trovati al suo interno

Nel primo caso, un’aggiunta di un nuovo modulo necessita obbligatoriamente di una ricompilazione dell’applicazione, mentre gli altri tre metodi possono essere utilizzati per aggiungere con facilità nuovi moduli all’applicazione in tempi successivi.

Caricamento dei moduli

Una volta definiti nel catalogo, i moduli devono esere caricati in memoria. Questo processo è piuttosto “semplice” (per noi) e demandato completamente alla logica di caricamento di CAL e del Framework. Nel caso in cui si impieghi Prism con Silverlight, i moduli (xap) verranno scaricati dagli URL di riferimento.

Inizializzazione dei moduli

Nella fase di inizializzazione, i moduli possono:

  • Registrare servizi o tipi condivisi nel Container (UnityContainer)
  • Registrare le Views nelle Region messe a disposizione dalla shell
  • Registrarsi ad eventi pubblicati da altri moduli o pubblicare eventi condivisi

La fase di inizializzazione di un modulo è sicuramente la più importante e delicata, in quanto è in questo momento che il modulo si integra con il resto dell’applicazione. E’ perciò necessario preoccuparsi di come il modulo si dovrà comportare nel contesto applicativo, evitando ad esempio di creare referenze tra moduli diversi, che potrebbero in seguito portare a riferimenti circolari tra moduli. E’ sufficiente seguire poche “regole” per la buona creazione dei moduli:

  • Inserire ogni modulo in un proprio namespace
  • Evitare (se possibile) di creare riferimenti tra moduli, ovvero riferimenti a tipi contenuti in altri moduli
  • Mantenere le interfacce considerate condivise in un assembly separato, per migliorare la testabilità ed il riutilizzo del codice

A seguire lo snippet di codice di un semplice modulo:

   1: namespace SimpleModule1
   2: {
   3:     public class SimpleModule1 : IModule
   4:     {        
   5:  
   6:         private IRegionManager _regionManager;
   7:         private IUnityContainer _container;
   8:  
   9:         public SimpleModule1(IRegionManager regionManager, IUnityContainer container)
  10:         {
  11:             _regionManager = regionManager;
  12:             _container = container;           
  13:         }
  14:      
  15:         #region IModule Members   
  16:  
  17:         public void Initialize()
  18:         {
  19:             //View Discovery            
  20:             _regionManager.RegisterViewWithRegion("MainRegion", typeof(Views.SimpleModule1View));
  21:             //Register service
  22:             _container.RegisterInstance<SimpleModule1.Services.IEmployeesService>(new SimpleModule1.Services.EmployeesService(), new ContainerControlledLifetimeManager());
  23:         }
  24:  
  25:         #endregion
  26:     }
  27: }

In questo snippet vengono iniettate nel costruttore le dipendenze da IRegionManager (utilizzato per registrare le Views all’interno delle regions esposte dalla shell) e da IUnityContainer (utilizzato per registrare l’istanza del servizio di tipo IEmployeesService, condiviso per tutta l’applicazione). Notate che era possibile passare anche solo la referenza al container, essndo possibile poi risolvere il RegionManager direttamente dal container.

Dipendenze tra moduli

Concludo queso lungo post riguardante i moduli parlando della possibilità di creare dipendenze tra essi. E’ altresì possibile, infatti, creare una dipendenza tra due moduli, in modo che il “Module Loader” si preoccupi di caricare prima il modulo “indipendente” e solo successivamente il modulo che necessita della presenza del modulo “indipendente”. In questo modo è possibile creare una sequenza di inizializzazione che assicura una corretta creazione dei vari moduli. La dipendenza può essere imposta:

  • da codice, tramite attributi
  • da file di configurazione, mediante utilizzo dei tag XML corretti
  • da file catalog, impiegando i tag XAML preposti alla gestione delle dipendenze

Ricordo infine che il caricamento dei moduli può anche essere effettuato “OnDemand”, ovvero è possibile effettuare il caricamento di un modulo non immediatamente dopo la scansione del catalogo, bensì tramite il metodo LoadModule dell’interfaccia IModuleManager. Questo approccio è particolarmente utile quando si desidera caricare un modulo ad un azione di un utente oppure a seguito di determinate situazioni che si vengono a creare durante il ciclo di vita dell’applicazione. Lungi dall’aver terminato di trattare l’argomento Modulo, chiudo qui questo post, rinviando ad un successivo le logiche che spingono a realizzare i vari componenti di un modulo come componenti basati su pattern M-V-VM. Nel frattempo, chi ha voglia di approfondire l’argomento può scaricare Prism qui.

Print | posted on venerdì 21 agosto 2009 19:54 |

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET