Versione per la stampa       Invia     
Valuta il contenuto e lascia un commento
Related Articles

Udi Dahan spiega il modo in cui il suo team ha identificato e superato problemi imprevisti durante lo sviluppo di un software su larga scala e l'applicazione finanziaria dei servizi.

Udi Dahan

MSDN Magazine Aprile 2009

...

Read more!

In questo articolo viene illustrato in che modo .NET Services all'interno di Azure Services Platform consente di semplificare il flusso di lavoro delle applicazioni per il cloud computing.

Aaron Skonnard

MSDN Magazine Aprile 2009

...

Read more!

L'utilizzo della memoria può influire direttamente sulla velocità di esecuzione delle applicazioni ed è pertanto un fattore importante da ottimizzare. In questo articolo vengono illustrate le nozioni di base sull'ottimizzazione della memoria per programmi .NET.

Subramanian Ramaswamy e Vance Morrison

MSDN Magazine Giugno 2009

...

Read more!

In questo articolo viene spiegato come integrare una soluzione basata sui Servizi Windows con SharePoint. Il risultato consente il provisioning, l'avvio, l'interruzione e la rimozione delle istanze di servizio tramite Amministrazione centrale SharePoint 3.0.

Pav Cherny

MSDN Magazine Aprile 2009

...

Read more!

Alla fine dello scorso anno, Microsoft ha rilasciato i controlli Calendar e DatePicker per WPF nel WPF Toolkit. Ne illustreremo il funzionamento e le possibilità di personalizzazione.

Charles Petzold

MSDN Magazine Giugno 2009

...

Read more!

Popular Articles

The MVP pattern helps you separate your logic and keep your UI layer free of clutter. This month learn how.

Jean-Paul Boodhoo

MSDN Magazine August 2006

...

Read more!

Kenny Kerr canta le lodi del nuovo Feature Pack di Visual C++ 2008 che porta moderne funzionalità a Visual C++.

Kenny Kerr

MSDN Magazine May 2008

...

Read more!

This article introduces 10 development tools that can increase your productivity, give you a better understanding of .NET, and maybe even change the way that you develop applications. The tools covered include NUnit to write unit tests, Reflector to examine assemblies, FxCop to police your code, Regulator to build regular expressions, NDoc to create code documentation and five more.

James Avery

MSDN Magazine July 2004

...

Read more!

Chris Tavares spiega come il modello Model View Controller di ASP.NET MVC Framework aiuti a creare applicazioni Web flessibili e facilmente verificabili.

Chris Tavares

MSDN Magazine March 2008

...

Read more!

When incorporating the ASP.NET DataGrid control into your Web apps, common operations such as paging, sorting, editing, and deleting data require more effort than you might like to expend. But all that is about to change. The GridView control--the successor to the DataGrid-- extends the DataGrid's functionality it in a number of ways. First, it fully supports data source components and can automatically handle data operations, such as paging, sorting, and editing, as long as its bound data source object supports these capabilities. In addition, ...

Read more!

Prism
Modelli per la creazione di applicazioni composite con WPF
Glenn Block

In questo articolo verranno discussi i seguenti argomenti:
  • Elementi di base delle applicazioni composite
  • Inizializzazione del programma di avvio automatico e dei moduli
  • Regioni e RegionManager
  • Viste, comandi ed eventi
In questo articolo verranno utilizzate le seguenti tecnologie:
Composite Application Guidance for WPF
Tecnologie come Windows ® Presentation Foundation (WPF) e Silverlight™ forniscono agli sviluppatori un mezzo semplice e dichiarativo per la creazione rapida di applicazioni che garantiscono agli utenti esperienze di qualità. Queste tecnologie facilitano l'ulteriore separazione del livello di presentazione da quello logico, ma non risolvono il vecchio problema della creazione di applicazioni gestibili.
In progetti di piccole dimensioni, è logico supporre che uno sviluppatore di media esperienza possa progettare e creare un'applicazione facilmente gestibile ed espandibile. Tuttavia, dal momento che le parti mobili e il numero di persone che lavorano su tali parti sono in aumento, la gestione del progetto risulta di gran lunga più difficoltosa.
Le applicazioni composite rappresentano una soluzione a tale problema. In questo articolo verrà spiegato cos'è un'applicazione composita e come si fa a crearne una utilizzando le funzionalità di WPF. Nel corso della spiegazione, verrà presentato il documento Composite Application Guidance for WPF contenente le nuove linee guida per le applicazioni composite per WPF (precedentemente noto con il nome in codice "Prism"); questo documento è stato pubblicato dal team Microsoft responsabile dei modelli e delle procedure.

Il problema: le applicazioni monolitiche
Farò subito un esempio per chiarire quanto siano necessarie le applicazioni composite. Contoso Financial Investments fornisce un'applicazione per la gestione di portafogli di investimenti. Utilizzando questa applicazione, un utente è in grado di visualizzare gli investimenti attuali e le novità relative a tali investimenti, aggiungere elementi a una watchlist ed eseguire transazioni di acquisto e vendita.
Se l'applicazione è stata creata mediante una tradizionale applicazione WPF con controlli utente, si inizia con una finestra di primo livello e si aggiungono controlli utente per ciascuna delle diverse funzioni menzionate. In questo caso, si avranno controlli utente come PositionGrid, PositionSummary, TrendLine e WatchList (vedere Figura 1). Ciascun controllo utente viene creato durante la fase di progettazione all'interno della finestra principale in modo manuale in XAML oppure mediante uno strumento di progettazione come Expression Blend™.
Figura 1 Controlli utente in un'applicazione monolitica (fare clic sull'immagine per ingrandirla)
Verranno poi utilizzati RoutedEvents, RoutedCommands e l'associazione dati per collegare il tutto. Per saperne di più su questo argomento, leggere l'articolo di Brian Noyes, "Informazioni su eventi e comandi indirizzati in WPF" nel presente numero (msdn.microsoft.com/magazine/cc785480). PositionGrid dispone di un RoutedCommand associato per la selezione. Nel gestore Execute del comando, viene generato un evento TickerSymbolSelected ogni volta che viene selezionata una posizione. TrendLine e NewsReader sono collegati al fine di ascoltare l'evento TickerSymbolSelected ed eseguire il rendering del contenuto in base al segno di spunta selezionato.
In questo caso, l'applicazione è strettamente collegata a ciascuno dei controlli. L'interfaccia utente dispone di una quantità di logica considerevole per la coordinazione delle diverse operazioni. Sono inoltre presenti interdipendenze tra i controlli.
In ragione di queste dipendenze, non esiste un metodo semplice per suddividere l'applicazione in un form in cui sia possibile sviluppare ciascuna operazione in modo indipendente. È possibile inserire tutti i controlli utente in un assembly separato per migliorarne la gestibilità; questo metodo tuttavia sposta unicamente il problema dall'applicazione principale all'assembly dei controlli. È molto difficile apportare modifiche significative o introdurre nuove funzionalità in questo modello.
Complichiamo ora la situazione aggiungendo due nuovi requisiti aziendali. Il primo è quello di aggiungere una schermata contenente note sui fondi che visualizzi le note personali sul fondo selezionato quando vi si fa doppio clic sopra. Il secondo è quello di aggiungere una nuova schermata che visualizzi un elenco di collegamenti ipertestuali associati al fondo selezionato. A causa di vincoli temporali, è necessario che diversi team sviluppino queste funzionalità in parallelo.
Ciascun team svilupperà controlli separati: FundNotes e FundLinks. Per aggiungere entrambi i controlli allo stesso assembly, è necessario aggiungerli uno alla volta al progetto di controllo. Inoltre, è necessario aggiungerli al form principale: ciò significa che occorre unire al form principale le modifiche effettuate al codice e a XAML da ciascun controllo. Questo tipo di operazione può risultare estremamente delicata, soprattutto per quanto riguarda le applicazioni esistenti.
Come è possibile reimportare tutte queste modifiche nell'applicazione principale? Prima di aver terminato l'operazione, si sarà probabilmente impiegato parecchio tempo a effettuare integrazioni e confronti nel controllo del codice sorgente. Se si commettono errori durante l'applicazione delle modifiche o si sovrascrive qualcosa accidentalmente, si otterrà un'applicazione che non funziona in modo corretto. La soluzione consiste nella riformulazione del progetto dell'applicazione.

