Unity è il framework di IoC messo a disposizione dal team di Microsoft Pattern&Practices; in linea col pattern, unity permette di disaccopiare l'implementazione dal suo contratto, rimuovendo le dipendenze a compile time e risolvendole dinamicamente a run time.
Cominciare ad usarlo non è per nulla complesso. Ci sono riuscito pure io ;)
Cosa si può fare per cominciare:
- possiamo definire quali implementazioni concrete utilizzare in risposta alla richiesta di determinate interfacce
- di queste implementazioni definire il lifecycle tramite dei tipi particolari
Vediamo un esempio di file di configurazione che ci permette di fare queste due prime semplicissime cose: come si vede, si deve definire una sezione nuova che conterrà tutte le informazioni per il mapping dei tipi. All'interno di questa sezione possiamo configurare quindi il nostro IoC container definendo il mapping delle interfacce sui tipi concreti. Il tag lifetime ci permette di definire che tipo di lifecycle associare al nostro oggetto: sarà il container a fare il resto. Come si può vedere si possono definire anche degli alias per i tipi, per evitare di riscreve sempre il Fully Qualified Name di tipi usati spesso all'iterno del file di configurazione (o semplicemente per rendere più leggibile la parte di configurazione del container).
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</configSections>
<unity>
<typeAliases>
<typeAlias
alias="singleton"
type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager, Microsoft.Practices.Unity" />
</typeAliases>
<containers>
<container>
<types>
<type
type="xxx.IMySingleton, xxx"
mapTo="yyy.MySingleton, yyy">
<lifetime type="singleton" />
</type>
<type
type="xxx.IMyClass, xxx"
mapTo="yyy.MyClass, yyy">
</type>
</types>
</container>
</containers>
</unity>
</configuration>
Dato il file di configurazione possiamo quindi crearci il nostro container configurandolo con la sezione appena aggiunta:
_container = new UnityContainer();
UnityConfigurationSection section =
(UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers.Default.Configure(_container);
Una volta configurato il container sarà possibile ottenere le istanze desiderate semplicemente richimando il metodo Resolve:
IMyClass concreteInstance = _container.Resolve<IMyClass>();
Un paio di osservazioni aggiuntive: se un tipo mappato ha un costruttore che riceve un parametro di un tipo noto al container configurato, questo verrà iniettato automaticamente dal contenitore stesso. Purtroppo (IMHO) il costruttore scelto da unity è sempre quello con il numero maggiore di parametri e non quello che ha più parametri "noti" al container (ovvero tipi che il container sa risolvere). Quindi se una classe ha un costruttore con una dipendenza in ingresso nota al container ed un altro costruttore con due parametri, non noti al container, il metodo Resolve genererà un eccezione perchè proverà a risolvere il costruttore con due parametri, non riuscendoci. Allo stesso modo verrà generata una eccezione se l'implementazione presenta due costruttori con lo stesso numero di parametri.
Invece che usare il costruttore la dipendenza può essere iniettata anche direttamente su una proprietà della classe: è sufficiente marcare la proprietà con l'attributo [Dependency] per comunicare al container che dovrà risolvere quella dipendenza.
Matteo