Una delle caratteristiche più interessanti di Castle Windsor è l'estensibilità, ottenibile principalmente tramite le facilities, vediamo come costruirne una.
Caso pratico: Progetto che usa il pattern di Repository, avrò una ICustomerRepository, IORderRepository, in generale un repository per ogni root di ogni aggregato (Se seziono il dominio con gli aggregati [Evans DDD]) oppure un repository per ogni oggetto. LA cosa noiosa è avere nel file di configurazione di Castle Windsor una incredibile quantità di componenti configurati tutti eguali. Una possibile soluzione è creare una facility che scandisce tutti i tipi di un assembly, controlla la presenza di un attributo in base al quale aggiunge dinamicamente il componente nel contenitore. Come prima cosa si definisce un attributo custom.
[AttributeUsage(AttributeTargets.Interface, Inherited=true, AllowMultiple=false)]
public class WindsorInterface : Attribute {
public LifestyleType LifestyleType {
get { return mLifestyleType; }
set { mLifestyleType = value; }
}
private LifestyleType mLifestyleType = LifestyleType.Singleton;
}
Fino a qui nulla di particolare un attributo con un unica proprietà che stabilisce il lifecycle, questo attributo va assegnato a tutte le interfacce per cui voglio fare autoscan, il passo successivo è creare una facilities che scandisca tutti i tipi di un assembly per configurare runtime un WindsorContainer.
public class AutoscanFacility : AbstractFacility {
protected override void Init() {
Configure();
}
...
Una facility è un particolare oggetto che si integra in un WindsorContainer per aggiungere caratteristiche, il primo passo è ereditare dalla AbstractFacility e fare l'override del metodo Init() virtuale ed astratto. Nell'init semplicemente viene chiamata la funzione Configure(). Cio che rende castle molto semplice da configurare è l'astrazione che viene fatta sui parametri di configurazione, prima di andare avanti è doveroso mostrare ad esempio come configurare questa facility in un container.
<CastleWindsor>
<facilities>
<facility id="Facility.Autoscan"
type="Nablasoft.Castle.Windsor.AutoscanFacility, NablaHelpers" >
<assemblies>
<assembly name="NablaHelpers.Tests" />
</assemblies>
</facility>
</facilities>
</CastleWindsor>
Come si può vedere basta specificare la classe che implementa la faciliy e poi inserire tutti i parametri che si desidera, nel caso in esame una lista di assembly da esaminare. A questo punto vediamo come è fatta la funzione Configure
private void Configure() {
foreach (IConfiguration child in FacilityConfig.Children) {
if (child.Name == "assemblies") {
ScanAssemblies(child);
}
else
throw new ConfigurationErrorsException(
"Unknown element " + child.Name + " in AutoscanFacility configuration");
}
}
Accedere alla configurazione è banale, l'oggetto FacilityConfig presente nella classe base permette di esaminare la configurazione, per prima cosa si itera su tutti i nodi figli, come si vede dal frammento xml l'unico nodo che può essere specificato si chiama "assemblies".
private void ScanAssemblies(IConfiguration child) {
foreach (IConfiguration asm in child.Children) {
Assembly asmtoscan = Assembly.Load(asm.Attributes["name"]);
ScanAssembly(asmtoscan);
}
}
La funzione scanAssemblies itera nuovamente su tutti i figli del nodo configurazione, in questo caso di valore "assembly" e per ogniuno di essi si recupera un riferimento all'assembly specificato, finalmente si chiama la funzione ScanAssembly().
private void ScanAssembly(Assembly asmtoscan) {
foreach (Type t in asmtoscan.GetTypes()) {
//Now scan all the interfaces implemented by the type.
foreach (Type it in t.GetInterfaces()) {
if (it.IsDefined(typeof(WindsorInterface), false)) {
WindsorInterface wi = (WindsorInterface)
System.Attribute.GetCustomAttribute(it, typeof (WindsorInterface));
Kernel.AddComponent(t.FullName, it, t, wi.LifestyleType);
}
}
}
}
Anche in questo caso il codice è decisamente semplice, per ogni tipo nell'assembly si controllano tutte le interfacce implementate e per ogniuna di esse si controlla se è definito l'attributo WindsorInterface, in caso positivo si recupera l'istanza dell'attributo per recuperare il LifeCycle, infine si aggiunge direttamente il componente runtime nell'oggetto Kernel che gestisce a basso livello le funzionalità di IoC.
Questo esempio mostra l'utilizzo base di una facilities, interagire con il kernel per aggiungere componenti a runtime.
Alk.