Applicazioni composite
Un'applicazione composita è costituita da moduli collegati liberamente, rilevati in modo dinamico e composti in fase di esecuzione. I moduli contengono componenti visuali e non, che rappresentano diverse suddivisioni verticali del sistema (vedere Figura 2). I componenti visuali (viste) sono inclusi in una shell comune che funziona da host per tutti i contenuti dell'applicazione. Le applicazioni composite offrono servizi che uniscono questi componenti a livello del modulo. I moduli possono garantire servizi aggiuntivi relativi alla specifica funzionalità dell'applicazione.
Figura 2 Componenti di un'applicazione composita (fare clic sull'immagine per ingrandirla)
In linea generale, un'applicazione composita è un'implementazione del modello di progettazione di una vista composita, che descrive una struttura ricorsiva dell'interfaccia utente costituita da viste contenenti elementi figlio, che sono anch'essi viste. Le viste sono a loro volta costituite da un meccanismo che, invece di essere composto staticamente in fase di progettazione, viene solitamente composto in fase di esecuzione.
Per illustrare i vantaggi del modello, basti pensare a un sistema di inserimento ordini in cui sono presenti più istanze per un ordine. Ciascuna istanza potrebbe implicare un'estrema complessità nella visualizzazione di intestazione, dettagli, spedizione e ricevute. Con l'evoluzione del sistema, potrebbe essere necessario visualizzare ulteriori informazioni. Si immagini inoltre che le parti dell'ordine vengano visualizzate in modo diverso a seconda del tipo di ordine.
Se una schermata di questo tipo viene creata in modo statico, è possibile ritrovarsi con una gran quantità di logica condizionale per la visualizzazione delle diverse parti dell'ordine. Inoltre, aggiungendo nuove funzionalità, aumenta la probabilità di possibili problemi nella logica esistente. Tuttavia, se si implementa il sistema come vista composita, si aggiungono dinamicamente solo gli elementi rilevanti della schermata dell'ordine. Ciò significa che è possibile fare a meno della logica di visualizzazione condizionale e aggiungere nuove schermate figlio senza apportare modifiche alla vista dell'ordine stessa.
I moduli forniscono le viste dalle quali viene creata la vista composita principale (nota anche come shell). I moduli non rimandano mai direttamente l'uno all'altro, né alla shell. Utilizzano invece servizi che comunicano reciprocamente e con la shell al fine di rispondere alle azioni dell'utente.
Avere un sistema costituito da moduli offre diversi vantaggi. I moduli possono aggregare dati provenienti da diversi sistemi di back-end all'interno della stessa applicazione. Inoltre, il sistema può svilupparsi con maggiore facilità nel corso del tempo. Con il mutare dei requisiti del sistema, è possibile aggiungere a quest'ultimo nuovi moduli con maggiore semplicità rispetto a un sistema non modulare. I moduli esistenti possono essere sviluppati in modo più indipendente ed è pertanto possibile migliorarne la testabilità. Infine, i moduli possono essere sviluppati, testati e mantenuti da team diversi.

Linee guida per le applicazioni composite
Il team Microsoft responsabile dei modelli e delle procedure ha pubblicato di recente la prima versione del documento Composite Application Guidance for WPF (disponibile in microsoft. com/CompositeWPF). Questo nuovo documento fornisce linee guida su come utilizzare al meglio le funzionalità e il modello di programmazione di WPF. Allo stesso tempo, il team ha anche migliorato le precedenti linee guida per applicazioni composite in base al feedback fornito dai team di prodotti Microsoft, dai clienti e dalla community di .NET.
Composite Application Guidance for WPF include un'implementazione di riferimento (l'applicazione Stock Trader menzionata in precedenza), una libreria CAL (Composite Application Library), le applicazioni delle Guide rapide e la documentazione tecnica e di progettazione.
La libreria CAL fornisce i servizi e il plumbing per la creazione di applicazioni composite. Si avvale di un modello di composizione che consente di utilizzare i propri servizi singolarmente o collettivamente come parti di un'applicazione CAL. Tutti i servizi sono inoltre facilmente sostituibili senza la ricompilazione della CAL. Ad esempio, la CAL viene fornita con un'estensione che utilizza Unity Application Block per l'inserimento di dipendenze, ma consente di sostituire tale blocco con il proprio servizio di inserimento di dipendenze.
Le Guide rapide forniscono applicazioni mirate di piccole dimensioni che effettuano dimostrazioni utilizzando ciascun componente CAL. Sono progettate per facilitare l'acquisizione di concetti senza dover memorizzare tutto in una sola volta.
Nel resto dell'articolo verranno discussi diversi concetti tecnici relativi alle applicazioni composite illustrati nell'implementazione di riferimento Stock Trader. Tutto il codice presente in questo articolo è disponibile nel documento Composite Application Guidance for WPF, che può essere scaricato da MSDN® all'indirizzo msdn.microsoft.com/library/cc707819.

