Avevo dato uno sguardo a MEF tempo fa, giusto uno sguardo. L’ho poi usato, in un piccolo progetto, per la gestione di un sistema basato su plugin, la morte sua, ed ero rimasto piacevolmente sorpreso dalla semplicità e rapidità con cui avevo messo in piedi la soluzione.
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? Smile
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);
Quindi perché non pensare ad una cosa del tipo:
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 );
	}
}
L’ApplicationBootstrapper è il “main entry point” della nostra applicazione (deriva da un WindsorApplicationBootstrapper che ha l’unico scopo di ridurre un po’ la frizione):
  • 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;
In uno scenario di questo genere un modulo cosa deve fare? semplicemente ad esempio questo:
[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 ) )
		);
	}
}
Definiamo semplicemente un nuovo “installer” per Windsor e lo marchiamo come “esportabile” e MEF farà tutto il resto.
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