MEF e Windsor: l’unione fa la forza :-)
Una cosa che però mi ha sempre lasciato un po’ perplesso è che MEF non è un framework di Inversion of Control, come ad esempio il mio amato Castle Windsor, ma piuttosto è un motore per fare “component composition”, è possibile usarlo come motore per IoC ma vi perdete alcune delle feature più interessanti che tutti i motori per IoC mettono a disposizione. Un’altra importante differenza è che MEF ragiona sulle istanze e non sui tipi, non “registrate” un tipo ma piuttosto descrivete (con dei metadati) come volete che gli oggetti (e non le classi) vengano composte a runtime.
Una coesistenza pacifica e proficua
Q: è possibile trarre il maggior beneficio dai due mondi?
A: ovviamente la risposta è si, altrimenti che scriverei a fare?
Scenario: Diciamo che abbiamo una semplice applicazione che fa largo uso di “UI composition” e che per tutta una serie di buoni motivi ha necessariamente bisogno di molte delle feature esposte da un motore di IoC e non erogate da MEF, dall’altra parte però ci rendiamo conto che è ci sono alcune cose che sono una vera menata colossale da realizzare, non tanto difficili quando noiose:
- il discovery dei moduli installati;
- il bootstrap dei singoli moduli;
- la condivisione tra l’applicazione e i moduli del “container” (che ovviamente deve essere uno ed uno solo);
L’ApplicationBootstrapper è il “main entry point” della nostra applicazione (deriva da un WindsorApplicationBootstrapper che ha l’unico scopo di ridurre un po’ la frizione):public sealed class ApplicationBootstrapper : WindsorApplicationBootstrapper { [ImportMany] public IEnumerable<IWindsorInstaller> Installers { get; set; } [Export] [Export( typeof( IServiceProvider ) )] public IWindsorContainer Container { get; set; } CompositionContainer MefContainer; protected override IWindsorContainer CreateServiceProvider() { this.Container = base.CreateServiceProvider(); var catalog = new DirectoryCatalog( Environment.CurrentDirectory ); this.MefContainer = new CompositionContainer( catalog ); return this.Container; } protected override void InitializeEnvironment( IWindsorContainer container ) { container.Register ( Component.For<CompositionContainer>() .Instance( this.MefContainer ) ); this.MefContainer.ComposeParts( this ); container.Install( this.Installers.ToArray() ); base.InitializeEnvironment( container ); } }
- all’atto della creazione del container fa due cose:
- Imposta una proprietà pubblica con l’istanza del container appena creato, proprietà decorata con un attributo di MEF che identifica che quella proprietà è “esportabile”;
- costruisce un’istanza di un CompositionContainer che è MEF;
- All’atto della configurazione:
- registra in Windsor MEF stesso al fine di renderlo disponibile al mondo;
- chiede a MEF di comporre se stesso: questo scatena il discovery da parte di MEF di tutti i tipi importabili e di tutti i tipi esportabili;
- A seguito del discovery sappiamo che MEF avrà popolato la proprietà pubblica “Installers” con tutte le istanze “esportate” di tipo IWindsorInstaller;
- possiamo quindi chiedere a Windsor di completare il processo di configurazione;
Definiamo semplicemente un nuovo “installer” per Windsor e lo marchiamo come “esportabile” e MEF farà tutto il resto.[Export( typeof( IWindsorInstaller ) )] public class PresentationInstaller : IWindsorInstaller { public void Install( IWindsorContainer container, IConfigurationStore store ) { var descriptor = AllTypes.FromAssemblyNamed( "MyAssembly" ); container.Register ( descriptor.Where( t => t.Namespace.IsLike( "*.Presentation" ) ) .Unless( t => t.IsAbstract || t.IsNested ) .Configure( d => { if( d.Implementation.Name.IsLike( "*Shell*" ) ) { d.LifeStyle.Is( LifestyleType.Singleton ); } else { d.LifeStyle.Is( LifestyleType.Transient ); } } ) .WithService.Select( ( type, baseTypes ) => { var all = type.GetInterfaces() .Where( i => i.IsAttributeDefined<ContractAttribute>() ); return all; } ) ); container.Register ( descriptor.Where( t => t.Namespace.IsLike( "*.Presentation.Model" ) ) .Unless( t => t.IsAbstract || t.IsNested ) .Configure( d => d.LifeStyle.Is( LifestyleType.Transient ) ) ); } }
Il tutto è di una semplicità disarmante e riduce drasticamente la complessità, ma ancor di più la quantità di codice ripetitivo e noioso, della realizzazione di un’applicazione modulare; non solo ma sulla falsariga di questo esempio si aprono scenari molto interessanti.
.m