Programma di avvio automatico e contenitori
Quando si creano applicazioni composite con la CAL, è necessario procedere per prima cosa all'inizializzazione di diversi servizi di composizione di base. In questa fase entra in gioco il programma di avvio automatico, che esegue tutte le funzioni necessarie per il verificarsi della composizione (vedere Figura 3). Sotto molti aspetti, si tratta del metodo Main di un'applicazione CAL.
Figura 3 Attività di inizializzazione del programma di avvio automatico (fare clic sull'immagine per ingrandirla)
Per prima cosa viene inizializzato il contenitore. Per contenitore si intende un'inversione del controllo (IoC)/contenitore per l'inserimento di dipendenze (DI). Se questi termini sono poco chiari, consultare l'articolo di MSDN Magazine "Gestione delle dipendenze software per applicazioni più flessibili" di James Kovacs (msdn.microsoft.com/magazine/cc337885).
I contenitori hanno un ruolo fondamentale in un'applicazione CAL. Il contenitore rappresenta l'archivio di tutti i servizi applicativi utilizzati nella composizione ed è responsabile dell'inserimento di tali servizi ovunque siano richiesti. Per impostazione predefinita, la CAL comprende un UnityBootstrapper astratto che utilizza il framework Unity di modelli e procedure come contenitore. Tuttavia, la CAL è stata creata per interagire con altri contenitori come Windsor, Structure Map e Sprint.NET. Nessuna classe presente nella CAL (a parte le estensioni Unity) dipende da un contenitore specifico.
Non appena il contenitore viene configurato, vengono registrati automaticamente diversi servizi di base utilizzati per la composizione, tra cui un logger e un aggregatore di eventi, e il programma di avvio automatico di base consente di sottoporre a override qualsiasi servizio tra quelli citati. Uno dei servizi che viene registrato automaticamente è IModuleLoader. Se si sottopone a override il metodo ConfigureContainer nel programma di avvio automatico, è possibile registrare il proprio caricatore di moduli.
protected override void ConfigureContainer() {
  Container.RegisterType<IModuleLoader, MyModuleLoader>();
  base.ConfigureContainer();
}
Se non si desidera che i servizi vengano registrati per impostazione predefinita, è possibile disattivare anche questa impostazione. È sufficiente chiamare l'overload del metodo Run nel programma di avvio automatico e fornire un valore false per il parametro useDefaultConfiguration.
Successivamente vengono configurati gli adattatori della regione. Per regione si intende una posizione (solitamente si tratta di un contenitore, ad esempio un pannello) nell'interfaccia utente in cui i moduli possono inserire gli elementi dell'interfaccia utente. Gli adattatori della regione gestiscono il collegamento tra diversi tipi di regione a cui si deve accedere. Questi adattatori sono associati a un'istanza singleton RegionAdapterMappings presente nel contenitore.
A questo punto la shell è stata creata. La shell è la finestra di livello superiore in cui si definiscono le regioni. Invece di essere dichiarata in App.Xaml, la shell viene creata mediante il metodo CreateShell dall'interno del programma di avvio automatico specifico per l'applicazione. Tutto questo per garantire che l'inizializzazione del programma di avvio automatico venga completata prima della visualizzazione della shell.
È sorprendente considerare come non sia necessario disporre di una shell nell'applicazione. Ad esempio, si potrebbe disporre di un'applicazione WPF esistente a cui si desidera aggiungere funzionalità CAL. Invece di controllare l'intera schermata mediante la CAL, si potrebbe aggiungere un pannello che costituirà una regione di livello superiore. In questo caso non è necessario definire una shell. Il programma di avvio automatico può ignorare semplicemente la visualizzazione della shell nel caso non sia definita.

Inizializzazione dei moduli
Da ultimo, vengono inizializzati i moduli. Un modulo in un'applicazione CAL è un'unità di separazione all'interno di una struttura composita che può essere distribuito come assembly separato, sebbene questo non sia un requisito. In un'applicazione CAL, il modulo è l'unità che contiene la maggior parte delle funzionalità.
Il caricamento dei moduli è un processo a due fasi che comprende due servizi: IModuleEnumerator e IModuleLoader. L'enumeratore è responsabile dell'individuazione dei moduli disponibili. Restituisce diversi insiemi di oggetti ModuleInfo che contengono metadati riguardanti il modulo. UnityBootstrapper contiene un GetModuleEnumerator che deve essere sottoposto a override per restituire l'enumeratore corretto; in caso contrario, sarà generata un'eccezione in fase di esecuzione. La CAL contiene enumeratori per individuare statisticamente i moduli dall'analisi della directory e dalla configurazione.
Per il caricamento, la CAL dispone di un ModuleLoader che viene utilizzato per impostazione predefinita da UnityBootstrapper. ModuleLoader carica e quindi inizializza ciascuno degli assembly dei moduli (se non sono già stati caricati). I moduli possono specificare dipendenze da altri moduli. ModuleLoader creerà un albero di dipendenze e inizializzerà i moduli nell'ordine corretto in base a tali specifiche.

Utilizzo del programma di avvio automatico
Poiché UnityBootstrapper è una classe astratta, StockTraderRIBootstrapper lo sottopone a override (vedere Figura 4). Il programma di avvio automatico dispone di diversi metodi virtuali protetti che consentono di inserire la propria funzionalità specifica per l'applicazione.
public class StockTraderRIBootstrapper : UnityBootstrapper {
  private readonly EntLibLoggerAdapter _logger = new EntLibLoggerAdapter();

  protected override IModuleEnumerator GetModuleEnumerator()  {
    return new StaticModuleEnumerator()
    .AddModule(typeof(NewsModule))
    .AddModule(typeof(MarketModule))
    .AddModule(typeof(WatchModule), "MarketModule")
    .AddModule(typeof(PositionModule), "MarketModule", "NewsModule");
  }

  protected override ILoggerFacade LoggerFacade  {
    get { return _logger; }
  }

  protected override void ConfigureContainer()  {
    Container.RegisterType<IShellView, Shell>();

    base.ConfigureContainer();
  }

  protected override DependencyObject CreateShell()  {
    ShellPresenter presenter = Container.Resolve<ShellPresenter>();
    IShellView view = presenter.View;
    view.ShowView();
    return view as DependencyObject;
  }
}
La prima cosa da notare è che un EntlibLoggerAdapter viene definito e memorizzato nella variabile _logger. Il codice sottopone poi a override la proprietà LoggerFacade per restituire tale logger, che implementa ILoggerFacade. In questo caso si sta utilizzando il logger di Enterprise Library, ma è possibile sostituirlo facilmente se si vuole utilizzare il proprio adattatore.
Successivamente viene sottoposto a override il metodo GetModuleEnumerator e viene restituito uno StaticModuleEnumerator, prepopolato con quattro moduli di implementazione di riferimento. L'implementazione di riferimento utilizza il caricamento di moduli statici, ma esistono altri modi per enumerare i moduli, tra cui la ricerca e la configurazione di directory. Per utilizzare un metodo di enumerazione diverso, è sufficiente modificare tale metodo per creare un'istanza di un altro enumeratore.
ConfigureContainer viene quindi sottoposto a override per registrare la shell. A questo punto, se necessario, è possibile registrare servizi aggiuntivi a livello di programmazione. Infine, CreateShell viene sottoposto a override con una logica specifica per la creazione della shell. In questo caso, il codice implementa il modello Model View Presenter, per cui la shell ha un presenter associato.
Il programma di avvio automatico nella Figura 4 mostra un modello comune di creazione di un'applicazione CAL partendo da zero, cioè creando un programma di avvio automatico specifico per l'applicazione. Uno dei principali vantaggi di questo approccio è il fatto che un programma di avvio automatico specifico per l'applicazione migliora la testabilità della propria applicazione. Il programma di avvio automatico non presenta dipendenze da WPF, tranne DependencyObject. È possibile, ad esempio, creare un programma di avvio automatico di prova basato sul programma di avvio automatico specifico dell'applicazione che sottoponga a override il metodo CreateContainer per restituire un contenitore AutoMocking e che consenta quindi di simulare tutti i servizi.
Inoltre, poiché il programma di avvio automatico fornisce un unico punto di ingresso per l'inizializzazione della composizione e poiché la CAL non si basa sull'ereditarietà proveniente dalle classi framework dell'applicazione, è possibile integrare la CAL nelle applicazioni esistenti con minore difficoltà rispetto ai framework precedenti. Si noti che la CAL stessa non dipende assolutamente dal programma di avvio automatico, quindi è possibile evitare di utilizzarla se il programma di avvio automatico non risponda alle proprie esigenze.

Moduli e servizi
Come affermato in precedenza, in un'applicazione composita creata mediante la CAL, la maggior parte della logica dell'applicazione risiede nei moduli. L'implementazione di riferimento Stock Trader comprende quattro moduli:
  • NewsModule fornisce newsfeed per ciascun fondo selezionato.
  • MarketModule fornisce dati sulle tendenze nonché dati sui mercati in tempo reale per il fondo selezionato.
  • WatchModule fornisce una watchlist in cui viene visualizzato un elenco dei fondi in fase di monitoraggio.
  • PositionModule consente di visualizzare un elenco di fondi in cui si è investito e di effettuare transazioni di acquisto e vendita.
Nella CAL, un modulo è una classe che implementa l'interfaccia IModule. Questa interfaccia dispone di un unico metodo, chiamato Inizialize. Se il programma di avvio automatico è equivalente al metodo Main dell'applicazione, allora il metodo Initialize è il Main per ciascun modulo. Ecco un esempio del metodo Initialize per WatchModule:
public void Initialize() {
  RegisterViewsAndServices();

  IWatchListPresentationModel watchListPresentationModel = 
    _container.Resolve<IWatchListPresentationModel>();
  _regionManager.Regions["WatchRegion"].Add(watchListPresentationModel.View);
  IAddWatchPresenter addWatchPresenter = 
    _container.Resolve<IAddWatchPresenter>();
  _regionManager.Regions["MainToolbarRegion"].Add(addWatchPresenter.View);
}
Prima di entrare nei dettagli sui moduli, è opportuno discutere di due aspetti, i riferimenti a _container e _regionManager. Se questi ultimi non sono definiti nell'interfaccia, da dove provengono allora? È necessario specificare la logica nel codice del modulo per individuare queste dipendenze?
Fortunatamente, la risposta all'ultima domanda è no. In questo caso, disporre di un contenitore IoC rappresenta la soluzione al problema. Quando si carica un modulo, questo viene risolto dal contenitore, che inserisce qualsiasi dipendenza specifica nel costruttore del modulo:
public WatchModule(IUnityContainer container, 
  IRegionManager regionManager) {
  _container = container;
  _regionManager = regionManager;
}
Si può notare che il contenitore stesso viene inserito all'interno del modulo. Questo è possibile perché il programma di avvio automatico registra il contenitore all'interno del proprio metodo ConfigureContainer:
Container.RegisterInstance<IUnityContainer>(Container);
Dando ai moduli accesso diretto al contenitore consente al modulo di registrare e risolvere le dipendenze dal contenitore in modo imperativo.
Quindi non è necessario avere questa registrazione imperativa. È possibile, invece, inserire tutti i servizi in una configurazione globale. Ciò significa che tutti i servizi devono essere registrati nel momento in cui viene creato inizialmente il contenitore. Ma la maggior parte dei moduli dispone di servizi specifici per il modulo. E, mantenendo la registrazione nel modulo, questi servizi specifici per il modulo vengono registrati solo se il modulo è caricato.
Nel caso del modulo illustrato in precedenza, la prima chiamata è per RegisterViewsAndServices. In questo metodo, ciascuna vista specifica per WatchModule viene registrata nel contenitore insieme all'interfaccia:
protected void RegisterViewsAndServices() {
  _container.RegisterType<IWatchListService, WatchListService>(
    new ContainerControlledLifetimeManager());
  _container.RegisterType<IWatchListView, WatchListView>();
  _container.RegisterType<IWatchListPresentationModel, 
    WatchListPresentationModel>();
  _container.RegisterType<IAddWatchView, AddWatchView>();
  _container.RegisterType<IAddWatchPresenter, AddWatchPresenter>();
}
Richiedere che l'interfaccia venga specificata contribuisce alla separazione delle problematiche e consente ad altri moduli nel sistema di interagire con la vista senza richiedere un riferimento diretto. Se si inserisce tutto nel contenitore, è possibile inserire automaticamente ogni dipendenza per i diversi oggetti. WatchListView, ad esempio, non viene mai creata direttamente nel codice, ma viene caricata come dipendenza nel costruttore WatchListPresentationModel:
public WatchListPresentationModel(IWatchListView view...)
Oltre alle viste, WatchModule registra anche WatchListService, che contiene i dati dell'elenco e viene utilizzato per aggiungere nuovi elementi. Le viste specifiche che vengono registrate sono la watchlist e la barra degli strumenti della watchlist. Dopo la registrazione, viene utilizzato RegionManager ed entrambe le viste registrate vengono aggiunte a WatchRegion e a ToolbarRegion.

Regioni e RegionManager
I moduli non sono di per sé interessanti, a meno che non possano eseguire il rendering del contenuto dell'interfaccia utente. Nella sezione precedente, si è appreso come il modulo Watch utilizzi una regione per aggiungere le sue due viste. Utilizzando una regione, il modulo non ha più l'esigenza di avere riferimenti specifici all'interfaccia utente o di conoscere la modalità in cui le viste inserite saranno disposte e visualizzate. Per un esempio in merito, la Figura 5 illustra le regioni in cui si inserisce WatchModule.
Figura 5 Inserimento di moduli nell'applicazione (fare clic sull'immagine per ingrandirla)
La CAL contiene una classe Region, che rappresenta fondamentalmente un handle contenente queste posizioni. La classe Region contiene una proprietà Views, che è una raccolta di viste di sola lettura da visualizzare all'interno della regione. Le viste vengono aggiunte alla regione chiamando il metodo Add della regione. La proprietà Views contiene una raccolta generica di oggetti; non è limitata al solo contenimento di UIElement. Questa raccolta implementa INotifyPropertyCollectionChanged in modo che l'UIElement associato alla regione possa legarsi ad essa e osservarne le modifiche.
Ci si potrebbe chiedere perché la raccolta Views sia scarsamente tipizzata e non sia invece di tipo UIElement. Grazie al supporto completo per i template in WPF, è possibile aggiungere modelli direttamente alla regione. Questo modello può quindi disporre di un DataTemplate associato e appositamente definito per il modello che definirà il rendering del modello. Se l'elemento aggiunto è un UIElement o un controllo utente, WPF ne eseguirà il rendering tale e quale. Ciò significa che se si dispone di una regione, che è una scheda di ordini aperti, è sufficiente aggiungere alla regione OrderModel o OrderPresentationModel e definire un DataTemplate personalizzato per controllare lo schermo, piuttosto che creare un controllo utente OrderView.
È possibile registrare le regioni in due modi. Il primo è definito in XAML mediante l'annotazione di un UIElement con una proprietà RegionName collegata. Ad esempio, il codice XAML per definire MainToolbarRegion è il seguente:
<ItemsControl Grid.Row="1" Grid.Column="1" 
  x:Name="MainToolbar" 
  cal:RegionManager.RegionName="MainToolbarRegion">
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <WrapPanel />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
</ItemsControl>
Una volta che una regione è stata definita tramite XAML, sarà automaticamente registrata in fase di esecuzione con RegionManager, uno dei servizi di composizione registrati dal programma di avvio automatico. RegionManager è essenzialmente un dizionario in cui la chiave è il nome della regione e il valore è un'istanza dell'interfaccia IRegion. La proprietà RegionManager collegata utilizza un RegionAdapter per creare questa istanza.
Si noti, tuttavia, che se l'utilizzo delle proprietà collegate non risponde alle proprie esigenze o se occorre registrare in modo dinamico regioni aggiuntive, è possibile creare manualmente un'istanza della classe Region o di una classe derivata e aggiungerla alla raccolta di regioni di RegionManager.
Si osservi nel frammento di codice XAML che MainToolbarRegion è un ItemsControl. La CAL dispone di tre adattatori di regione, che sono registrati dal programma di avvio automatico: ContentControlRegionAdapter, ItemsControlRegionAdapter e SelectorRegionAdapter. Gli adattatori sono registrati con una classe RegionAdapterMappings. Tutti gli adattatori ereditano da RegionAdapterBase, che implementa l'interfaccia I-RegionAdapter.
Nella Figura 6 viene visualizzata l'implementazione di ItemsControlRegionAdapter. La modalità di implementazione dell'adattatore stesso dipende interamente dal tipo di UIElement a cui viene adattato. Nel caso di ItemsControlRegionAdapter, la maggior parte dell'implementazione avviene nel metodo Adapt. Il metodo Adapt accetta due parametri. Il primo parametro è un'istanza della classe Region stessa, creata da RegionManager. Il secondo parametro è UIElement, che rappresenta tale regione. Il metodo Adapt esegue il plumbing necessario per assicurare che la regione funzioni con l'elemento.
public class ItemsControlRegionAdapter : RegionAdapterBase<ItemsControl> {
  protected override void Adapt(IRegion region, ItemsControl regionTarget) {
    if (regionTarget.ItemsSource != null || 
      (BindingOperations.GetBinding(regionTarget, 
      ItemsControl.ItemsSourceProperty) != null))
      throw new InvalidOperationException(
        Resources.ItemsControlHasItemsSourceException);

    if (regionTarget.Items.Count > 0) {
      foreach (object childItem in regionTarget.Items) {
        region.Add(childItem);
      }
      regionTarget.Items.Clear();
    }
    regionTarget.ItemsSource = region.Views;
  }

