În ultimele săptămâni am descoperit împreună principiile de bază ale Programării Orientate pe Aspecte (AOP). Acum este timpul să vedem cum putem valorifica adevăratul potențial caracteristicile AOP utilizând PostSharp.
Înainte de a intra în subiect, să facem o scurtă recapitulare. AOP este o paradigmă de programare care are drept scop principal creșterea modularității unei aplicații. AOP încearcă să atingă acest scop prin permiterea separării aspectelor secante(cross-cutting concerns) - utilizând interceptarea diferitelor comenzi sau cereri.
În ultimele articole am descoperit cum putem utiliza AOP folosind Unity și proprietăți .NET 4.5 (RealProxy). Unity ne oferă posibilitatea de a înregistra acțiunile care pot fi executate înainte și după o acțiune specifică. Clasa RealProxy este clasa principală în jurul tuturor acestor proprietăți, care este utilizată de către framework-uri precum Unity pentru a oferi această proprietate.
Cea mai mare diferență dintre RealProxy și un stack care ne oferă AOP este din perspectiva proprietăților. Utilizarea RealProxy în mod direct ne va cere să scriem toată funcționalitatea de care avem nevoie - aceasta se poate traduce în timp, bani și mai mult cod care necesită mentenanță (în cele din urmă, nu dorim să reinventăm roata).
PostSharp este primul framework AOP real prezentat în această serie de articole. Până acum ne-am uitat la diferite moduri în care putem utiliza proprietățile AOP, dar fără a utiliza un stack AOP real și dedicat.
Am decis să încep cu PostSharp, deoarece atunci când ai nevoie de un framework AOP pentru un proiect real care este destul de mare și de complex, mai întâi ar trebui să îți îndrepți atenția înspre PostSharp. Este tipul de framework care îți oferă aproape toate proprietățile AOP de care ai nevoie.
De obicei, eu compar PostSharp cu ReSharper din punctul de vedere al calității produsului. Este tipul de produs care are toate proprietățile de care ai nevoie atunci când vorbești despre o proprietate specifică.
PostSharp are multe proprietăți care nu pot fi discutate într-un singur articol. În viitorul apropiat, vom studia separat fiecare dintre aceste proprietăți, dar pentru moment, vom arunca o privire asupra celor mai importante proprietăți care sunt în jurul AOP.
Principalele proprietăți ale PostSharp sunt:
Threading Pattern Library - ne permite să controlăm nivelul de abstracție din jurul threading, să detectăm și să diagnosticăm blocajele, să controlăm modul în care sunt executate acțiunile pe diferite fire de execuție (threads) și poziția (în prim plan sau în fundal).
Model Pattern Library - ne oferă proprietățile complete ale AOP utilizând interfața INotifyPropertyChanged. De toate setările și de celelalte lucruri se va ocupa PostSharp. Comportamentul mai complicat poate fi implementat foarte simplu când începem să utilizăm Code Contracts.
Architecture Framework -validează diferite aspecte ale calității codului și designului, șabloane de design, relații nucleu, analiză de cod și multe altele.
Odată menționate principalele proprietăți ale PostSharp, vă prezentăm partea tehnică și modul cum putem adăuga AOP în proiectul nostru, utilizând PostSharp.
Cea mai mare diferență între PostSharp și alte soluții AOP constă în modalitatea în care se adaugă comportament la comandă (custom behaviour). În general, acest comportament se adaugă în timpul de execuție, dar nu și în cazul PostSharp. Toate anexele (hooks) sunt adăugate în timpul de compilare. Astfel, performanța nu este afectată prea mult. Bineînțeles, ca și în cazul oricărui cadru AOP, performanța este afectată puțin, dar în cazul PostSharp, performanța este aproape la fel ca și fără aceasta.
În principiu, PostSharp este o acțiune post-procesor, care ia codul care este compilat și îl modifică. Toate anexele (hooks) sunt adăugate la acest nivel. Rezultatul compilatorului este preluat de PostSharp și prelucrat.
Utilizarea cea mai obișnuită a AOP este să execute o specificație înainte și după ce o metodă a proprietății este apelată (de exemplu, pentru logare). Aceasta se poate face foarte simplu utilizând PostSharp, cu câteva linii de cod.
Primul pas este să definim comportamentul pe care dorim să îl executăm în acel moment anume. Acest lucru poate fi realizat prin extinderea OnMethodBoundaryAspect. Putem să supra-reglăm OnEntry, OnSuccess și OnException. În fiecare dintre aceste metode, noi avem acces la parametri de intrare, la rezultat și așa mai departe. Noi putem chiar să modificăm rezultatul apelării.
[Serializable]
public class FooAOPAttribute : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
...
}
public override void OnSuccess(MethodExecutionArgs args)
{
...
}
public override void OnException(MethodExecutionArgs args)
{
...
}
}
Din acest moment putem utiliza acest atribut pentru a adnota toate metodele pentru care dorim să avem această proprietate. Bineînțeles, putem specifica lista metodelor și în alte feluri. Putem specifica lista metodelor din AssemblyInfo.cs, unde putem defini filtre personalizate pentru metodele pentru care am dori să avem această proprietate.
Această proprietate ne permite să adăugăm cod la clasa noastră în timpul perioadei de rulare. Utilizând un atribut personalizat sau de la AssemblyInfo.cs, putem injecta cod specific în clasa noastră. De exemplu, putem specifica ca o clasă să implementeze o interfață specifică sau putem injecta o metodă specifică de proprietăți.
În exemplul de mai jos, vom descoperi cum putem injecta o proprietate specifică în clasa noastră.
[Serializable]
public class FooAOPAspect : InstanceLevelAspect
{
public string FirstName { get; set; }
}
[IntroduceMember]
public string FirstName { get; set; }
[FooAOPAspect]
public class Student
{
}
Codul IL care va fi generat va conține în interior proprietatea FirstName.
Când lucrăm pe un desktop sau pe o aplicație nativă, trebuie să folosim această interfață pentru a putea primi notificații atunci când valoarea proprietății este modificată (pe UI sau în cod). De obicei, aceasta se face prin implementarea interfeței de mai sus. Pentru a nu complica prea mult codul, o clasă de bază este creată atunci când este adăugată funcția de notificare.
Pentru un cod mic, aceasta este acceptabilă, dar dacă ai o aplicație complicată, ai adăuga mult cod duplicat pentru a susține această proprietate.
PostSharp rezolvă această problemă pentru noi, prin NotifyPropertyChangedAttribute. Odată ce adăugăm această proprietate clasei noastre prin Smart Tag, nu va mai trebui să ne facem griji în privința notificărilor. PostSharp se va ocupa de restul.
[NotifyPropertyChanged]
public class StudentEdit
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get
{
return FirstName + LastName);
}
}
public string Email { get; set; }
}
Ceea ce mi-a plăcut a fost suportul Transitive Dependencies. Aceasta înseamnă că, dacă valoarea FirstName este modificată, se va declanșa o notificare și pentru FullName.
Dacă dorim să adăugăm un atribut specific tuturor claselor noastre dintr-un spațiu de nume (namespace), putem să o facem relativ ușor prin utilizarea atributului multicasting. Acest lucru se face direct în fișierul AssemblyInfo.cs și ne va permite să specificăm pentru care clase este nevoie să adăugăm un atribut anume. Avem posibilitatea de a adăuga filtre, de a exclude anumite clase și așa mai departe. Bineînțeles, această setare multicasting poate fi făcută și direct din cod, utilizând IAspectProvider.
Ultimul lucru pe care trebuie să îl știți în legătură cu aceasta este că mai aveți de asemenea și alte atribute care pot fi utilizate pentru a ignora anumite proprietăți sau pentru a trata notificările într-un mod personal.
După cum ne spune numele, Code Contracts (Contractele codului) ne oferă posibilitatea de a defini un acord la nivelul codului între apelare și metoda proprietății care este apelată. În acest fel, validarea input-ului nu va mai trebui să fie făcută cu un IF (dacă) personalizat. Este destul de asemănătoare cu validarea care poate fi făcută prin ActionFilter și atributele de validare din MVC, de exemplu. Avantajul este că putem defini acest acord la orice nivel, de exemplu atunci când expunem o bibliotecă API.
Cel mai simplu exemplu este verificarea NULL. De obicei, când dorim să facem o verificare NULL, adăugăm un IF (dacă) în metoda/proprietatea noastră și dăm o excepție când valoarea nu este NULL. Același lucru poate fi făcut dacă utilizăm atributul Required.
Vezi exemplul de mai jos:
public class Student
{
public void SetLastName([Required] string newLastName)
{
...
}
}
Fără PostSharp ar trebui să verificăm în corpul mesajului valoarea input și să dăm o excepție. Imaginați-vă cum ar fi să scrieți același cod de 1000 de ori. Acest tip de atribute pot fi folosite și la nivelul proprietății sau al câmpului. Un lucru interesant este atunci când le folosim la valoarea câmpului. Atunci când setăm această valoare la valoarea câmpului sau a proprietății, nu este important de unde este setată valoarea (de exemplu, dintr-o altă metodă), verificarea pentru NULL va fi făcută.
public class Student
{
[Required]
private string _lastName = "Default"
public void SetLastName(string newLastName)
{
_lastName = newLastName;
}
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
public void SetFullName(string newFullName)
{
...
_lastName = lastName;
}
}
O parte din acțiunile de validare default sunt deja definite. Oricând, noi putem defini propria noastră validare la comandă, prin implementarea ILocationValidationAspect. Avem metoda ValidateValue de care avem nevoie pentru a implementa, unde putem să facem validarea noastră personalizată.
Mai sunt și alte proprietăți grozave pe care încă nu le-am discutat, de la cea care ne permite să interceptăm evenimente, aspect compus, injectare de cod, tratarea excepțiilor, securitate, persistență obiect și multe altele. O altă proprietate care îmi place la PostSharp este posibilitatea de a specifica faptul că o interfață nu poate fi implementată de către alte ansambluri și poate fi doar utilizată.
Vă invit pe toți să vizitați web site-ul PostSharp și să îl încercați.
Există trei tipuri de licențe PostSharp. Pentru uz individual, puteți folosi cu succes versiunea Express, care este foarte bună dacă doriți să învățați și să înțelegeți cum funcționează PostSharp.
Pe lângă versiunea Express, mai există cea Professional și Ultimate, care vin cu alte proprietăți care pot fi utilizate cu succes în producție.
Nu uitați că modelul cu licență este per dezvoltator și nu per produse dezvoltate, ceea ce înseamnă că puteți utiliza aceeași licență pentru 1 sau 100 de proiecte.
Bineînțeles că toate aceste proprietăți pot fi implementate de către noi, dar PostSharp ne oferă toate aceste lucruri de-a gata. Este un stack AOP utilizat în întreaga lume, extrem de matur și de bun.
de Cristian Pup
de Alin Luncan
de Corina Pip