În ultimul număr al revistei Today Software Magazine, am discutat despre principiile de bază ale AOP și despre cum putem implementa conceptul de bază al AOP utilizând caracteristici ale .NET 4.5, fără a folosi alte cadre. În acest articol, vom vorbi despre Unity și vom vedea cum putem utiliza acest cadru pentru a implementa AOP.
Într-o primă parte , vă prezentăm o definiție a acronimului AOP și cum o putem utiliza în .NET 4.5 fără alte cadre.
Aspect Oriented Programming (Programarea Orientată pe Aspecte) este o paradigmă de programare având ca scop principal creșterea modularității unei aplicații. AOP încearcă să atingă acest scop prin permiterea separării aspectelor secante (cross-cutting concerns), folosind interceptarea diferitelor comenzi sau cereri.
Un exemplu bun pentru acest caz este audit-ul și logging-ul. În mod normal, dacă utilizăm OOP pentru a dezvolta o aplicație care necesită logging sau audit, vom avea într-o formă sau alta diverse apelări ale mecanismului de logging în codul nostru. În OOP, acest lucru poate fi acceptat, deoarece aceasta este singura modalitate de a scrie log-uri, de a face prelucrare și așa mai departe. Când utilizăm AOP, implementarea sistemului de logging sau audit va trebui să se afle într-un modul separat. Mai mult decât atât, vom avea nevoie de o cale de a scrie informația de logging fără a scrie cod în alte module care vor face apelarea în sine a sistemului de logging, folosind interceptarea.
Această funcționalitate poate fi implementată utilizând instrumentele din .NET 4.5, cum sunt RealProxy și Attribute. RealProxy este ingredientul special în cazul nostru, dându-ne posibilitatea de a intercepta toate cererile adresate unei metode sau proprietăți.
Acesta este un cadru binecunoscut de către dezvoltatorii .NET drept un dependency injection container. Acesta este parte a componentelor care formează Pattern-urile și Practicile Microsoft și este undependency injection container în cazul aplicației ASP.NET MVC. Unity a atins un punct critic în momentul în care a fost complet integrat cu ASP.NET MVC. Din acel moment, mii de proiecte web au început să îl utilizeze.
Din punctul de vedere al unui container de injectare dependențe, Unity este un cadru matur care are toate funcțiile pe care un dezvoltator se așteaptă să le găsească la un astfel de cadru. Caracteristici precum configurare XML, înregistrare, rezolver implicit sau special, injectare de proprietăți, container-e personalizate, sunt în întregime susținute de Unity.
În următorul exemplu, vom vedea cum putem înregistra interfața IFoo în containerul de injectare dependențe și cum putem obține o referință la această exemplificare.
IUnityContainer myContainer = new UnityContainer();
myContainer.RegisterType<ifoo, foo="">();
//or
myContainer. RegisterInstance<ifoo>( new Foo());
IFoo myFoo = myContainer.Resolve<foo>();
</foo></ifoo></ifoo,>
Când este folosit cu ASP.NET MVC, dezvoltatorii nu mai trebuie să gestioneze durata resurselor utilizate de Controller-i, Unity este cel care se ocupă de acest aspect în locul lor. Singura operație pe care trebuie să o facă este să înregistreze aceste resurse și să le solicite în controller, ca în următorul exemplu:
public class HomeController : Con
troller
{
Foo _foo;
public HomeController(Foo foo)
{
_foo = foo;
}
public ActionResult Index()
{
ViewData["Message"] = "Hello World!";
return View();
}
public ActionResult About()
{
return View();
}
}
După cum putem observa în exemplul de mai sus, Unity va putea rezolva toate dependențele în mod automat.
Înainte de a analiza acest subiect mai în amănunt, este necesar să abordăm a atitudine obiectivă. Unity nu este un cadru AOP și, de aceea, nu vom avea toate caracteristicile care formează AOP.
Unity susține AOP la nivel de interceptare. Aceasta înseamnă că avem posibilitatea de a intercepta apelurile către obiectul nostru de la dependency injection container și de a configura un comportament special la acel nivel.
Unity ne oferă posibilitatea de a injecta codul nostru înainte și după o apelare a obiectului nostru țintă. Aceasta poate fi făcută numai cu obiectele care sunt înregistrate în Unity. Noi nu putem modifica cursul pentru obiecte care nu se află în container-ul Unity.
Unity ne oferă această funcționalitate utilizând un mecanism similar cu cel care este obținut prin Decorated Pattern. Singura diferență este că în cazul Unity, container-ul este cel care decorează apelul cu un comportament – atribut "făcut la comandă". La sfârșitul articolului vom vedea cât de ușor este să adaugi un mecanism de urmărire utilizând Unity sau să faci toate apelurile către baza de date să execute, folosind tranzacții.
Putem utiliza Unity pentru obiecte care au fost create în Unity și înregistrate în diferite container-e sau obiecte care au fost create în alte părți ale aplicației noastre. Singura cerință de la Unity este să aibă acces la aceste obiecte într-un fel sau altul.
Toate apelurile de la client pentru tipuri specifice trec prin Unity Interceptor. Acesta este o componentă a cadrului care poate intercepta toate apelurile și poate injecta unul sau mai multe comportamente la comandă, înainte sau după apel. Aceasta înseamnă că între apelul clientului și obiectul țintă, componenta Unity Interceptor va intercepta apelul, va declanșa comportamentul la comandă și va face apelul real către obiectul țintă.
Toate aceste apeluri sunt interceptate prin folosirea unui substitut care trebuie să fie configurat de către dezvoltator. Odată ce substitutul este setat, toate apelurile vor trece prin Unity. Nu există nicio cale prin care cineva să poată "sparge" sistemul și să treacă pe lângă interceptor.
Comportamentul care este injectat utilizând Unity poate schimba valoarea parametrului de intrare sau valoarea returnată.
Există două moduri de a configura interceptarea – de la cod sau utilizând fișiere de configurare. În ambele cazuri, este nevoie să definim comportamentul special care va fi injectat în container-ele Unity. Personal, eu prefer să folosesc codul, utilizând fluentul API care este disponibil. Recomand utilizarea fișierelor de configurare numai atunci când sunteți siguri că va trebui să schimbați configurarea în timpul execuției sau fără a recompila codul. Chiar dacă facem această recomandare, noi am utilizat frecvent configurarea din fișiere pentru că aceasta este mai flexibilă.
Atunci când folosiți fișiere de configurare, vă asumați riscul de a introduce mai ușor probleme de configurare – scrierea greșită a numelui claselor sau redenumirea unui tip și omiterea de a actualiza și fișierele de configurare.
Primul pas este acela de a defini comportamentul pe care dorim să îl executăm atunci când realizăm interceptarea apelului. Acest lucru se face numai prin cod, implementând InterceptionBehavior.
Cea mai importantă metodă a acestei interfețe este "Invoke" (Invocarea), care este declanșată când cineva apelează metodele care sunt interceptate. De la această metodă este nevoie să facem apelul propriu-zis către metoda reală. Înainte și după apel, putem injecta orice tip de comportament.
Deși era previzibilă existența a două metode, una care este apelată înainte de apelul propriu-zis și una după apel, această funcție nu o deține Unity. O altă componentă importantă a acestei interfețe este proprietatea "WillExecute". Stabilirea drept TRUE (adevărată) a proprietății, condiționează interceptarea apelului.
Utilizând această interfață, avem posibilitatea de a controla apelurile către orice metode de la obiectele aplicației noastre. Avem deplin control de a face apelul real sau de a-l mima:
public class FooBehavior : IInterceptionBehavior
{
public IEnumerable<type> GetRequiredInterfaces()
{
return Type.EmptyTypes;
}
public IMethodReturn Invoke(IMethodInvocation input,
GetNextInterceptionBehaviorDelegate getNext)
{
Trace.TraceInformation("Before call");
// Make the call to real object
var methodReturn = getNext().Invoke(input, getNext);
Trace.TraceInformation("After call");
return methodReturn;
}
public bool WillExecute
{
get { return true; }
}
}
</type>
Apoi va trebui să adăugăm acest comportament la Unity. Vom adăuga o secțiune specială la fișierul nostru de configurare care să specifice pentru ce tip dorim să facem această interceptare. În următorul exemplu, vom face această interceptare numai pentru tipul Foo:
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.
Configuration.InterceptionConfigurationExtension,
Microsoft.Practices.Unity.Interception.Configuration"/>
…
<container>
<extension type="Interception" />
<register type="IFoo" mapTo="Foo">
<interceptor type="InterfaceInterceptor" />
<interceptionBehavior type="FooBehavior" />
</register>
</container>
În acest exemplu, specificăm în Unity să folosească interceptorul interfață folosind FooBehavior pentru toate exemplele de obiecte IFoo care sunt asociate cu Foo.
Aceeași configurare poate fi realizată din cod, utilizând configurare fluentă.
unity.RegisterType<ifoo, foo="">(
new ContainerControlledLifetimeManager(),
new Interceptor<interfaceinterceptor>(),
new InterceptionBehavior<foobehavior>());</foobehavior></interfaceinterceptor></ifoo,>
Din acest moment, toate apelurile la exemple Ifoo din containerul Unity vor fi interceptate de către interceptorul (comportamentul) nostru special creat.
Mai există și o altă metodă de a implementa acest mecanism, folosind atribute. Dacă utilizați atribute, va trebui să implementați interfața IcallHandler pentru a specifica comportamentul special și pentru a crea atribute speciale care vor fi folosite pentru a decora metoda pe care dorim să o interceptăm. Din punctul nostru de vedere , este de preferat prima versiune, cea a utilizării IInterceptionBehavior.
În acest articol am văzut cât de ușor putem adăuga funcționalitate AOP unui proiect care utilizează deja Unity. Implementarea acestei funcții este foarte simplă și flexibilă. Ne putem imagina foarte ușor scenarii mai complexe, combinând IinterceptionBehavior și atribute speciale.
Chiar dacă nu avem metode specifice apelate de Unity înainte și după invocare, aceasta poate fi foarte ușor extinsă. Vă invit pe toți să încercați Unity.