  protected override IRegion CreateRegion() {
    return new AllActiveRegion();
  }
}
Nel caso di un controllo ItemsControl, l'adattatore rimuove automaticamente qualsiasi elemento figlio da ItemControl e lo aggiunge alla regione. La raccolta Views della regione viene quindi associata all'ItemsSource del controllo.
Il secondo metodo sottoposto a override è CreateRegion, che restituisce una nuova istanza AllActiveRegion. Le regioni possono contenere viste attive o inattive. Nel caso di ItemsControl, tutti i suoi elementi sono sempre attivi perché non hanno una nozione di selezione. Tuttavia, nel caso di altri tipi di regione, come Selector, viene selezionato un solo elemento alla volta. Una vista può implementare l'interfaccia IActiveAware in modo che riceva una notifica dalla propria regione selezionata. Ogni volta che si seleziona una vista, la proprietà IsSelected è impostata su true.
Durante tutte le fasi dello sviluppo dell'applicazione composita, potrebbe essere necessario creare regioni aggiuntive e adattatori di regione, ad esempio un adattatore per adattare un controllo di un fornitore terzo. Per registrare il nuovo adattatore di regione, sottoporre a override il metodo ConfigureRegionAdapter-Mappings nel programma di avvio automatico. Dopo aver eseguito la registrazione, aggiungere codice nel modo seguente:
protected override RegionAdapterMappings 
  ConfigureRegionAdapterMappings() {
  RegionAdapterMappings regionAdapterMappings = 
    base.ConfigureRegionAdapterMappings();

  regionAdapterMappings.RegisterMapping(typeof(Selector), 
    new MyWizBangRegionAdapter());

  return regionAdapterMappings;
}
Dopo aver definito la regione, è possibile accedervi da qualsiasi classe all'interno dell'applicazione utilizzando il servizio RegionManager. Un modo semplice per effettuare questa operazione in un'applicazione CAL è disporre di un contenitore di inserimento dipendenze che inserisca RegionManager nel costruttore della classe che lo richiede. Per aggiungere una vista o un modello a una regione, è sufficiente chiamare il metodo Add della regione. Dopo aver aggiunto la vista, è possibile specificare un nome facoltativo:
_regionManager.Regions["MainRegion"].Add(
  somePresentationModel, "SomeView");
