2. Who I am...
Mauro Servienti
blog: http://blogs.ugidotnet.org/topics
email: mauro@topics.it
web: https://mvp.support.microsoft.com/profile/mauro
community: www.xedotnet.org, www.ugidotnet.org
14/03/2008 www.xedotnet.org 2
3. Upcoming Events
Community Meeting
In collaborazione con:
14/03/2008 www.xedotnet.org 3
4. Perchè abbiamo bisogno di realizzare applicazioni estensibili.
Estensibilità... who cares?
5. Perchè?
» Supporto per l’integrazione e l’interoperabilità;
» Consente di modificare a caldo il
comportamento;
» È un’ottimo “strumento” commerciale;
14/03/2008 www.xedotnet.org 5
6. Scrivere in ottica estensibilità
» Pensare agli entry point;
» Prevedere quali dati potranno essere necessari;
» Incapsulare i dati in classi adatte al loro
trasporto:
• EventArgs;
• CancelEventArgs;
• CustomEventArgs : CancelEventArgs/EventArgs
» Le “pipeline”, un esempio da seguire:
• HttpModule + HttpHanlder;
14/03/2008 www.xedotnet.org 6
8. il problema: le dipendenze statiche
Componente Componente
ComA ComB
14/03/2008 www.xedotnet.org 8
9. Il cammino verso la soluzione...
Componente Interfaccia
ComA IComB
Componente
ComB
14/03/2008 www.xedotnet.org 9
10. Inversion of Control
Interfaccia
Componente
ServiceProvider IComB
ComA (IoC Container)
Componente IoC
ComB Config
14/03/2008 www.xedotnet.org 10
11. Inversion of Control
» Elevato supporto per la manutenzione
» Sostituzione dei componenti a caldo
» Supporto esteso per l’estensibilità:
• Intercepting;
• Dependency Injection
• Chain of Responsability
» Ma... IoC si basa sul contratto;
» Nonostante tutto adoro IoC!
14/03/2008 www.xedotnet.org 11
12. Non avrai altro contratto all’infuori di me.
Un contratto è per sempre
13. Problema: le dipendenze
» Entrambe le parti dipendono in maniera statica
dal contratto;
Host Contract AddIn
Dipendenza Statica
14/03/2008 www.xedotnet.org 13
14. Problema: il versioning del contratto
» Un contratto in quanto tale non dovrebbe
cambiare nel tempo;
» Sappiamo che nella vita reale questo non è
sempre vero:
• Analisi iniziale sbagliata;
• Condizioni che cambiano;
• Introduzione di nuove funzionalità che non potevano
essere previste;
• Nuova versione dell’applicazione che vuole
soddisfare nuovi requisiti;
• Etc...
14/03/2008 www.xedotnet.org 14
15. Adesso che siamo inseparabili possiamo vivere felici?
Finchè morte non ci separi
16. Il versioning non è l’unico dei problemi
» Il processo di Discovery;
» L’attivazione degli AddIn;
• Le dipendenze, da library di terze parti, sia dell’Host
che dell’AddIn: the AssemblyResolve problem;
» L’isolamento del codice altrui: Sandboxing;
• La gestione della security;
• Gestione degli errori nell’AddIn:
• Il fx 2.0 ha cambiato il comportamento in caso eccezione
non gestita;
» La gestione del ciclo di vita: Unloading;
14/03/2008 www.xedotnet.org 16
17. Ma non solo...
» Compatibilità all’indietro:
• Nuovi Host che hanno bisogno di caricare vecchi AddIn;
» Compatibilità in avanti:
• Vecchi Host che hanno bisogno di funzionalità fornite da
nuovi AddIn;
» La necessità di introdurre nuovi livelli di isolamento:
• In Process;
• Different AppDomain;
• Out of Process;
14/03/2008 www.xedotnet.org 17
24. La vista sul contratto
» Host View:
public interface IMyContractView
{
void Execute();
}
» AddIn View:
[AddInBase]
public interface IMyContractView
{
void Execute();
}
» Le View possono essere “SharedViews”;
14/03/2008 www.xedotnet.org 24
25. Gli adattatori: AddIn Side
[AddInAdapter]
public class MyAddInSideAdapter: ContractBase, IMyContract
{
IMyContractView _contract;
public MyAddInSideAdapter( IMyContractView contract )
{
_contract = contract;
}
public void Execute()
{
_contract.Execute();
}
}
14/03/2008 www.xedotnet.org 25
26. Gli adattatori: Host Side
[HostAdapter]
public class MyHostSideAdapter : IMyContractView
{
IMyContract _contract;
ContractHandle _handle;
public MyHostSideAdapter( IMyContract contract )
{
_contract = contract;
_handle = new ContractHandle(contract);
}
public void Execute()
{
_contract.Execute();
}
}
14/03/2008 www.xedotnet.org 26
27. “ContractHandle”
» Dato che l’architettura permette agli AddIn di
comunicare attraverso i confini di un AppDomain (o
addirittura di un processo) non possiamo fare
affidamento sul GC per la gestione della memoria.
» Questa classe ci offre un sistema di “reference
counting” COM Like:
• Quando viene costruito un ContractHandle nel
costruttore viene chiamato AcquireLifetimeToken().
• Quando il ContractHandle viene “disposto” viene
chiamato RevokeLifetimeToken().
14/03/2008 www.xedotnet.org 27
31. P.S. Diario di Bordo
» “Folder Naming”: quello è e quello deve essere,
siamo in V1...;
» L’AddIn può intercettare il suo shutdown:
• Evento custom esposto dall’host;
• Distruttore: C# Finalizer
• AppDomain.UnloadDomain
• Attenzione alla CAS:
PermissionSet permSet = new PermissionSet(
PermissionState.None );
permSet.AddPermission(
new SecurityPermission(
SecurityPermissionFlag.ControlAppDomain |
SecurityPermissionFlag.Execution ) );
14/03/2008 www.xedotnet.org 31
32. P.S. Extreme Sandboxing
» È possibile attivare gli AddIn in un processo
separato:
• Fornisce massimo supporto per il sandboxing
• Isola il processo host dalle eventuali exception non
gestite dall’AddIn:
• Il CLR del fx 2.0 introduce un radicale cambiamento;
• È possibile, ma non consigliabile, cambiare questo
comportamento (via app.config):
• configurationruntimelegacyUnhandledExceptionPolicy
enabled="1“
» Se usate un processo separato eseguite l’attach
anche di quello per poter debuggare;
14/03/2008 www.xedotnet.org 32
34. Recap: a che punto siamo...
» Ogni AddIn vive, su disco, nella sua cartella:
• Con le sue dipendenze;
• Senza generare conflitti di versione con gli altri AddIn;
• Con Il proprio faile di configurazione.
» L’host può intercettare gli errori in un AddIn,
scaricarlo ed eventualmente ricaricarlo senza che
questo impatti sull’host e sugli altri AddIn;
• A patto che non giri in un processo separato;
» Gli AddIn possono essere isolati (Sandboxing)
fornendo un particolare contesto di security;
» Gli AddIn possono essere scaricati liberando risorse;
35. Tutto bello ma... quanto codice
» Abbiamo scritto troppo plumbing code per
ottenere il risultato desiderato:
• Pipeline Builder Tool:
• Genera tutto il plumbing code in maniera automatica;
• Pipeline Hint:
• Ci permette di controllare il Pipeline Builder Tool:
• SharedView;
• Commenti;
• Naming degli Assembly generati;
• Fornisce supporto per esporre eventi tra Host e AddIn e
viceversa;
14/03/2008 www.xedotnet.org 35
36. Gestione degli eventi e comunicazione duplex
Demo: Pipeline Builder & Events Handling
38. Il nostro rapporto non è più lo stesso... “cara ti amo” <cit.>
Demo: Versioning
39. Recap: data astrale....
» Cuore della gestione del versioning sono gli
adattatori;
» Tutto può essere soggetto a versioning:
• Contratto degli AddIn;
• Contratto dell’Host;
• Contratto dei “dati”;
» Il Pipeline Builder Tool genera adattatori per
tutto ciò che implementa, nell’assembly del
contratto l’interfaccia, “IContract”;
40. Alcune note
» Passaggio di un tipo complesso attraverso i
confini:
• Abbiamo a che fare con remoting:
• Il tipo deve essere serializzabile?
• Chi è che affettivamente passa i confini?
• Definire sempre un’interfaccia;
• Se dobbiamo passare una collection, nel contratto,
usiamo IListContract<T>:
• La classe statica CollectionAdapters vi offre facilities per la
conversione da e verso IList<T>;
Demo: Creazione "amanuense" Crea la solution vuota Creazione del progetto dei contratti aggiunta del contratto aggiunta delle reference a System.AddIn fagli notare che sono compilati per la 2.0 inheritance da IContract marcatura con l'attributo [System.AddIn.Pipeline.AddInContract()] Aggiunta di un metodo impostazione dell'output della build (..\\output\\contracts\\) Aggiungiamo un progetto per le "Shared" view impostiamo l'output (..\\output\\AddInViews\\) aggiungiamo una interfaccia IMySampleContractView aggiungiamo le reference a System.AddIn marchiamo l'interfaccia con [System.AddIn.Pipeline.AddInBaseAttribute()] Creiamo l'adapter lato addin impostiamo l'output su (..\\output\\AddInSideAdapters\\) Aggiungiamo le reference a System.AddIn Aggiungiamo le reference al contratto e alla view Aggiungiamo una classe per l'adapter: IMySampleContractView_ViewToContract_AddInAdapter marchiamo la classe con l'attributo [System.AddIn.Pipeline.AddInAdapterAttribute()] deriviamo da: : System.AddIn.Pipeline.ContractBase implementiamo l'interfaccia del nostro contratto originale: AddIn.HandMade.Contract.IMySampleContract Aggiungiamo un costruttore che prenda in pasto la "view" (Shared in questo caso) e la salva in un field l'implementazione dell'interfaccia non farà altro che runculare la chiamata sulla view privata Creiamo il progetto per l'addin aggiungiamo la classe per l'addin aggiungiamo le reference a System.AddIn aggiungiamo la reference alle View (Shared) !!!!impostiamo copy local a false marchiamo l'addin con: [System.AddIn.AddIn( "NomeAddIn" )] impostiamo l'output su: ..\\output\\addins\\NomeAddIn\\ implementiamo l'interfaccia della Shared View Creiamo l'adattatore lato host impostiamo l'output su: (..\\output\\HostSideAdapters\\) aggiungiamo le reference a System.AddIn Aggiungiamo le reference al contratto e alla view creiamo la classe per adattare dal contratto alla view: IMySampleContract_ContractToView_HostAdapter marchiamo con [System.AddIn.Pipeline.HostAdapterAttribute()] aggiungiamo un costruttore che prende una reference al contratto e lo adatta alla view implementiamo l'interfaccia della View creiamo l'handle System.AddIn.Pipeline.ContractHandle passandogli una reference al contratto (che è poi il transparent proxy di remoting creiamo il progetto Console per l'host aggiungiamo le reference a System.AddIn aggiungiamo la reference alla sharedview Aggiungiamo un po' di codice: - list di backend IList<IMySampleContractView> addins = new List<IMySampleContractView>(); - ricerca e attivazione String[] result = AddInStore.Update( PipelineStoreLocation.ApplicationBase ); IList<AddInToken> tokens = AddInStore.FindAddIns( typeof( IMySampleContractView ), PipelineStoreLocation.ApplicationBase ); foreach( AddInToken token in tokens ) { addins.Add( token.Activate<IMySampleContractView>( AddInSecurityLevel.Internet ) ); } - invocazione foreach( IMySampleContractView addin in addins ) { addin.Execute(); } impostazione dell'output: (..\\output\\) impostazione dello startup project breakpoint sull'host e sull'addint F5
Demo: Creazione "amanuense" Crea la solution vuota Creazione del progetto dei contratti aggiunta del contratto aggiunta delle reference a System.AddIn fagli notare che sono compilati per la 2.0 inheritance da IContract marcatura con l'attributo [System.AddIn.Pipeline.AddInContract()] Aggiunta di un metodo impostazione dell'output della build (..\\output\\contracts\\) Aggiungiamo un progetto per le "Shared" view impostiamo l'output (..\\output\\AddInViews\\) aggiungiamo una interfaccia IMySampleContractView aggiungiamo le reference a System.AddIn marchiamo l'interfaccia con [System.AddIn.Pipeline.AddInBaseAttribute()] Creiamo l'adapter lato addin impostiamo l'output su (..\\output\\AddInSideAdapters\\) Aggiungiamo le reference a System.AddIn Aggiungiamo le reference al contratto e alla view Aggiungiamo una classe per l'adapter: IMySampleContractView_ViewToContract_AddInAdapter marchiamo la classe con l'attributo [System.AddIn.Pipeline.AddInAdapterAttribute()] deriviamo da: : System.AddIn.Pipeline.ContractBase implementiamo l'interfaccia del nostro contratto originale: AddIn.HandMade.Contract.IMySampleContract Aggiungiamo un costruttore che prenda in pasto la "view" (Shared in questo caso) e la salva in un field l'implementazione dell'interfaccia non farà altro che runculare la chiamata sulla view privata Creiamo il progetto per l'addin aggiungiamo la classe per l'addin aggiungiamo le reference a System.AddIn aggiungiamo la reference alle View (Shared) !!!!impostiamo copy local a false marchiamo l'addin con: [System.AddIn.AddIn( "NomeAddIn" )] impostiamo l'output su: ..\\output\\addins\\NomeAddIn\\ implementiamo l'interfaccia della Shared View Creiamo l'adattatore lato host impostiamo l'output su: (..\\output\\HostSideAdapters\\) aggiungiamo le reference a System.AddIn Aggiungiamo le reference al contratto e alla view creiamo la classe per adattare dal contratto alla view: IMySampleContract_ContractToView_HostAdapter marchiamo con [System.AddIn.Pipeline.HostAdapterAttribute()] aggiungiamo un costruttore che prende una reference al contratto e lo adatta alla view implementiamo l'interfaccia della View creiamo l'handle System.AddIn.Pipeline.ContractHandle passandogli una reference al contratto (che è poi il transparent proxy di remoting creiamo il progetto Console per l'host aggiungiamo le reference a System.AddIn aggiungiamo la reference alla sharedview Aggiungiamo un po' di codice: - list di backend IList<IMySampleContractView> addins = new List<IMySampleContractView>(); - ricerca e attivazione String[] result = AddInStore.Update( PipelineStoreLocation.ApplicationBase ); IList<AddInToken> tokens = AddInStore.FindAddIns( typeof( IMySampleContractView ), PipelineStoreLocation.ApplicationBase ); foreach( AddInToken token in tokens ) { addins.Add( token.Activate<IMySampleContractView>( AddInSecurityLevel.Internet ) ); } - invocazione foreach( IMySampleContractView addin in addins ) { addin.Execute(); } impostazione dell'output: (..\\output\\) impostazione dello startup project breakpoint sull'host e sull'addint F5