È possibile utilizzare successivamente questo nome per recuperare la vista dalla regione mediante il metodo GetView della regione.

Regioni definite a livello locale
L'impostazione predefinita prevede la presenza di una sola istanza RegionManager nell'applicazione, il che rende tutte le regioni definite a livello globale. Questa soluzione funziona bene in una vasta gamma di scenari, ma vi sono situazioni in cui si potrebbe voler definire una regione che esista solo in un ambito specifico. Un esempio di questa situazione è quando l'applicazione dispone di una vista per i dettagli dei dipendenti in cui più istanze della vista possono essere mostrate contemporaneamente. Se queste viste sono complesse, si comportano come mini shell o CompositeViews. In questi casi, è auspicabile che ciascuna vista disponga delle proprie regioni, come nel caso della shell. La CAL consente di definire un RegionManager locale per una vista in modo che tutte le regioni definite al suo interno o nelle viste figlio siano automaticamente registrate in questa regione locale.
La guida rapida UI Composition inclusa in Composite Application Guidance for WPF illustra questo scenario (vedere Figura 7). Nella guida rapida è presente un elenco di dipendenti. Quando si fa clic su ciascun dipendente vengono visualizzati i dettagli associati. Per ciascun dipendente selezionato, viene creata una nuova EmployeeDetailsView che viene aggiunta a DetailsRegion (vedere Figura 8). Questa vista contiene una TabRegion locale, in cui EmployeesController inserisce una ProjectListView all'interno del proprio metodo OnEmployeeSelected.
Figura 7 Composizione dell'interfaccia utente mediante RegionManager (fare clic sull'immagine per ingrandirla)
public virtual void 
  OnEmployeeSelected(BusinessEntities.Employee employee) {
  IRegion detailsRegion = 
    regionManager.Regions[RegionNames.DetailsRegion];
  object existingView = detailsRegion.GetView(
    employee.EmployeeId.ToString(CultureInfo.InvariantCulture));

  if (existingView == null) {
    IProjectsListPresenter projectsListPresenter = 
    this.container.Resolve<IProjectsListPresenter>();
    projectsListPresenter.SetProjects(employee.EmployeeId);

    IEmployeesDetailsPresenter detailsPresenter = 
      this.container.Resolve<IEmployeesDetailsPresenter>();
    detailsPresenter.SetSelectedEmployee(employee);

    IRegionManager detailsRegionManager = 
      detailsRegion.Add(detailsPresenter.View,
      employee.EmployeeId.ToString(CultureInfo.InvariantCulture), true);

    IRegion region = detailsRegionManager.Regions[RegionNames.TabRegion];
    region.Add(projectsListPresenter.View, "CurrentProjectsView");
    detailsRegion.Activate(detailsPresenter.View);
  }
  else {
    detailsRegion.Activate(existingView);
  }
}
La regione è sottoposta a rendering come TabControl e comprende sia contenuti statici che dinamici. Le schede General e Location sono definite staticamente all'interno del codice XAML. Nella scheda Current Projects, invece, vengono inserite le viste appropriate.
Il codice mostra che è stata restituita una nuova istanza RegionManager dal metodo detailsRegion.Add. Si osservi inoltre fatto che si sta utilizzando l'overload di Add che fornisce un nome alla vista e imposta su true il parametro createRegionManagerScope. In questo modo si crea un'istanza RegionManager locale che sarà utilizzata per tutte le regioni definite nei figli. La TabRegion stessa viene definita nel codice XAML di EmployeeDetailsView:
<TabControl AutomationProperties.AutomationId="DetailsTabControl" 
  cal:RegionManager.RegionName="{x:Static local:RegionNames.TabRegion}" .../>
L'utilizzo di regioni locali fornisce un ulteriore vantaggio anche se non si utilizzano regioni dell'istanza. È possibile utilizzare queste regioni per definire un limite di livello superiore in modo che il modulo non esponga automaticamente le proprie regioni a tutti. Per farlo, è sufficiente aggiungere la vista di livello superiore per quel modulo in una regione e specificare che dispone del proprio ambito specifico. Una volta aggiunta questa vista, sono state protette efficacemente le regioni del modulo da occhi indiscreti. Non è impossibile accedervi, ma è molto più difficile.

Senza viste non ci sarebbe bisogno di un'applicazione composita. Le viste sono l'elemento più importante che viene creato all'interno delle applicazioni composite, poiché rappresentano per gli utenti il gateway per accedere a tutte le funzionalità dell'applicazione.
Le viste sono in genere le schermate dell'applicazione. Possono contenere altre viste, diventando così viste composite. Le viste sono utilizzate anche per i menu e le barre degli strumenti. In Stock Trader, ad esempio, OrdersToolbar è una vista che contiene i pulsanti Submit, Cancel, Submit All e Cancel All.
WPF supporta una nozione molto più estesa di vista rispetto a Windows Forms. In Windows Forms ci si limitava fondamentalmente a utilizzare i controlli come rappresentazioni visive. In WPF, questo modello è ancora supportato ed è possibile creare controlli utente personalizzati per la rappresentazione di diverse schermate. Se si esamina globalmente l'applicazione Stock Trader, si percepisce che si tratta del meccanismo principale utilizzato per la definizione delle viste.
Un altro approccio è quello di utilizzare modelli. WPF consentirà di associare qualsiasi modello all'interfaccia utente e di utilizzare un DataTemplate per eseguirne il rendering. I template sono sottoposti ricorsivamente a rendering: ciò significa che se un template esegue il rendering di un elemento associato a una proprietà del modello, questa proprietà sarà sottoposta a rendering mediante un template, se disponibile.
Per una dimostrazione di questo comportamento, si osservi il seguente esempio di codice: Questo esempio implementa la stessa interfaccia utente della guida rapida UI Composition, ma utilizza unicamente modelli e DataTemplate. Nell'intero progetto non esiste alcun controllo utente. La Figura 9 illustra come viene gestita la vista EmployeeDetailsView. La vista è ora costituita da una serie di tre DataTemplate che sono stati definiti in un ResourceDictionary. Tutto ha inizio con EmployeeDetailsPresentationModel. Il template di questo modello dichiara che il rendering deve essere eseguito come TabControl. Come parte del template, stabilisce un'associazione tra ItemsSource di TabControl e la proprietà della raccolta EmployeeDetails di EmployeeDetailsPresentationModel. Questa raccolta è popolata da due tipi di informazione al momento della creazione dei dettagli relativi al dipendente:
public EmployeesDetailsPresentationModel() {
  EmployeeDetails = new ObservableCollection<object>();
  EmployeeDetails.Insert(0, new HeaderedEmployeeData());
  EmployeeDetails.Insert(1, new EmployeeAddressMapUrl());
  ...
}
<ResourceDictionary 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:EmployeesDetailsView=
    "clr-namespace:ViewModelComposition.Modules.Employees.Views.EmployeesDetailsView">

  <DataTemplate 
    DataType="{x:Type EmployeesDetailsView:HeaderedEmployeeData}">
    <Grid x:Name="GeneralGrid">
      <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition Width="5"></ColumnDefinition>
        <ColumnDefinition Width="*"></ColumnDefinition>
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
      </Grid.RowDefinitions>
      <TextBlock Text="First Name:" Grid.Column="0" Grid.Row="0">
      </TextBlock>
      <TextBlock Text="Last Name:" Grid.Column="2" Grid.Row="0">
      </TextBlock>
      <TextBlock Text="Phone:" Grid.Column="0" Grid.Row="2"></TextBlock>
      <TextBlock Text="Email:" Grid.Column="2" Grid.Row="2"></TextBlock>
      <TextBox x:Name="FirstNameTextBox" 
        Text="{Binding Path=Employee.FirstName}" 
        Grid.Column="0" Grid.Row="1"></TextBox>
      <TextBox x:Name="LastNameTextBox" 
        Text="{Binding Path=Employee.LastName}" 
        Grid.Column="2" Grid.Row="1"></TextBox>
      <TextBox x:Name="PhoneTextBox" Text="{Binding Path=Employee.Phone}" 
        Grid.Column="0" Grid.Row="3"></TextBox>
      <TextBox x:Name="EmailTextBox" Text="{Binding Path=Employee.Email}" 
        Grid.Column="2" Grid.Row="3"></TextBox>
    </Grid>
  </DataTemplate>

  <DataTemplate 
    DataType="{x:Type EmployeesDetailsView:EmployeeAddressMapUrl}">
    <Frame Source="{Binding AddressMapUrl}" Height="300"></Frame>
  </DataTemplate>

  <DataTemplate DataType="{x:Type
    EmployeesDetailsView:EmployeesDetailsPresentationModel}">
    <TabControl x:Name="DetailsTabControl" 
      ItemsSource="{Binding EmployeeDetails}" >
      <TabControl.ItemContainerStyle>
        <Style TargetType="{x:Type TabItem}" 
          BasedOn="{StaticResource RoundedTabItem}">
          <Setter Property="Header" Value="{Binding HeaderInfo}" />
        </Style>
      </TabControl.ItemContainerStyle>
    </TabControl>
  </DataTemplate>
</ResourceDictionary>
Verrà eseguito il rendering di una scheda separata per ciascun elemento della raccolta. Non appena viene eseguito il rendering del primo elemento, WPF utilizza il DataTemplate specificato per HeaderedEmployeeData. Il modello HeaderedEmployeeData contiene il nome e le informazioni di contatto del dipendente. Il template di questo modello esegue il rendering del modello come se si trattasse di una serie di etichette per la visualizzazione delle informazioni. Verrà poi eseguito il rendering del secondo elemento mediante il template specificato per EmployeeAddressMapUrl che, in questo caso, eseguirà il rendering di un frame contenente una pagina Web con una mappa del luogo di residenza del dipendente.
Si tratta di un importante cambiamento paradigmatico, poiché significa che la vista precedente esiste realmente solo in fase di esecuzione mediante la combinazione del modello e del template associato. È inoltre possibile implementare un ibrido di entrambi gli approcci (come dimostrato nell'applicazione Stock Trader), in cui si dispone di controlli utente che presentano controlli al loro interno, che vengono poi associati a modelli di cui viene eseguito il rendering tramite i template.

Presentazione separata
Precedentemente in questo articolo è stato detto che uno dei vantaggi della creazione di un'applicazione composita risiede nella maggiore gestibilità e testabilità del codice. Per ottenere questo risultato sono presenti diversi modelli di presentazione già delineati, applicabili all'interno delle viste. Nel documento Composite Application Guidance for WPF compaiono due modelli ricorrenti utilizzati nell'interfaccia utente: Presentation Model e Supervising Controller.
Presentation Model presuppone un modello che contenga sia il comportamento sia i dati per l'interfaccia utente. La vista proietta quindi sullo schermo lo stato di Presentation Model.
Il modello interagisce dietro le quinte con modelli aziendali e di dominio. Il modello comprende inoltre informazioni di stato aggiuntive che indicano, ad esempio, l'elemento selezionato o riferiscono se un elemento è stato selezionato oppure no. Successivamente la vista stabilisce direttamente un'associazione con Presentation Model e ne esegue il rendering (vedere Figura 10). Il supporto avanzato in WPF per l'associazione dati, i template e i comandi rende Presentation Model un'opzione interessante per gli sviluppatori.
Figura 10 Modello Presentation Model (fare clic sull'immagine per ingrandirla)
L'applicazione Stock Trader utilizza in modo avveduto Presentation Model, come ad esempio nel riepilogo dei ruoli:
public class PositionSummaryPresentationModel :
  IPositionSummaryPresentationModel, INotifyPropertyChanged {
  public PositionSummaryPresentationModel(
    IPositionSummaryView view,...) {
    ...
  }

  public IPositionSummaryView View { get; set; }
  public ObservableCollection<PositionSummaryItem> 
    PositionSummaryItems { 
    get; set; }
}
Si osservi che PositionSummaryPresentationModel implementa INotifyPropertyChanged per segnalare alla vista le eventuali modifiche. La vista stessa viene inserita nel costruttore mediante l'interfaccia IPositionSummaryView nel momento in cui PositionSummaryPresentationModel viene risolto dal contenitore. Questa interfaccia consente di simulare la vista durante gli unit test. Presentation Model espone una raccolta osservabile di PositionSummaryItems. Questi elementi sono associati a PostionSummaryView e ne viene eseguito il rendering.
All'interno del modello Supervising Controller sono presenti il modello, la vista e il presenter, come illustrato nella Figura 11. Il modello rappresenta i dati; si tratta molto spesso di un oggetto business. La vista è un UIElement a cui si associa direttamente il modello. Infine, il presenter è una classe contenente la logica dell'interfaccia utente. In questo modello, la vista contiene una logica molto bassa che consente semplicemente di delegare al presenter e rispondere alle richiamate provenienti da quest'ultimo per eseguire semplici azioni (ad esempio mostrare o nascondere un controllo).
Figura 11 Modello Supervising Controller (fare clic sull'immagine per ingrandirla)
Il modello Supervising Controller è inoltre utilizzato in alcuni casi nell'applicazione Stock Trader al posto di Presentation Model. Un esempio di questi usi è costituito dalla linea di tendenza (vedere Figura 12). Analogamente a PositionSummaryPresentationModel, TrendLinePresenter viene inserito con la vista TrendLineView mediante l'interfaccia ITrendLineView. Il presenter espone un metodo OnTickerSymbolSelected, richiamato dalla vista mediante la propria logica di delega. Si noti che, in questo metodo, il presenter richiama successivamente la vista richiamando i propri metodi UpdateLineChart e SetChartTitle.
public class TrendLinePresenter : ITrendLinePresenter {
  IMarketHistoryService _marketHistoryService;

  public TrendLinePresenter(ITrendLineView view, 
    IMarketHistoryService marketHistoryService) {
    this.View = view;
    this._marketHistoryService = marketHistoryService;
  }

  public ITrendLineView View { get; set; }

  public void OnTickerSymbolSelected(string tickerSymbol) {
    MarketHistoryCollection historyCollection = 
      _marketHistoryService.GetPriceHistory(tickerSymbol);
    View.UpdateLineChart(historyCollection);
    View.SetChartTitle(tickerSymbol);
  }
}

Una delle sfide che si manifestano quando si procede all'implementazione di una presentazione separata è la comunicazione tra la vista e il modello di presentazione o il presenter. Esistono diversi approcci per far fronte a questo aspetto. Un approccio spesso utilizzato consiste nel disporre di gestori di eventi nella vista, che effettuano direttamente chiamate o generano eventi per il modello di presentazione o il presenter. Gli stessi UIElement che inizializzano chiamate al presenter spesso devono essere attivati o disattivati nell'interfaccia utente, in base ad autorizzazioni o modifiche di stato. Questo richiede che la vista disponga di metodi che possano essere utilizzati per richiamarla al fine di disattivare tali elementi.
Un altro approccio è quello di utilizzare i comandi WPF. I comandi garantiscono un modo sicuro per risolvere queste situazioni senza bisogno di creare un traffico intenso di logica di delega. Gli elementi in WPF possono essere associati a comandi per gestire la logica di esecuzione e attivare o disattivare gli elementi. Quando un UIElement è associato a un comando, sarà automaticamente disattivato in tutti i casi in cui la proprietà CanExecute è false. I comandi possono essere associati in modo dichiarativo nel codice XAML.
WPF dispone di RoutedUICommands pronti all'uso. L'utilizzo di questi comandi richiede un gestore per i metodi Execute e CanExecute all'interno del codebehind della vista; ciò significa che per le comunicazioni avanti e indietro è ancora necessaria la modifica del codice. I RoutedUICommands presentano anche altri vincoli, ad esempio richiedono che il ricevente sia presente nell'albero logico in WPF, un vincolo problematico per la creazione di applicazioni composite.
Fortunatamente, i RoutedUICommands sono solo un'implementazione dei comandi. WPF fornisce l'interfaccia ICommand e si assocerà a ogni comando che la implementa. Ciò significa che è possibile creare comandi personalizzati per rispondere a tutte le esigenze, senza bisogno di intervenire sul codebehind. Lo svantaggio è rappresentato dal fatto che è necessario implementare ovunque comandi personalizzati, tra cui SaveCommand, SubmitCommand e CancelCommand.
La CAL contiene nuovi comandi, come DelegateCommand<T>, che consente di specificare i due delegati per i metodi Execute e CanExecute nel costruttore. L'utilizzo di questo comando consente di collegare viste senza dover delegare ai metodi definiti nella vista stessa e creare comandi personalizzati per ciascuna azione.
Nell'applicazione Stock Trader, DelegateCommand viene utilizzato in diversi ambiti, tra cui la watchlist. WatchListService utilizza questo comando per aggiungere elementi alla watchlist:
public WatchListService(IMarketFeedService marketFeedService) {
  this.marketFeedService = marketFeedService;
  WatchItems = new ObservableCollection<string>();
  AddWatchCommand = new DelegateCommand<string>(AddWatch);
}

Oltre ai comandi di routing tra la vista e un presenter o un modello di presentazione, esistono altri tipi di comunicazione, come la pubblicazione di eventi, che devono essere gestiti in un'applicazione composita. In questi casi, l'autore è stato completamente separato dal sottoscrittore. Ad esempio, esiste la possibilità per un modulo di esporre un endpoint del servizio Web che riceve notifiche dal server. Quando la notifica è stata ricevuta, è necessario generare un evento a cui si possano iscrivere i componenti all'interno dello stesso modulo o in altri moduli.
Per supportare questa funzionalità, la CAL dispone di un servizio EventAggregator, registrato all'interno del contenitore. Utilizzando questo servizio, che è un'implementazione del modello EventAggregator, autori e sottoscrittori possono comunicare liberamente. Il servizio EventAggregator contiene un archivio di eventi che sono istanze della classe astratta EventBase. Il servizio dispone di un metodo GetEvent<TEventType> per il recupero di istanze di eventi.
La CAL comprende la classe CompositeWPFEvent<TPayload> che eredita EventBase e fornisce supporto specifico per WPF. Questa classe utilizza delegati piuttosto che eventi .NET completi per la pubblicazione. Dietro le quinte utilizza una classe DelegateReference che, per impostazione predefinita, funziona come un delegato debole (per ulteriori informazioni sui delegati deboli, consultare msdn.microsoft.com/library/ms404247). Ciò consente ai sottoscrittori di essere raccolti nel Gargabe Collector anche se la sottoscrizione non viene esplicitamente annullata.
La classe CompositeWPFEvent contiene i metodi Publish, Subscribe e Unsubscribe. Ciascun metodo utilizza informazioni di tipo generico dell'evento per garantire che l'autore fornisca i parametri corretti (TPayload) e che la proprietà Subscriber li riceva (Action<TPayload>). Il metodo Subscribe consente di specificare una ThreadOption, che può essere impostata in PublisherThread, UIThread o BackgroundThread. Questa opzione determina su quale thread sarà richiamato il delegato che effettua la sottoscrizione. Inoltre, il metodo Subscribe è in overload per consentire di specificare un filtro Predicato<T>, in modo che il sottoscrittore riceva notifiche sull'evento solo nel caso in cui i criteri del filtro siano soddisfatti.
Nell'applicazione Stock Trader, EventAggregator è utilizzato per la trasmissione ogni volta che un simbolo viene selezionato nella schermata delle posizioni. Il modulo News effettua la sottoscrizione a questo evento e consente di visualizzare le notizie per il fondo. Ecco come viene implementata la funzionalità:
public class TickerSymbolSelectedEvent : 
  CompositeWpfEvent<string> {
}
In primo luogo, l'evento viene definito nell'assembly StockTraderRI.Infrastructure. Si tratta di un assembly condiviso, a cui fanno riferimento tutti i moduli:
public void Run() {
  this.regionManager.Regions["NewsRegion"].Add(
    articlePresentationModel.View);
  eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Subscribe( 
    ShowNews, ThreadOption.UIThread);
}

public void ShowNews(string companySymbol) {
  articlePresentationModel.SetTickerSymbol(companySymbol);
}
Il NewsController del modulo News effettua la sottoscrizione a questo evento nel proprio metodo Run:
private void View_TickerSymbolSelected(object sender, 
  DataEventArgs<string> e) {
  _trendLinePresenter.OnTickerSymbolSelected(e.Value);

  EventAggregator.GetEvent<TickerSymbolSelectedEvent>().Publish(
    e.Value);
}
Il modello PositionSummaryPresentation genera successivamente l'evento ogni volta che viene selezionato un simbolo.

Conclusioni
Scaricare le linee guida da microsoft.com/compositewpf. Per eseguire il codice, è sufficiente aver installato .NET Framework 3.5.
Le linee guida contengono strumenti utili per iniziare. Le Guide rapide forniscono esempi di semplice comprensione incentrati su diversi aspetti della creazione di applicazioni composite. L'implementazione di riferimento fornisce esempi che trattano tutti i diversi aspetti. Infine, la documentazione offre informazioni complementari, una gamma completa di procedure per compiti specifici e laboratori pratici.
Dopo aver consultato le linee guida, è possibile inviare la propria opinione ai forum CodePlex oppure inviare un'e-mail a cafbk@microsoft.com.
Glenn Block è PM per il nuovo MEF (Managed Extensibility Framework) di .NET Framework 4,0. Prima di cominciare a lavorare su MEF, Glenn era Product Planner nel team responsabile dei modelli e delle procedure di Prism ed era anche consulente per altri client. Glenn è un grande appassionato di informatica e trascorre buona parte del suo tempo a trasmettere questa passione agli altri partecipando a conferenze e gruppi, tra cui ALT.NET.

Page view tracker