Sunteți pe pagina 1din 201

CAPITOLUL CONTROALELE DATELOR

16

Tabelul 16-7. Șabloane TemplateField

Mod Descriere

HeaderTemplate Determină aspectul și conținutul celulei antet.

FooterTemplate Determină aspectul și conținutul celulei subsol (dacă setați


ShowFooter la adevărat).

ItemTemplate Determină aspectul și conținutul fiecărei celule de date.

AlternatingItemTemplate Determină aspectul și conținutul rândurilor pare. Pentru


exemplu, dacă setați AlternatingItemTemplate să aibă un șablon umbrit
culoarea de fundal, GridView aplică această umbrire fiecărei secunde
rând.

EditItemTemplate Stabilește aspectul și controalele utilizate în modul editare.

InsertItemTemplate Stabilește aspectul și controalele utilizate în modul editare. Cel


GridView nu acceptă acest șablon, ci DetailsView și
Controalele FormView (care sunt descrise mai jos în acest capitol) fac acest lucru.

Dintre șabloanele enumerate în tabelul 16-7, EditItemTemplate este unul dintre cele mai utile, deoarece vă
oferă posibilitatea de a controla experiența de editare pentru câmp. Dacă nu utilizați câmpuri șablon, sunteți
limitat la casetele de text obișnuite și nu veți avea nicio validare. Vizualizarea grilă definește, de asemenea,
două șabloane pe care le puteți utiliza în afara oricărei coloane. Acestea sunt PagerTemplate, care vă
permite să personalizați aspectul controalelor pager și EmptyDataTemplate, care vă permite să setați
conținutul care ar trebui să apară dacă GridView este legat de un obiect de date gol.

Editarea șabloanelor în Visual Studio


Visual Studio include suport solid pentru editarea șabloanelor în designerul de pagini web. Pentru a încerca
acest lucru, urmați acești pași:
1. Creați un GridView cu cel puțin o coloană șablon.
2. Selectați GridView și faceți clic pe Editați șabloanele în eticheta inteligentă.
Acest lucru comută GridView în modul de editare a șablonului.
3. În eticheta inteligentă, utilizați lista verticală Afișare pentru a alege șablonul pe
care doriți să îl editați (a se vedea Figura 16-13). Puteți alege oricare dintre cele
două șabloane care se aplică întregului GridView (EmptyDataTemplate sau
PagerTemplate) sau puteți alege un șablon specific pentru una dintre coloanele
șablonului.

566
CAPITOLUL CONTROALELE DATELOR
16 •

Figura 16-13. Editarea unui șablon în Visual Studio

4. Introduceți conținutul în control. Puteți introduce conținut static, comenzi de


glisare și plasare și așa mai departe.
5. Când ați terminat, alegeți Terminați editarea șabloanelor din eticheta inteligentă.

Gestionarea evenimentelor într-un șablon


În unele cazuri, poate fi necesar să gestionați evenimentele generate de controalele pe care le adăugați la
o coloană șablon. De exemplu, imaginați-vă că doriți să adăugați un link de imagine pe care se poate face
clic adăugând un control ImageButton. Acest lucru este destul de ușor de realizat:
<asp:TemplateField HeaderText="Status"> <ItemTemplate>
<asp:ImageButton ID="ImageButton1" runat="server"
ImageUrl="statuspic.gif" /> </ItemTemplate>
</asp:TemplateField>

Problema este că, dacă adăugați un control la un șablon, GridView creează mai multe copii ale acelui
control, câte una pentru fiecare element de date. Când se face clic pe ImageButton, aveți nevoie de o
modalitate de a determina pe ce imagine s-a făcut clic și cărui rând îi aparține.

567
CAPITOLUL CONTROALELE DATELOR
16 •

Modul de rezolvare a acestei probleme este să utilizați un eveniment din GridView, nu butonul conținut. Evenimentul
GridView.RowCommand servește acestui scop, deoarece se declanșează ori de câte ori se face clic pe orice buton în
orice șablon. Acest proces, în care un eveniment de control dintr-un șablon este transformat într-un eveniment în
controlul care conține, se numește barbotare eveniment.
Desigur, aveți nevoie în continuare de o modalitate de a transmite informații evenimentului RowCommand pentru a
identifica rândul în care a avut loc acțiunea. Secretul constă în două proprietăți de șir pe care le oferă toate controalele
butoanelor: CommandName și CommandArgument. CommandName setează un nume descriptiv pe care îl puteți utiliza
pentru a distinge clicurile pe ImageButton de clicurile pe alte controale de buton din GridView. CommandArgument
furnizează un fragment de date specifice rândului pe care le puteți utiliza pentru a identifica rândul pe care s-a făcut clic.
Puteți furniza aceste informații utilizând o expresie de legare a datelor.
Iată un câmp șablon care conține eticheta revizuită ImageButton:

<asp:TemplateField HeaderText="Status"> <ItemTemplate> <asp:ImageButton


ID="ImageButton1" runat="server" ImageUrl="statuspic.gif"
CommandName="StatusClick" CommandArgument='<%# Eval("ProductID") %>' />
</ItemTemplate> </asp:TemplateField>
Descărcați de la Wow! eBook <www.wowebook.com>

Iată codul de care aveți nevoie pentru a răspunde atunci când faceți clic pe un buton Image:

vid protejat GridView1_RowCommand(expeditor obiect, GridViewCommandEventArgs e)


{
dacă (e.CommandName == "StatusClick") lblInfo.Text = "Ați făcut clic pe produs #"
+ e.CommandArgument.ToString();
}

Acest exemplu afișează un mesaj simplu cu ID-ul produsului într-o etichetă.

Editarea cu un șablon
Unul dintre cele mai bune motive pentru a utiliza un șablon este de a oferi o experiență de editare mai bună. În capitolul
anterior, ați văzut cum GridView oferă capacități de editare automată - tot ce trebuie să faceți este să comutați un rând în
modul de editare setând proprietatea GridView.EditIndex. Cel mai simplu mod de a face acest lucru posibil este să
adăugați o coloană CommandField cu butonul ShowEditButton setat la true. Apoi, utilizatorul trebuie doar să facă clic pe
un link din rândul corespunzător pentru a începe editarea acestuia. În acest moment, fiecare etichetă din fiecare coloană
este înlocuită cu o casetă text (cu excepția cazului în care câmpul este doar în citire).
Suportul standard de editare are mai multe limitări:

Nu este întotdeauna adecvat să editați valori utilizând o casetă text: Anumite tipuri de date sunt gestionate cel mai bine cu
alte controale (cum ar fi listele verticale). Câmpurile mari au nevoie de casete de text cu mai multe linii și așa mai departe.

Nu primiți nicio validare: Ar fi bine să restricționați posibilitățile de editare, astfel încât cifrele valutare să
nu poată fi introduse ca numere negative, de exemplu. Puteți face acest lucru adăugând controale
validatoare la un EditItemTemplate.
Aspectul vizual este adesea urât: un rând de casete de text dintr-o grilă ocupă prea mult spațiu și
rareori pare profesional.

Într-o coloană șablon, nu aveți aceste probleme. În schimb, definiți în mod explicit controalele de editare și
aspectul lor utilizând EditItemTemplate. Acesta poate fi un proces oarecum laborios.

568
CAPITOLUL CONTROALELE DATELOR
16 •

Iată coloana șablon utilizată anterior pentru informații despre stoc cu un șablon de editare:

<asp:TemplateField HeaderText="Status"> <ItemStyle width="100px"


/> <ItemTemplate> <b>In stoc:</b> <%# Eval("UnitsInStock") %><br
/> <b>La comanda:</b> <%# Eval("UnitsOnOrder") %><br />
<b>Reorder:</b> <%# Eval("ReorderLevel") %> </ItemTemplate>
<EditItemTemplate> <b>In stoc: </b> <%# Eval("UnitsInStock")
%><br /> <b>La comanda:</b> <%# Eval("UnitsOnOrder") %><br
/><br /> <b>Reordonare:</b>

<asp:TextBox Text='<%# Bind("ReorderLevel") %>' width="25px" runat="server" id="txtReorder" /> </EditItemTemplate>


</asp:TemplateField>

Figura 16-14 prezintă rândul în modul editare.

Figura 16-14. Utilizarea unui șablon de editare

Când legați o valoare editabilă la un control, trebuie să utilizați metoda Bind() în expresia de legare a datelor
în locul metodei obișnuite Eval(). Spre deosebire de metoda Eval(), care poate fi plasată oriunde într-o
pagină, metoda Bind() trebuie utilizată pentru a seta o proprietate de control. Numai metoda Bind() creează
legătura bidirecțională, asigurându-se că valorile actualizate vor fi returnate la server.

569
CAPITOLUL CONTROALELE DATELOR
16 •

Un detaliu interesant aici este că, deși șablonul de element afișează trei câmpuri, șablonul de editare permite
modificarea doar a unuia dintre acestea. Când GridView comite o actualizare, va trimite numai parametrii limitați,
editabili. În exemplul anterior, aceasta înseamnă că GridView va trece înapoi
un parametru @ReorderLevel, dar nu un parametru @UnitsInStock sau @UnitsOnOrder. Acest lucru este
important, deoarece atunci când scrieți comanda de actualizare parametrizată, aceasta trebuie să utilizeze
numai parametrii pe care îi aveți la dispoziție. Iată controlul SqlDataSource modificat cu comanda corectă:
<asp:SqlDataSource ID="sourceProducts" runat="server" ConnectionString="<%$
ConnectionStrings:Northwind %>" SelectCommand="SELECT ProductID, ProductName,
UnitPrice, UnitsInStock, UnitsOnOrder,ReorderLevel FROM Products"
UpdateCommand="UPDATE Products SET ProductName=@ProductName,
UnitPrice=@UnitPrice, ReorderLevel=@ReorderLevel WHERE ProductID=@ProductID">
</asp:SqlDataSource>

Editare cu validare
Acum că aveți șablonul pregătit, de ce să nu adăugați o bibelouri suplimentare, cum ar fi un validator, pentru
a prinde greșelile de editare? În exemplul următor, un RangeValidator previne modificările care pun
ReorderLevel la mai puțin de 0 sau mai mult de 100:
<asp:TemplateField HeaderText="Status"> <ItemStyle width="100px"
/> <ItemTemplate> <b>In stoc:</b> <%# Eval("UnitsInStock") %><br
/> <b>La comanda:</b> <%# Eval("UnitsOnOrder") %><br />
<b>Reorder:</b> <%# Eval("ReorderLevel") %> </ItemTemplate>
<EditItemTemplate> <b>In stoc: </b> <%# Eval("UnitsInStock")
%><br /> <b>La comanda:</b> <%# Eval("UnitsOnOrder") %><br
/><br /> <b>Reordonare:</b>

<asp:TextBox Text='<%# Bind("ReorderLevel") %>' width="25px" runat="server" id="txtReorder" /> <asp:RangeValidator


id="rngValidator" MinimumValue="0" MaximumValue="100" ControlToValidate="txtReorder" runat="server"
ErrorMessage="Valoare în afara intervalului." Tip="Întreg"/> </EditItemTemplate> </asp:TemplateField>

Figura 16-15 prezintă validarea la locul de muncă. Dacă valoarea nu este validă, browserul nu permite ca
pagina să fie postată înapoi și nu rulează niciun cod de bază de date.

570
CAPITOLUL CONTROALELE DATELOR
16 •

Figura 16-15. Crearea unui șablon de editare cu validare

Notă SqlDataSource este suficient de inteligent pentru a gestiona validarea în mod corespunzător, chiar dacă ați dezactivat validarea pe partea client (sau browserul nu o acceptă). În această situație, pagina este postat înapoi, dar SqlDataSource observă că conține date nevalide (inspectând proprietatea Page.IsValid) și nu încearcă să efectueze actualizarea. Pentru mai multe informații despre validarea pe partea client și pe partea server, consultați capitolul 9.

Editarea fără coloană de comandă


Până acum, toate exemplele pe care le-ați văzut au folosit un câmp de comandă care generează automat controale de
editare. Cu toate acestea, acum că ați făcut tranziția la o abordare bazată pe șabloane, merită să luați în considerare
modul în care puteți adăuga propriile controale de editare.
De fapt, este destul de ușor. Tot ce trebuie să faceți este să adăugați un control buton la șablonul de
element și să setați CommandName la Editare. Acest lucru declanșează automat procesul de editare, care
declanșează evenimentele corespunzătoare și comută rândul în modul de editare.
<ItemTemplate> <b>In stoc:</b> <%# Eval("UnitsInStock")
%><br /> <b>La comanda:</b> <%# Eval("UnitsOnOrder")
%><br /> <b>Reorder:</b> <%# Eval("ReorderLevel") %>
<br /><br />

<asp:LinkButton runat="server" text="editare"

571
CAPITOLUL CONTROALELE DATELOR
16 •

CommandName = "Edit" ID = "LinkButton1" / >


</ItemTemplate>

În șablonul de editare a elementului, aveți nevoie de încă două butoane cu valorile CommandName ale Update și Cancel:

<EditItemTemplate> <b>In stoc:</b> <%# Eval("UnitsInStock")


%><br /> <b>La comanda:</b> <%# Eval("UnitsOnOrder") %><br
/><br /> <b>Reorder:</b>

<asp:TextBox Text='<%# Bind("ReorderLevel") %>' width="25px" runat="server" id="txtReorder" /> <br /><br />

<asp:LinkButton runat="server" text="Update" CommandName="Update" ID="LinkButton1" /> <asp:LinkButton


runat="server" text="cancel" commandname="cancel" ID="LinkButton2" CausesValidation="False" />

</EditItemTemplate>
Observați că butonul Anulare trebuie să aibă proprietatea CausesValidation setată la false pentru a ocoli
validarea. În acest fel, puteți anula editarea chiar dacă datele curente nu sunt valide.
Atâta timp cât utilizați aceste nume, evenimentele de editare GridView se vor declanșa, iar controalele sursei
de date vor reacționa în același mod ca și cum ați utiliza controalele de editare generate automat. Figura
16-16 prezintă butoanele de editare personalizate.

Figura 16-16. Comenzi de editare personalizate

572
CAPITOLUL CONTROALELE DATELOR
16

DetailsView și FormView
GridView excelează la afișarea unui tabel dens cu mai multe rânduri de informații. Cu toate acestea, uneori doriți să oferiți o
privire detaliată asupra unei singure înregistrări. Puteți găsi o soluție utilizând o coloană șablon într-un GridView, dar ASP.NET
include două controale care sunt adaptate în acest scop: DetailsView și FormView. Ambele afișează o singură înregistrare la un
moment dat, dar pot include butoane pager opționale care vă permit să parcurgeți o serie de înregistrări (afișând una pe
pagină). Ambele vă oferă o modalitate simplă de a insera o înregistrare nouă, pe care GridView nu o permite. Și ambele
acceptă șabloane, dar FormView le solicită. Aceasta este distincția esențială dintre cele două controale.

O altă diferență este faptul că DetailsView își redă conținutul într-un tabel, în timp ce FormView vă oferă
flexibilitatea de a vă afișa conținutul fără tabel. Astfel, dacă intenționați să utilizați șabloane, FormView vă
oferă cea mai mare flexibilitate. Dar dacă doriți să evitați complexitatea șabloanelor, DetailsView vă oferă un
model mai simplu, care vă permite să construiți o afișare de date cu mai multe rânduri din obiecte de câmp,
în același mod în care GridView este construit din obiecte coloană. Acum că înțelegeți caracteristicile
GridView, puteți ajunge la curent cu DetailsView și FormView destul de repede. Acest lucru se datorează
faptului că ambele împrumută o parte din modelul GridView.

DetaliiVezi
Vizualizarea detaliilor afișează o singură înregistrare la un moment dat. Plasează fiecare câmp într-un rând separat
al unui tabel. Ați văzut în capitolul 15 cum să creați o vizualizare de bază a detaliilor pentru a afișa înregistrarea
selectată în prezent. DetailsView vă permite, de asemenea, să treceți de la o înregistrare la alta utilizând controale
de paginare, dacă ați setat proprietatea AllowPaging la true. Puteți configura controalele de paginare utilizând
proprietățile PagerStyle și PagerSettings în același mod în care modificați pagerul pentru GridView. Figura 16-17
prezintă DetailsView atunci când este legat la un set de înregistrări de produse, cu informații complete despre
produs.

Figura 16-17. DetaliiVizualizați cu paginarea

573
CAPITOLUL CONTROALELE DATELOR
16

Este tentant să utilizați comenzile pagerului DetailsView pentru a crea un browser de înregistrări la îndemână.
Din păcate, această abordare poate fi destul de ineficientă. O problemă este că este necesară o postback
separată de fiecare dată când utilizatorul trece de la o înregistrare la alta (în timp ce un control grilă poate
afișa mai multe înregistrări pe aceeași pagină). Dar adevăratul dezavantaj este că de fiecare dată când
pagina este postată înapoi, setul complet de înregistrări este preluat, chiar dacă este afișată o singură
înregistrare. Acest lucru duce la o muncă suplimentară inutilă pentru serverul bazei de date. Dacă alegeți să
implementați o pagină de browser de înregistrări cu DetailsView, trebuie să activați memorarea în cache
pentru a reduce lucrul în baza de date (consultați capitolul 23).
Sfat Este aproape întotdeauna o idee mai bună să utilizați un alt control pentru a permite utilizatorului să aleagă o anumită înregistrare (de exemplu, alegând un ID dintr-o casetă listă), apoi să afișați înregistrarea completă în Vizualizare detalii utilizând o comandă parametrizată care se potrivește doar cu înregistrarea selectată. Capitolul 15 demonstrează această tehnică.

Definirea câmpurilor
DetailsView utilizează reflexia pentru a genera câmpurile pe care le afișează. Aceasta înseamnă că examinează obiectul
de date și creează un rând separat pentru fiecare câmp pe care îl găsește, la fel ca GridView. Puteți dezactiva această
generare automată a rândurilor setând AutoGenerateRows la false. Apoi, depinde de dvs. să declarați informațiile pe
care doriți să le afișați.
Interesant este că utilizați aceleași etichete de câmp pentru a construi un DetailsView pe care îl utilizați pentru a proiecta un
GridView. De exemplu, câmpurile din elementul de date sunt reprezentate cu eticheta BoundField, butoanele pot fi create cu
ButtonField și așa mai departe. Pentru lista completă, consultați tabelul anterior 16-1.
Următorul cod definește un DetailsView care afișează informații despre produs. Această etichetă creează
aceeași grilă de informații prezentată în Figura 16-17, când AutoGenerateRows a fost setată la true.

<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False"


DataSourceID="sourceProducts"> <Fields> <asp:BoundField DataField="ProductID"
HeaderText="ProductID" ReadOnly="True" /> <asp:BoundField DataField="ProductName"
HeaderText="ProductName" /> <asp:BoundField DataField="SupplierID"
HeaderText="SupplierID" /> <asp:BoundField DataField="CategoryID"
HeaderText="CategoryID" /> <asp:BoundField DataField="QuantityPerUnit"
HeaderText="QuantityPerUnit" /> <asp:BoundField DataField="UnitPrice"
HeaderText="UnitPrice" /> <asp:BoundField DataField="UnitsInStock"
HeaderText="UnitsInStock" /> <asp:BoundField DataField="UnitsOnOrder"
HeaderText="UnitsOnOrder" /> <asp:BoundField DataField="ReorderLevel"
HeaderText="ReorderLevel" /> <asp:CheckBoxField DataField="Discontinued"
HeaderText="Discontinued" /> </Fields> ...

</asp:DetailsView>

Puteți utiliza eticheta BoundField pentru a seta proprietăți precum textul antetului, șirul de formatare,
comportamentul de editare etc. (consultați tabelul 16-2). În plus, puteți utiliza proprietatea ShowHeader.
Când este fals, textul antetului este lăsat în afara rândului, iar datele câmpului ocupă ambele celule.

574
CAPITOLUL CONTROALELE DATELOR
16

Sfat În loc să codificați manual fiecare câmp, puteți utiliza aceeași comandă rapidă pe care ați utilizat-o cu GridView. Pur și simplu selectați controlul la momentul proiectării și selectați Reîmprospătare schemă din eticheta inteligentă.

Modelul de câmp nu este singura parte a GridView adoptată de controlul DetailsView. De asemenea, utilizează un
set similar de stiluri, un set similar de evenimente și un model de editare similar. Singura diferență este că, în loc să
creați o coloană dedicată pentru editarea controalelor, setați pur și simplu una dintre proprietățile booleene ale
DetailsView, cum ar fi AutoGenerateDeleteButton, AutoGenerateEditButton și AutoGenerateInsertButton. Linkurile
pentru aceste activități sunt adăugate în partea de jos a Vizualizării detalii. Atunci când adăugați sau editați o
înregistrare, DetailsView utilizează controale standard ale casetei text (consultați Figura 16-18), la fel ca GridView.
Pentru mai multă flexibilitate de editare, veți dori să utilizați șabloane cu DetailsView (adăugând un TemplateField
în loc de BoundField) sau cu controlul FormView (așa cum este descris în continuare).

Figura 16-18. Editarea în DetailsView

FormularVizualizare formular
Dacă aveți nevoie de flexibilitatea maximă a șabloanelor, FormView oferă un control doar pentru șabloane pentru
afișarea și editarea unei singure înregistrări.
Frumusețea modelului șablon FormView este că se potrivește destul de îndeaproape cu modelul
TemplateField din GridView. Aceasta înseamnă că puteți lucra cu următoarele șabloane:

575
CAPITOLUL CONTROALELE DATELOR
16

• ItemTemplate
• EditItemTemplate
• InsertItemTemplate
• FooterTemplate
• HeaderTemplate
• EmptyDataTemplate
• PagerTemplate

Notă Spre deosebire de GridView și DetailsView, care vă permit să adăugați câte obiecte TemplateField doriți, FormView permite doar o singură copie a fiecărui șablon. Dacă doriți să afișați mai multe valori, trebuie să adăugați mai multe expresii de legare la același ItemTemplate.

Puteți utiliza același conținut șablon pe care îl utilizați cu un ȘablonCâmp într-o Vizualizare grilă din
FormularVizualizare. Mai devreme în acest capitol, ați văzut cum puteți utiliza un câmp șablon pentru a
combina informațiile despre stocul unui produs într-o singură coloană (așa cum se arată în Figura 16-12). Iată
cum puteți utiliza același șablon în FormularVizualizare:
<asp:FormView ID="FormView1" runat="server" DataSourceID="sourceProducts">
<ItemTemplate> <b>In stoc:</b> <%# Eval("UnitsInStock") %> <br /> <b>La
comanda:</b> <%# Eval("UnitsOnOrder") %> <br /> <b>Reorder:</b> <%#
Eval("ReorderLevel") %> <br /> </ItemTemplate> </asp: FormularVizualizare>

La fel ca DetailsView, FormView poate afișa o singură înregistrare la un moment dat. (Dacă sursa de date are
mai multe înregistrări, o veți vedea doar pe prima.) Puteți rezolva această problemă setând proprietatea
AllowPaging la true, astfel încât linkurile de paginare să fie create automat. Aceste linkuri permit utilizatorului
să treacă de la o înregistrare la alta, ca în exemplul anterior cu DetailsView. O altă opțiune este să vă asociați
la o sursă de date care returnează o singură înregistrare. Figura 16-19 prezintă un exemplu în care un control
listă verticală vă permite să alegeți un produs, iar o a doua sursă de date afișează înregistrarea
corespunzătoare în controlul FormView.

576
CAPITOLUL CONTROALELE DATELOR
16

Figura 16-19. O vizualizare formular care afișează o singură înregistrare

Iată marcajul de care aveți nevoie pentru a defini lista verticală și sursa sa de date:

<asp:SqlDataSource ID="sourceProducts" runat="server"


ConnectionString="<%$ ConnectionStrings:Northwind %>"
SelectCommand="SELECT ProductID, ProductName FROM Products">
</asp:SqlDataSource>
<asp:DropDownList ID="lstProducts" runat="server" AutoPostBack="True"
DataSourceID="sourceProducts" DataTextField="ProductName"
DataValueField="ProductID" width="184px"> </asp:DropDownList>

Vizualizarea formular utilizează șablonul din exemplul anterior (este regiunea umbrită din pagină). Iată
marcajul pentru FormularView (fără a include șablonul) și sursa de date care obține detaliile complete pentru
produsul selectat.
<asp:SqlDataSource ID="sourceProductFull" runat="server"
ConnectionString="<%$ ConnectionStrings:Northwind %>"
SelectCommand="SELECT * FROM Produse UNDE ProductID=@ProductID">
<SelectParameters> <asp:ControlParameter Name="ProductID"
ControlID="lstProducts" PropertyName="SelectedValue" />
</SelectParameters> </asp:SqlDataSource>

<asp:FormView ID="formProductDetails" runat="server"


DataSourceID="sourceProductFull" BackColor="#FFE0C0"
CellPadding="5"> <ItemTemplate> ...

</ItemTemplate> </asp:FormView>

577
CAPITOLUL CONTROALELE DATELOR
16

Notă Dacă doriți să acceptați editarea cu FormView, trebuie să adăugați controale de buton care declanșează procesele de editare și actualizare, așa cum este descris în secțiunea "Editarea cu un șablon".

Ultimul cuvânt
În acest capitol, ați luat în considerare tot ce aveți nevoie pentru a construi pagini bogate legate de date. Ați
făcut un tur detaliat al GridView și ați luat în considerare suportul său pentru formatare, selectare, sortare,
paginare, utilizarea șabloanelor și editare. De asemenea, ați luat în considerare DetailsView și FormView,
care vă permit să afișați și să editați înregistrări individuale. Folosind aceste trei controale, puteți construi
pagini all-in-one care afișează și editează date, fără a fi nevoie să scrieți pagini de cod ADO.NET. Cel mai
bun dintre toate, fiecare control al datelor este complet configurabil, ceea ce înseamnă că îl puteți adapta
pentru a se potrivi cu aproape orice aplicație web.
Descărcați de la Wow! eBook <www.wowebook.com>

578
C A P I T O L U L 17

•■■

Fișiere și fluxuri

Există un motiv bun pentru care această carte a acoperit ADO.NET înainte de a se ocupa de tehnici mai simple de acces
la date, cum ar fi scrierea și citirea fișierelor obișnuite. Accesul tradițional la fișiere este, în general, mult mai puțin util
într-o aplicație web decât este într-un program desktop. Bazele de date, pe de altă parte, sunt proiectate de la început
pentru a sprijini un număr mare de utilizatori simultani cu viteză, siguranță și eficiență. Majoritatea aplicațiilor web se vor
baza pe o bază de date pentru unele caracteristici, dar multe nu vor avea niciun motiv să utilizeze accesul direct la
fișiere.
Desigur, dezvoltatorii de ASP.NET întreprinzători pot găsi o utilizare pentru aproape orice tehnologie. Dacă această
carte nu ar acoperi accesul la fișiere, fără îndoială că mulți dezvoltatori ar fi frustrați atunci când proiectează aplicații
web cu utilizări legitime (și inovatoare) pentru fișiere obișnuite. De fapt, accesul la fișiere este atât de ușor și simplu în
.NET încât poate fi perfect pentru soluții simple, la scară mică, care nu au nevoie de un produs de bază de date cu
drepturi depline, cum ar fi SQL Server.
Acest capitol explică modul în care puteți utiliza clasele din .NET pentru a citi și modifica informațiile
sistemului de fișiere și chiar pentru a construi un browser de fișiere simplu. Veți învăța, de asemenea, cum
să creați fișiere text simple și binare proprii. În cele din urmă, veți lua în considerare modul în care puteți
permite utilizatorilor să încarce propriile fișiere pe serverul dvs.

Fișiere și aplicații web


De ce majoritatea aplicațiilor web nu utilizează fișiere? Există mai multe limitări ale fișierelor:

Limitări privind denumirea fișierelor: Când creați un fișier nou, este evident că acesta nu poate avea același nume
ca un fișier existent în același director. Asta înseamnă că probabil va trebui să vă întoarceți la un sistem pentru
generarea aleatorie a numelor fișierelor. De exemplu, puteți crea un nume de fișier pe baza unui număr aleatoriu
combinat cu data și ora curente sau puteți crea un nume de fișier care încorporează un identificator global unic
(GUID). Cu ambele abordări, numele fișierelor ar fi unice din punct de vedere statistic, ceea ce înseamnă că
duplicatele ar fi extrem de puțin probabile. Cu toate acestea, numele fișierelor nu ar fi foarte semnificative. În bazele
de date, această problemă este rezolvată mai bine cu tipul de date cu incrementare automată, care completează
automat un anumit câmp cu un număr unic atunci când creați o înregistrare.
Limitări multiutilizator: Bazele de date relaționale oferă caracteristici precum blocarea și tranzacțiile pentru a
preveni inconsecvențele și pentru a se asigura că mai multe persoane pot utiliza aceleași date în același
timp. Comparativ, sistemul de fișiere al serverului web este jalnic înapoi. Deși puteți permite mai multor
utilizatori să citească un fișier simultan, este aproape imposibil să permiteți mai multor utilizatori să
actualizeze același fișier în același timp fără catastrofă.
Probleme de scalabilitate: Operațiunile de fișiere suferă de unele cheltuieli generale. Într-un scenariu simplu, accesul la
fișiere poate fi mai rapid decât conectarea la o bază de date și efectuarea unei interogări. Dar efectul cumulativ într-o

579
CAPITOLUL FIȘIERE ȘI FLUXURI
17

aplicație web mare este foarte diferit. Când mai mulți utilizatori lucrează cu fișiere în același timp, serverul dvs. web poate
încetini dramatic.

Riscuri de securitate: Dacă permiteți utilizatorului să specifice un nume de fișier sau cale, utilizatorul ar putea
concepe o modalitate de a păcăli aplicația să acceseze sau să suprascrie un fișier de sistem protejat. Chiar și
fără această capacitate, un utilizator rău intenționat sau neglijent ar putea utiliza o pagină ASP.NET care
creează sau încarcă fișiere pentru a umple hard disk-ul serverului web și a-l face să nu mai funcționeze. Toate
aceste probleme pot fi prevenite, dar necesită ceva mai multă muncă decât o soluție bazată pe baze de date.

Desigur, accesul la fișiere are utilizările sale. Poate că trebuie să accesați informațiile pe care o altă aplicație le-a
stocat într-un fișier. Sau poate trebuie să stocați informațiile într-un fișier, astfel încât alte aplicații să le poată
accesa. De exemplu, este posibil să creați o aplicație intranet care permite unui grup mic de angajați de încredere
să încarce și să gestioneze documente. Puteți stoca documentele lor într-un câmp binar într-un tabel de bază de
date, dar acest lucru ar face mai dificilă navigarea și deschiderea acestor fișiere fără a utiliza front-end-ul web.

În aceste situații, veți fi bucuroși să aflați că ASP.NET puteți utiliza toate caracteristicile de acces la fișiere
ale .NET Framework. Asta înseamnă că aplicațiile dvs. web pot explora liber sistemul de fișiere, pot
gestiona fișiere și pot crea fișiere noi cu conținut personalizat.

Informații despre sistemul de fișiere


Cel mai simplu nivel de acces la fișiere implică doar preluarea informațiilor despre fișierele și directoarele
existente și efectuarea operațiunilor tipice ale sistemului de fișiere, cum ar fi copierea fișierelor și crearea de
directoare.
.NET oferă cinci clase de bază pentru recuperarea acestui tip de informații. Toate sunt localizate în spațiul
de nume System.IO (și, întâmplător, pot fi utilizate în aplicațiile desktop exact în același mod în care sunt
utilizate în aplicațiile web). Acestea includ următoarele:
• Clasele Director și Fișiere, care oferă metode statice care vă permit să preluați
informații despre orice fișiere și directoare vizibile de pe serverul dvs.
• Clasele DirectoryInfo și FileInfo, care utilizează metode și proprietăți similare ale
instanțelor pentru a prelua același tip de informații
• Clasa DriveInfo, care oferă metode statice care vă permit să recuperați
informații despre o unitate și cantitatea de spațiu liber pe care o oferă
În capitolul 3, ați văzut cum o clasă poate oferi două tipuri de membri. Membrii statici sunt întotdeauna
disponibili - utilizați doar numele clasei. Dar membrii instanței sunt disponibili numai dacă aveți un obiect viu.

Cu clasele de acces la fișiere, metodele statice sunt mai convenabile de utilizat, deoarece nu necesită crearea unei instanțe a
clasei. Asta înseamnă că puteți utiliza o instrucțiune rapidă de cod cu o singură linie pentru a efectua o sarcină simplă, cum ar
fi verificarea existenței unui fișier. Pe de altă parte, dacă trebuie să preluați mai multe informații din același fișier sau director,
este mai ușor să utilizați membrii instanței. În acest fel, nu trebuie să continuați să specificați numele directorului sau fișierului
de fiecare dată când apelați o metodă. Abordarea instanței este, de asemenea, puțin mai rapidă în această situație. Acest
lucru se datorează faptului că clasele FileInfo și DirectoryInfo efectuează verificările de securitate o singură dată, atunci când
creați instanța obiectului. Clasele Director și Fișier efectuează o verificare de securitate de fiecare dată când invocați o
metodă, ceea ce adaugă mai multe cheltuieli generale.

Veți afla despre toate aceste clase în acest capitol. Dar mai întâi, merită să faceți un ocol pentru a vă uita la
o altă clasă care poate simplifica codul care se ocupă de sistemul de fișiere: clasa Path.

580
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Clasa de cale
Împreună cu cele cinci clase prezentate în secțiunea anterioară, .NET include, de asemenea, o clasă de ajutor numită
Path în același spațiu de nume System.IO. Clasa Path nu include nicio funcționalitate reală de gestionare a fișierelor.
Pur și simplu oferă câteva metode statice care sunt utile atunci când manipulați șiruri de caractere care conțin căi de
fișiere și directoare.
De exemplu, clasa Path include o metodă GetFileName() care extrage numele fișierului dintr-un șir
complet. Iată un exemplu:

fișier șir = Path.GetFileName ( @"c: \ Documents \


Upload \ Users \ JamesX \ resume.doc"); fișierul conține
acum "resume.doc"

CĂI DE FIȘIER ÎN ȘIRURI

În C #, trebuie să aveți grijă deosebită atunci când creați șiruri de caractere care dețin căi de fișier sau căi de
director. Acest lucru se datorează faptului că în C#, caracterul de separare a directorului (\) are și un alt înțeles -
indică începutul unei secvențe speciale de caractere. Pentru a indica faptul că doriți cu adevărat o bară oblică
inversă și nu o secvență specială de caractere, aveți nevoie de două bare oblice, așa cum se arată aici:

string myDirectory = "c:\\Temp\\MyFiles";


O altă opțiune este să precedați șirul cu semnul "at" (@). Acest lucru îi spune lui C# să interpreteze
șirul exact așa cum este scris. Iată un exemplu al acestei sintaxe:
șir myDirectory = @"c:\Temp\MyFiles";
Abordarea pe care o utilizați depinde în întregime de dvs., deși sintaxa @ facilitează citirea
căilor lungi (și evitarea greșelilor de scriere).

Clasa Traseu include, de asemenea, o metodă Combine() care poate aborda un traseu relativ la capătul
unui traseu absolut. Aici este la lucru, fuzionând două corzi împreună:

string absolutePath = @"c:\Users\MyDocuments"; șir subPath =


@"Sarah\worksheet.xls"; șir combinat = Path.Combine(absolutePath,
subPath); combinate acum conține "c: \ Users \ MyDocuments \ Sarah \
worksheet.xls"
Puteți efectua toate aceste sarcini pe cont propriu, dar clasa Path este o modalitate excelentă de a evita
erorile. Tabelul 17-1 enumeră metodele clasei Path.

Tabelul 17-1. Metode de cale

Metode Descriere

Combină() Combină o cale cu un nume de fișier sau un subdirector.

ChangeExtension() Returnează o copie a șirului cu o extensie modificată. Dacă nu specificați o


extensie, extensia curentă este eliminată.

581
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Metode Descriere

GetDirectoryName() Returnează toate informațiile din director, care este textul dintre primul și ultimul
separator de director (\).

GetFileName() Returnează doar porțiunea de nume de fișier a unui traseu, care este porțiunea
de după ultimul separator de directoare.

GetFileNameFără Returnează doar porțiunea de nume de fișier a unei căi, dar omite extensia de
extensie() fișier la sfârșit.

GetFullPath() Modifică o cale relativă într-o cale absolută utilizând directorul curent. De exemplu,
dacă c:\Temp\ este directorul curent, apelarea GetFullPath() pe un nume de fișier,
cum ar fi test.txt returnează c:\Temp\test.txt. Această metodă nu are niciun efect
asupra unei căi absolute.

GetPathRoot() Regăsește un șir cu unitatea rădăcină (de exemplu, "c:\"), cu condiția ca


informațiile să fie în șir. Pentru un traseu relativ, returnează o referință nulă.

HasExtension() Returnează true dacă traseul se termină cu o extensie.

IsPathRooted() Returnează true dacă traseul este un traseu absolut și false dacă este un traseu relativ.

Clasele de directoare și fișiere


Clasele Director și File oferă o serie de metode statice utile. Tabelele 17-2 și 17-3 prezintă o prezentare
generală a celor mai importante metode. Majoritatea acestor metode iau același parametru: un nume de
cale complet calificat care identifică directorul sau fișierul asupra căruia doriți să acționeze operațiunea.
Câteva metode, cum ar fi Delete() și Move(), iau parametri suplimentari.
Tabelul 17-2. Directory Class Members

Metodă Descriere

CreateDirectory() Creează un director nou. Dacă specificați un director într-un alt director
inexistent, ASP.NET va crea cu grijă toate directoarele necesare.

Șterge() Șterge directorul gol corespunzător. Pentru a șterge un director împreună


cu conținutul său (subdirectoare și fișiere), adăugați al doilea parametru
opțional al true.

Există() Returnează true sau false pentru a indica dacă directorul specificat există.

GetCreationTime(), Returnează un obiect DateTime care reprezintă ora la care directorul a


GetLastAccessTime() și fost creat, accesat sau scris. Fiecare metodă GetXxx() are o metodă
GetLastWriteTime() SetXxx() corespunzătoare, care nu este afișată în acest tabel.

582
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Metodă Descriere

GetDirectories() și Returnează o matrice de șiruri, câte unul pentru fiecare subdirector sau fișier
GetFiles() (în funcție de metoda utilizată) din directorul specificat. Aceste metode pot
accepta un al doilea parametru care specifică o expresie de căutare (cum ar
fi ASP*.*).

GetLogicalDrives() Returnează o matrice de șiruri, câte unul pentru fiecare unitate prezentă
pe computerul curent. Literele de unitate sunt în acest format: "c:\".

GetParent() Analizează șirul directorului furnizat și vă spune ce este directorul părinte.


Puteți face acest lucru pe cont propriu căutând caracterul \ (sau, mai
generic, Path.DirectorySeparatorChar), dar această funcție face viața puțin
mai ușoară.

GetCurrentDirectory() și Vă permite să setați sau să regăsiți directorul curent, ceea ce este util dacă
SetCurrentDirectory() trebuie să utilizați căi relative în locul căilor complete. În general, aceste
funcții nu sunt necesare.

Mutare() Acceptă doi parametri: calea sursă și calea destinație. Directorul și tot
conținutul său pot fi mutate pe orice cale, atâta timp cât se află pe aceeași
unitate. (Dacă trebuie să mutați fișiere de pe o unitate pe alta, va trebui să
asociați în schimb o operațiune de copiere și o operațiune de ștergere.)

Tabelul 17-3. Membrii clasei de fișiere

Metodă Descriere

Copiere() Acceptă doi parametri: numele fișierului sursă complet calificat și numele
fișierului destinație complet calificat. Pentru a permite suprascrierea, utilizați
versiunea care preia un al treilea parametru boolean și setați-l la true.

Șterge() Șterge fișierul specificat, dar nu lansează o excepție dacă fișierul nu poate fi
găsit.

Există() Indică adevărat sau fals dacă există un fișier specificat.

GetAttributes() și Regăsește sau setează o valoare enumerată care poate include


SetAttributes() orice combinație a valorilor din enumerarea FileAttributes.

GetCreationTime(), Returnează un obiect DateTime care reprezintă ora la care fișierul a fost
GetLastAccessTime() și creat, accesat sau scris ultima dată. Fiecare metodă GetXxx() are o metodă
GetLastWriteTime() SetXxx() corespunzătoare, care nu este afișată în acest tabel.

Mutare() Acceptă doi parametri: numele fișierului sursă complet calificat și numele
fișierului destinație complet calificat. Puteți muta un fișier între unități și chiar
îl puteți redenumi în timp ce îl mutați (sau îl puteți redenumi fără a-l muta).

583
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Clasa Fișier include, de asemenea, câteva metode care vă permit să creați și să deschideți fișiere ca fluxuri. Veți
explora aceste caracteristici în secțiunea "Citirea și scrierea cu fluxuri" din acest capitol. Singura caracteristică care
lipsește clasei File (și clasa FileInfo oferă) este capacitatea de a prelua dimensiunea unui fișier specificat.

Metodele File and Directory sunt destul de intuitive. De exemplu, luați în considerare codul pentru o pagină
simplă care afișează unele informații despre fișierele dintr-un anumit director. Puteți utiliza acest cod pentru a
crea o pagină de administrare simplă care vă permite să revizuiți conținutul unui director FTP (vezi Figura 17-1).
Clienții ar putea folosi această pagină pentru a-și revizui documentele și pentru a elimina fișierele suspecte.

Figura 17-1. O pagină de administrator cu informații despre fișiere

Ar trebui să începeți prin a importa spațiul de nume care are clasele IO:

folosind System.IO;
Codul pentru această pagină este următorul:

public clasa partiala ViewFiles : System.Web.UI.Page


{
șir privat ftpDirectory;

584
CAPITOLUL FIȘIERE ȘI FLUXURI
17

protected void Page_Load(Object sender, EventArgs e) { ftpDirectory =


Path.Combine(Request.PhysicalApplicationPath, "FTP"); dacă (!acest. IsPostBack)
{ CreateFileList(); } }

vid privat CreateFileList() { // Preluați lista de fișiere și afișați-o în pagină. // Acest cod dezactivează, de asemenea, butonul de
ștergere, asigurându-se că utilizatorul // trebuie să vizualizeze informațiile despre fișier înainte de a-l șterge. string[] fileList =
Directory.GetFiles(ftpDirectory); lstFiles.DataSource = fileList; lstFiles.DataBind(); lblFileInfo.Text = ""; } cmdDelete.Enabled =
fals;

protejat void cmdRefresh_Click(Object sender, EventArgs e) {


CreateFileList(); }

protejat void lstFiles_SelectedIndexChanged(Object sender,


EventArgs e) { // Afișați informațiile despre fișierul selectat.

Utilizați StringBuilder pentru cel mai rapid mod de a construi șirul. string fileName =
lstFiles.SelectedItem.Text; System.Text.StringBuilder displayText = nou
System.Text.StringBuilder(); displayText.Append("<b>");
displayText.Append(numefișier); displayText.Append("</b><br /><br />");
displayText.Append("Creat: ");
displayText.Append(File.GetCreationTime(fileName). ToString());
displayText.Append("<br />Ultima accesare: ");
displayText.Append(File.GetLastAccessTime(fileName). ToString());
displayText.Append("<br />");

Afișați informații despre atribute. GetAttributes() poate returna o combinație // de


valori enumerate, deci trebuie să o evaluați cu operatorul // bitwise și (&).

Atribute FileAttributes = File.GetAttributes(fileName); if ((atribute &


FileAttributes.Hidden) == FileAttributes.Hidden) { displayText.Append("Acesta
este un fișier ascuns.<br />"); } dacă ((atribute & FileAttributes.ReadOnly) ==
FileAttributes.ReadOnly) { displayText.Append("Acesta este un fișier doar în
citire.<br />"); cmdDelete.Enabled = false; } else { cmdDelete.Enabled = true; }

585
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Afișați textul generat într-o etichetă. lblFileInfo.Text


= displayText.ToString();
}

vid protejat cmdDelete_Click(Object sender, EventArgs e) {


File.Delete(lstFiles.SelectedItem.Text); CreateFileList(); }

Disecarea codului . . .
• De fiecare dată când pagina se încarcă, setează șirul ftpDirectory. Calea este setată la
subfolderul FTP în directorul curent de aplicații web (care este furnizat de proprietatea
Request.PhysicalApplicationPath). Aceste două detalii (directorul curent de aplicații web
și subfolderul FTP) sunt fuzionate împreună într-un singur șir de cale utilizând metoda
Combine() din clasa Path.
• Procedura CreateFileList() este ușor de codificat, deoarece utilizează
caracteristica de legare a datelor din ListBox. Matricea returnată din metoda
GetFiles() poate fi plasată în listă cu doar câteva linii de cod.
• Proprietatea AutoPostBack a ListBox este setată la true. În acest fel, atunci când utilizatorul
alege un element din listă, ListBox postează pagina înapoi imediat, astfel încât codul să
poată citi informațiile despre fișier și să reîmprospăteze detaliile fișierului de pe pagină.

• Când evaluați enumerarea FileAttributes, trebuie să utilizați operatorul & pentru a


efectua aritmetica la nivel de biți. Acest lucru se datorează faptului că valoarea
returnată de la GetAttributes() poate conține de fapt o combinație de mai multe
atribute. Folosind aritmetica la nivel de biți, puteți extrage doar atributul care vă
interesează și apoi puteți determina dacă este setat.
• Codul care primește informațiile despre fișier construiește un șir lung de text, care este apoi
afișat într-o etichetă. Pentru performanțe optime, acest cod utilizează clasa
System.Text.StringBuilder. Fără StringBuilder, ar trebui să utilizați concatenarea șirurilor
pentru a uni șirul împreună. Acest lucru este mult mai lent, deoarece de fiecare dată când
codul adaugă o bucată de text la șir, .NET creează un obiect șir complet nou în spatele
scenei.
• Codul care afișează informații despre fișiere ar putea beneficia de trecerea la clasa
FileInfo (așa cum se arată în secțiunea următoare). Așa cum este, fiecare metodă trebuie
să specifice același nume de fișier. Acest lucru este puțin obositor și este puțin mai lent,
deoarece fiecare metodă necesită o verificare de securitate separată.
Un ingredient care îi lipsește acestui cod este tratarea erorilor. Când utilizați orice resursă externă, inclusiv fișiere,
este esențial să vă apărați cu un bloc de încercare / captură. În acest fel, puteți face față unor evenimente
imprevizibile care nu pot fi controlate - de exemplu, dacă fișierul nu este accesibil deoarece este deja deschis în alt

586
CAPITOLUL FIȘIERE ȘI FLUXURI
17

program sau dacă contul care rulează codul nu are permisiunile necesare. Codul din acest exemplu este ușor de
corectat - pur și simplu înfășurați toate operațiunile fișierului într-un bloc de încercare / captură. (Veți avea nevoie de
trei - unul pentru codul care citește fișierele din directorul curent, unul pentru codul care preia informațiile din fișierul
selectat și unul pentru codul care șterge fișierul.) Pentru a vedea codul cu logica adăugată de tratare a erorilor,
consultați exemplele descărcabile pentru acest capitol.

PERMISIUNI DE FIȘIER

Când testați aplicația în Visual Studio, este puțin probabil să întâmpinați erori de permisiune pentru fișier. Cu
toate acestea, atunci când implementați aplicația, viața devine mai complicată. Într-un site web implementat,
ASP.NET rulează sub un cont cu privilegii limitate cu atenție. Deși contul exact depinde de versiunea IIS
(consultați capitolul 26 pentru detalii complete), este aproape întotdeauna un membru al grupului IIS_IUSRS.
Dacă încercați să accesați un fișier utilizând un cont care nu are permisiunile necesare, veți primi o
excepție de securitate. Pentru a rezolva astfel de probleme, puteți modifica permisiunile pentru un
fișier sau pentru un întreg director. Pentru aceasta, faceți clic dreapta pe fișier sau director, selectați
Proprietăți și alegeți fila Securitate. Aici puteți adăuga sau elimina utilizatori și grupuri și puteți
configura operațiunile pe care le pot efectua. Alternativ, este posibil să vă fie mai ușor să modificați
contul pe care îl folosește ASP.NET sau să schimbați apartenența la grup. Pentru mai multe informații,
consultați capitolul 26.

Clasele DirectoryInfo și FileInfo


Clasele DirectoryInfo și FileInfo reflectă funcționalitatea din clasele Director și Fișier. În plus, acestea
facilitează parcurgerea relațiilor dintre directoare și fișiere. De exemplu, puteți prelua cu ușurință obiectele
FileInfo pentru fișierele dintr-un director reprezentat de un obiect DirectoryInfo. Rețineți că, în timp ce clasele
Director și Fișier expun numai metodele, DirectoryInfo și FileInfo oferă o combinație de proprietăți și metode.
De exemplu, în timp ce clasa File avea metode separate GetAttributes() și SetAttributes(), clasa FileInfo
include o proprietate Atribute. Un alt lucru frumos despre clasele DirectoryInfo și FileInfo este că împărtășesc
un set comun de proprietăți și metode, deoarece derivă din clasa de bază comună FileSystemInfo. Tabelul
17-4 descrie membrii pe care îi au în comun.

Tabelul 17-4. Membrii DirectoryInfo și FileInfo

Membru Descriere

Atribute Vă permite să regăsiți sau să setați atribute utilizând o combinație de


valori din enumerarea FileAttributes.

CreationTime, Vă permite să setați sau să regăsiți ora creării, ora ultimei accesări și ora
LastAccessTime și ultimei scrieri utilizând un obiect DateTime.
LastWriteTime
Există Returnează true sau false în funcție de existența fișierului sau directorului. Cu
alte cuvinte, puteți crea obiecte FileInfo și DirectoryInfo care nu corespund de
fapt directoarelor fizice curente, deși, evident, nu veți putea utiliza proprietăți
precum CreationTime și metode precum MoveTo().

587
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Membru Descriere

NumeComplet, Nume și Returnează un șir care reprezintă numele complet calificat, directorul sau
Extensie numele fișierului (cu extensie) sau extensia singură, în funcție de
ce proprietate utilizați.

Șterge() Elimină fișierul sau directorul, dacă există. Când ștergeți un director, acesta
trebuie să fie gol sau trebuie să specificați un parametru opțional setat la true.

Reîmprospătare() Actualizează obiectul astfel încât să fie sincronizat cu orice modificări ale sistemului de fișiere care
au avut loc între timp (de exemplu, dacă un atribut a fost modificat
manual utilizând Windows Explorer).

Creare() Creează directorul sau fișierul specificat.

MoveTo() Copiază directorul și conținutul acestuia sau fișierul. Pentru un DirectoryInfo


obiect, trebuie să specificați noua cale; pentru un obiect FileInfo, specificați
o cale și un nume de fișier.
Descărcați de la Wow! eBook <www.wowebook.com>

În plus, clasele FileInfo și DirectoryInfo au câțiva membri unici, așa cum este indicat în tabelul 17-5 și
tabelul 17-6.

Tabelul 17-5. Membrii unici DirectoryInfo

Membru Descriere

Părinte și rădăcină Returnează un obiect DirectoryInfo care reprezintă directorul părinte sau rădăcină. Pentru
un director precum c:\temp\myfiles, părintele este c:\temp, iar rădăcina este c:\.

CreareSubdirector() Creează un director cu numele specificat în directorul reprezentat de obiectul


DirectoryInfo. De asemenea, returnează un nou obiect DirectoryInfo care
reprezintă subdirectorul.

GetDirectories() Returnează o matrice de obiecte DirectoryInfo care reprezintă


toate subdirectoarele conținute în acest director.

GetFiles() Returnează o matrice de obiecte FileInfo care reprezintă toate fișierele conținute
în acest director.

588
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Tabelul 17-6. Membrii unici FileInfo

Membru Descriere

Director Returnează un obiect DirectoryInfo care reprezintă directorul părinte.

DirectoryName Returnează un șir care identifică numele directorului părinte.

Lungime Returnează un număr lung (întreg pe 64 de biți) cu dimensiunea fișierului în octeți.

CopyTo() Copiază un fișier în noua cale și în noul nume de fișier specificat ca parametru. De asemenea,
returnează un nou obiect FileInfo care reprezintă fișierul nou (copiat). Puteți furniza
un parametru suplimentar opțional true pentru a permite suprascrierea.

Când creați un obiect DirectoryInfo sau FileInfo, specificați calea completă în constructor:

DirectoryInfo myDirectory = noul DirectoryInfo(@"c:\Temp"); FileInfo


myFile = nou FileInfo(@"c:\Temp\readme.txt");

Această cale poate sau nu să corespundă unui fișier fizic real sau unui director. Dacă nu, puteți utiliza
întotdeauna metoda Creare() pentru a crea fișierul sau directorul corespunzător:

Definiți noul director și fișier.


DirectoryInfo myDirectory = noul DirectoryInfo(@"c:\Temp\Test"); FileInfo
myFile = nou FileInfo(@"c:\Temp\Test\readme.txt");

Acum creați-le. Ordinea aici este importantă.


Nu puteți crea un fișier într-un director care nu există încă.
myDirectory.Creează(); myFile.Create();

Clasa DriveInfo
Clasa DriveInfo vă permite să regăsiți informații despre o unitate de pe computer. Doar câteva informații vă vor
interesa. De obicei, clasa DriveInfo este utilizată doar pentru a prelua cantitatea totală de spațiu utilizat și liber.

Tabelul 17-7 prezintă membrii DriveInfo. Spre deosebire de clasele FileInfo și DriveInfo, nu există nicio
clasă Drive cu versiuni de instanță ale acestor metode.

Tabelul 17-7. Membrii DriveInfo

Membru Descriere

Dimensiune totală Obține dimensiunea totală a unității, în octeți. Aceasta include spațiul alocat și liber.

TotalFreeSpace Obține cantitatea totală de spațiu liber, în octeți.

DisponibilFreeSpace Obține cantitatea totală de spațiu liber disponibil, în octeți. Spațiul disponibil poate fi mai
mic decât spațiul liber total dacă ați aplicat cote de disc care limitează spațiul pe care îl
poate utiliza procesul ASP.NET.

589
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Membru Descriere

Format unitate Returnează numele sistemului de fișiere utilizat pe unitate (cum ar fi NTFS sau
FAT32) ca șir.

Tip unitate Returnează o valoare din enumerarea DriveType, care indică dacă unitatea este o
unitate fixă, de rețea, CDRom, RAM sau amovibilă (sau Necunoscut dacă tipul
unității nu poate fi determinat).

IsReady Returnează dacă unitatea este pregătită pentru operații de citire sau scriere.
Unitățile amovibile sunt considerate "nepregătite" dacă nu au niciun suport media.
De exemplu, dacă nu există niciun CD într-o unitate CD, IsReady va returna false.
În această situație, nu este sigur să interogați celelalte proprietăți DriveInfo.
Unitățile fixe sunt întotdeauna lizibile.

Nume Returnează numele literei de unitate a unității (cum ar fi C: sau E:).

VolumeLabel Obține sau setează eticheta descriptivă de volum pentru unitate. Într-o unitate
formatată NTFS, eticheta de volum poate avea până la 32 de caractere. Dacă nu
este setată, această proprietate returnează o referință nulă (Nimic).

RootDirectory Returnează un obiect DirectoryInfo pentru directorul rădăcină din această unitate.

GetDrives() Regăsește o matrice de obiecte DriveInfo, reprezentând toate unitățile logice de pe


computerul curent.

Sfat: Încercarea de a citi de pe o unitate care nu este gata (de exemplu, o unitate CD care nu are un CD în ea) va genera o excepție. Pentru a evita această problemă, verificați proprietatea DriveInfo.IsReady și încercați să citiți alte proprietăți numai dacă se întoarce adevărat.

A exemplu de browser de fișiere


Puteți utiliza metode precum DirectoryInfo.GetFiles() și DirectoryInfo.GetDirectories() pentru a crea un
browser de fișiere simplu. Următorul exemplu vă arată cum. Fiți avertizat că, deși acest cod este un bun
exemplu de utilizare a claselor DirectoryInfo și FileInfo, nu este un bun exemplu de securitate. În general, nu
ați dori ca un utilizator să poată afla atât de multe informații despre fișierele de pe serverul dvs.
Programul de browser de fișiere eșantion permite utilizatorului să vadă informații despre orice fișier
din orice director din unitatea curentă, așa cum se arată în Figura 17-2.

590
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Figura 17-2. Un browser de fișiere server web

Codul pentru pagina browserului de fișiere este următorul:

clasă publică parțială FileBrowser : System.Web.UI.Page


{
protejat void Page_Load(object sender, EventArgs e) { if (!this. IsPostBack) { string startingDir = @"c:\"; lblCurrentDir.Text =
startingDir; ShowFilesIn(startingDir); } } ShowDirectoriesIn(startingDir);

591
CAPITOLUL FIȘIERE ȘI FLUXURI
17

private void ShowFilesIn(string dir) { lblFileInfo.Text = "";


lstFiles.Items.Clear(); încercați { DirectoryInfo dirInfo = noul
DirectoryInfo(dir); foreach (FileInfo fileItem in dirInfo.GetFiles()) {
lstFiles.Items.Add(fileItem.Name); } } catch (Exception err) { //
Ignorați eroarea și lăsați caseta listă goală. } }

private void ShowDirectoriesIn(string dir) { lstDirs.Items.Clear(); încercați { DirectoryInfo dirInfo = nou DirectoryInfo(dir); foreach
(DirectoryInfo dirItem in dirInfo.GetDirectories()) { lstDirs.Items.Add(dirItem.Name); }} catch (Exception err) { // Ignorați eroarea și
lăsați caseta listă goală. } }

vid protejat cmdBrowse_Click(Expeditor obiect, EventArgs e) { // Navigați la subdirectorul selectat în prezent. dacă
(lstDirs.SelectedIndex != -1) { string newDir = Path.Combine(lblCurrentDir.Text, lstDirs.SelectedItem.Text); lblCurrentDir.Text
= newDir; ShowFilesIn(newDir); ShowDirectoriesIn(newDir); } }

vid protejat cmdParent_Click(expeditor obiect, EventArgs e) { // Navigați


până la părintele directorului curent. // Metoda Directory.GetParent() ne
ajută. dacă (Directory.GetParent(lblCurrentDir.Text) == null) { // Acesta
este directorul rădăcină; nu mai există niveluri. } } else { string newDir =
Directory.GetParent(lblCurrentDir.Text). Nume complet; lblCurrentDir.Text
= newDir; ShowFilesIn(newDir); } ShowDirectoriesIn(newDir);

592
CAPITOLUL FIȘIERE ȘI FLUXURI
17

vid protejat cmdShowInfo_Click(expeditor obiect, EventArgs e) { // Afișați informații pentru fișierul selectat curent. dacă
(lstFiles.SelectedIndex != -1) { string fileName = Path.Combine(lblCurrentDir.Text, lstFiles.SelectedItem.Text);

StringBuilder displayText = nou StringBuilder(); încercați { FileInfo


selectedFile = nou FileInfo(fileName); displayText.Append("<b>");
displayText.Append(selectedFile.Name); displayText.Append("</b><br
/>Size: "); displayText.Append(selectedFile.Length);
displayText.Append("<br />"); displayText.Append("Creat: ");
displayText.Append(selectedFile.CreationTime.ToString());
displayText.Append("<br />Ultima accesare: "); }
displayText.Append(selectedFile.LastAccessTime.ToString()); catch
(Exception err) } { displayText.Append(err. Mesaj);

lblFileInfo.Text = displayText.ToString();
}
}
}

Disecarea codului . . .
• Controalele listei din acest exemplu nu se afișează imediat. În schimb, pagina web
se bazează pe butoanele Răsfoire la selectat, Sus cu un nivel și Afișare informații.
• În mod implicit, numele directoarelor nu se termină cu un caracter bară oblică inversă (\) (de
exemplu, c:\Temp este utilizat în loc de c:\Temp\). Cu toate acestea, atunci când se face referire la
unitatea rădăcină, este necesară o bară oblică. Acest lucru se datorează unei inconsecvențe
interesante care datează din zilele DOS. Când se utilizează nume de directoare, c:\ se referă la
unitatea rădăcină, dar c: se referă la directorul curent, oricare ar fi acesta. Această ciudățenie poate
cauza probleme atunci când manipulați șiruri care conțin nume de fișiere, deoarece nu doriți să

593
CAPITOLUL FIȘIERE ȘI FLUXURI
17

adăugați o bară oblică suplimentară la o cale (ca în calea nevalidă c:\\myfile.txt). Pentru a rezolva
această problemă, pagina utilizează metoda Combine() din clasa Path. Această metodă unește
corect orice nume de fișier și cale împreună, adăugând \ atunci când este necesar.

• Codul include tot codul necesar de gestionare a erorilor. Dacă încercați să citiți
informațiile pentru un fișier pe care nu aveți permisiunea să îl examinați, se afișează
mesajul de eroare în locul secțiunii de detalii despre fișier. Dacă apare o eroare la
apelarea DirectoryInfo.GetFiles() sau DirectoryInfo.GetDirectories(), eroarea este pur și
simplu ignorată și fișierele sau subdirectoarele nu sunt afișate. Această eroare apare în
cazul în care contul care execută codul nu are permisiunea de a citi conținutul
directorului. De exemplu, acest lucru se întâmplă dacă încercați să accesați directorul
c:\System Volume Information în Windows și nu sunteți administrator.

• Metodele ShowFilesIn() și ShowDirectoriesIn() trec prin colecțiile de fișiere și


directoare pentru a construi listele. O altă abordare este de a utiliza legarea
datelor, așa cum se arată în următorul exemplu de cod:
Un alt mod de a umple lstFiles.
DirectoryInfo dirInfo = noul DirectoryInfo(dir);

lstFiles.DataSource = dirInfo.GetFiles();
lstFiles.DataMember = "Nume";
lstFiles.DataBind();

Amintiți-vă că atunci când legați o colecție de obiecte, trebuie să specificați ce


proprietate va fi utilizată pentru listă. În acest caz, este proprietatea
DirectoryInfo.Name sau FileInfo.Name.

Citirea și scrierea cu fluxuri


.NET Framework facilitează crearea de fișiere simple "plate" în format text sau binar. Spre deosebire de o
bază de date, aceste fișiere nu au nicio structură internă (de aceea se numesc plate). În schimb, aceste
fișiere sunt într-adevăr doar o listă cu orice informații pe care doriți să le stocați.

Fișiere text
Puteți scrie într-un fișier și citi dintr-un fișier utilizând un StreamWriter și un StreamReader - clase dedicate care
abstractizează procesul de interacțiune cu fișierul. Chiar nu este prea mult. Puteți crea clasele StreamWriter și
StreamReader pe cont propriu sau puteți utiliza una dintre metodele statice utile incluse în clasa Fișier, cum ar fi
CreateText() sau OpenText().
Iată un exemplu care primește un StreamWriter pentru scrierea datelor în fișierul c:\myfile.txt:

Definiți un StreamWriter (care este conceput pentru scrierea fișierelor text).


StreamWriter w;

Creați fișierul și obțineți un StreamWriter pentru acesta.


w = File.CreateText(@"c:\myfile.txt");

594
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Când apelați metoda CreateText(), creați fișierul și primiți obiectul StreamWriter. În acest moment, fișierul este deschis și
gata să primească conținutul. Trebuie să scrieți datele în fișier și apoi să îl închideți cât mai curând posibil.

Folosind StreamWriter, puteți apela metoda WriteLine() pentru a adăuga informații la fișier. Metoda
WriteLine() este supraîncărcată, astfel încât poate scrie multe tipuri de date simple, inclusiv șiruri, numere
întregi și alte numere. În esență, toate aceste valori sunt convertite în șiruri de caractere atunci când sunt
scrise într-un fișier și trebuie convertite manual înapoi în tipurile corespunzătoare atunci când citiți fișierul.
w.WriteLine("Acest fișier generat de ASP.NET"); Scrieți un șir.
w.WriteLine(42); Scrieți un număr.

Când terminați cu fișierul, trebuie să vă asigurați că îl închideți apelând metoda Close() sau Dispose(). În caz
contrar, este posibil ca modificările să nu fie scrise corect pe disc și fișierul ar putea fi blocat deschis.
w.Close();
În cele din urmă, atunci când depanați o aplicație care scrie în fișiere, este întotdeauna o idee bună să vă
uitați la ceea ce ați scris folosind un editor de text precum Notepad. Figura 17-3 prezintă conținutul creat în
c:\myfile.txt cu codul simplu pe care l-ați luat în considerare.

Figura 17-3. Un exemplu de fișier text

Pentru a citi informațiile, utilizați clasa StreamReader corespunzătoare. Acesta furnizează o metodă
ReadLine() care obține următoarea valoare disponibilă și o returnează ca șir. ReadLine() începe de la prima
linie și avansează poziția până la sfârșitul fișierului, rând cu rând.
StreamReader r = File.OpenText(@"c:\myfile.txt"); șir de intrareȘir; inputString
= r.ReadLine(); = "Acest fișier generat de ASP.NET" inputString = r.ReadLine();
= "42"

ReadLine() returnează o referință nulă atunci când nu mai există date în fișier. Aceasta înseamnă că
puteți citi toate datele dintr-un fișier folosind un cod ca acesta:

Citiți și afișați liniile din fișier până când se ajunge la sfârșitul fișierului.

linie de șir;
face
{
linie = r.ReadLine(); if
(linie != null) { //

595
CAPITOLUL FIȘIERE ȘI FLUXURI
17

(Procesați linia aici.) } } în timp ce (linia


!= null);

La fel ca atunci când scrieți într-un fișier, trebuie să închideți fișierul după ce ați terminat:
r.Close();
Codul pe care l-ați văzut până acum deschide un fișier în modul utilizator unic. Dacă un al doilea utilizator
încearcă să acceseze același fișier în același timp, va apărea o excepție. Puteți reduce această problemă atunci
când deschideți fișiere utilizând versiunea mai generică cu patru parametri a metodei File.Open() în loc de
File.OpenText(). Trebuie să specificați FileShare.Read pentru parametrul final. Spre deosebire de metoda
OpenText(), metoda Open() returnează un obiect FileStream și trebuie să creați manual un StreamReader care îl
împachetează. Iată codul de care aveți nevoie pentru a crea un StreamReader multiuser-friendly:
FileStream fs = File.Open(@"c:\myfile.txt", FileMode.Open, FileAccess.Read,
FileShare.Read); StreamReader r = noul StreamReader(fs);

Sfat
În capitolul 8, ați văzut cum puteți crea un cookie pentru utilizatorul curent, care poate fi păstrat pe disc ca un simplu fișier text. Aceasta este o tehnică comună pentru stocarea informațiilor într-o aplicație web, dar este destul de diferită de codul de acces la fișiere pe care l-ați văzut în acest capitol. Cookie-urile sunt create pe partea clientului, mai degrabă decât pe server. Aceasta înseamnă că codul dvs. ASP.NET le poate utiliza la solicitări ulterioare de la același utilizator, dar nu sunt adecvate atunci când stocați informații pe care trebuie să le revizuiți

ulterior, informații care sunt mai permanente sau informații care afectează mai mulți utilizatori.

Fișiere binare
De asemenea, puteți citi și scrie în fișiere binare. Datele binare utilizează spațiul mai eficient, dar creează și fișiere
care nu pot fi citite de om. Dacă deschideți un fișier în Notepad, veți vedea o mulțime de caractere ASCII extinse
(cunoscute politicos ca gibberish).
Pentru a deschide un fișier pentru scrierea binară, trebuie să creați un nou obiect BinaryWriter.
Constructorul acceptă un flux, pe care îl puteți prelua folosind metoda File.OpenWrite(). Iată codul pentru a
deschide fișierul c:\binaryfile.bin pentru scrierea binară:
FileStream fs = File.OpenWrite(@"c:\binaryfile.bin"); BinaryWriter
w = nou BinaryWriter(fs);

.NET se concentrează pe obiectele fluxului, mai degrabă decât pe sursa sau destinația datelor. Aceasta
înseamnă că puteți scrie date binare în orice tip de flux, indiferent dacă reprezintă un fișier sau un alt tip de
locație de stocare, utilizând același cod. În plus, scrierea într-un fișier binar este aproape aceeași cu scrierea
într-un fișier text.
string str = "ASP.NET Binary File Test"; int întreg
= 42;
w.Write(str);
w.Write(număr întreg);
w.Close();

596
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Citirea datelor dintr-un fișier binar este ușoară, dar nu la fel de ușoară ca citirea datelor dintr-un fișier text.
Problema este că trebuie să cunoașteți tipul de date al datelor pe care doriți să le preluați. Pentru a regăsi un șir,
utilizați metoda ReadString(). Pentru a regăsi un întreg, trebuie să utilizați ReadInt32(). De aceea, exemplul de
cod precedent scrie variabile în loc de valori literale. Dacă valoarea 42 ar fi codificată ca parametru pentru
metoda Write(), nu ar fi clar dacă valoarea ar fi scrisă ca întreg pe 16 biți, întreg pe 32 de biți, zecimal sau
altceva. Din păcate, poate fi necesar să microgestionați fișierele binare în acest fel pentru a preveni erorile.

BinaryReader r = nou BinaryReader(File.OpenRead(@"c:\binaryfile.bin")); str de


coarde; int întreg; str = r.ReadString(); întreg = r.ReadInt32();

r.Close();

Încă o dată, dacă doriți să utilizați partajarea fișierelor, trebuie să utilizați File.Open() în loc de
File.OpenRead(). Apoi puteți crea un BinaryReader manual, așa cum se arată aici:

FileStream fs = File.Open(@"c:\binaryfile.bin", FileMode.Open,


FileAccess.Read, FileShare.Read); BinaryReader r = nou
BinaryReader(fs);

Notă Nu aveți o modalitate ușoară de a sări la o locație dintr-un fișier text sau binar fără a citi toate informațiile în ordine. Deși puteți utiliza metode precum Seek() pe fluxul subiacent, trebuie să specificați un decalaj în octeți, ceea ce implică unele calcule destul de implicate bazate pe dimensiunile tipurilor de date. Dacă aveți nevoie să stocați o cantitate mare de informații și să vă deplasați rapid prin ea, aveți nevoie de o bază de date dedicată, nu de un fișier binar.

Comenzi rapide pentru citirea și scrierea fișierelor


.NET include funcționalități pentru turbo-încărcarea scrierii și citirii fișierelor. Această funcționalitate provine din mai
multe metode statice din clasa File care vă permit să citiți sau să scrieți un fișier întreg într-o singură linie de cod.

De exemplu, iată un fragment rapid de cod care scrie un fișier cu trei linii și apoi îl regăsește într-un singur
șir:

string[] lines = șir nou[]{"Aceasta este prima linie a fișierului.", "Aceasta este a
doua linie a fișierului.", "Aceasta este a treia linie a fișierului."};

Scrieți fișierul dintr-o singură lovitură.


File.WriteAllLines(@"c:\testfile.txt", linii);

Citiți fișierul dintr-o singură fotografie (într-o variabilă numită conținut).


conținut șir = File.ReadAllLines(@"c:\testfile.txt");
Tabelul 17-8 descrie setul complet de metode rapide de acces la fișiere. Toate acestea sunt metode statice.

597
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Tabelul 17-8. Metode de fișier pentru intrare/ieșire rapidă

Metodă Descriere

ReadAllText() Citește întregul conținut al unui fișier și îl returnează ca un singur șir.

ReadAllLines() Citește întregul conținut al unui fișier și îl returnează ca o matrice de șiruri, unul pentru
fiecare linie.

ReadAllBytes() Citește întregul fișier și returnează conținutul acestuia ca matrice de octeți.

WriteAllText() Creează un fișier, scrie un șir furnizat în fișier și îl închide. Dacă fișierul este deja
există, este suprascris.

WriteAllLines() Creează un fișier, scrie o matrice furnizată de șiruri în fișier (separând fiecare linie
cu un returnare greu) și închide fișierul. Dacă fișierul există deja, acesta este suprascris.

WriteAllBytes() Creează un fișier, scrie o matrice de octeți furnizată în fișier și îl închide. Dacă fișierul
Descărcați de la Wow! eBook <www.wowebook.com>

există deja, este suprascris.

Metodele rapide de acces la fișiere sunt cu siguranță convenabile pentru crearea de fișiere mici. De asemenea, se
asigură că un fișier este păstrat doar pentru o perioadă cât mai scurtă posibil, ceea ce este întotdeauna cea mai bună
abordare pentru a minimiza problemele de concurență. Dar sunt ele cu adevărat practice? Totul depinde de dimensiunea
fișierului. Dacă aveți un fișier mare (să zicem, unul care are câțiva megaocteți), citirea întregului conținut în memorie
simultan este o idee teribilă. Este mult mai bine să citiți câte o bucată de date la un moment dat și să procesați
informațiile puțin câte puțin. Chiar dacă aveți de-a face cu fișiere de dimensiuni medii (să zicem, câteva sute de
kiloocteți), poate doriți să vă îndepărtați de metodele rapide de acces la fișiere. Acest lucru se datorează faptului că
într-un site web popular este posibil să aveți mai multe solicitări care se ocupă de fișiere în același timp, iar cheltuielile
combinate de păstrare a datelor de fișiere ale fiecărui utilizator în memorie ar putea reduce performanța aplicației dvs.

O simplă carte de oaspeți


Următorul exemplu demonstrează tehnicile de acces la fișiere descrise în secțiunile anterioare pentru a crea
o carte de oaspeți simplă. Pagina are de fapt două părți. Dacă nu există intrări de oaspeți curente, clientul
va vedea doar controalele pentru adăugarea unei noi intrări, așa cum se arată în Figura 17-4.

598
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Figura 17-4. Pagina inițială a cărții de oaspeți

Când utilizatorul face clic pe Trimitere, va fi creat un fișier pentru noua intrare din cartea de oaspeți. Atâta
timp cât există cel puțin o intrare în cartea de oaspeți, un control GridView va apărea în partea de sus a
paginii, așa cum se arată în Figura 17-5.

599
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Figura 17-5. Pagina completă a cărții de oaspeți

GridView care reprezintă cartea de oaspeți este construit folosind legarea datelor, pe care ați explorat-o în
capitolele 15 și 16. Din punct de vedere tehnic, GridView este legat de o colecție care conține instanțe ale
clasei BookEntry. Definiția clasei BookEntry este inclusă în fișierul din spatele codului pentru pagina web și
arată astfel:
clasa publică BookEntry
{
autor privat de șiruri; șir public
Autor { get { return author; } set
{ autor = valoare; } }

privat DateTime trimis; public


DateTime Trimis { } obțineți {

600
CAPITOLUL FIȘIERE ȘI FLUXURI
17

returnare trimisă; } set { trimis =


valoare; }

mesaj șir privat; șir public Mesaj


{ get { mesaj return; } set {
mesaj = valoare; } }

Vizualizarea grilă utilizează o singură coloană șablon, care pescuiește valorile pe care trebuie să le
afișeze. Iată cum arată (fără detaliile stilului):

<asp:GridView ID="GuestBookList" runat="server" AutoGenerateColumns="False">


<Columns> <asp:TemplateField HeaderText="Guest Book Comments">
<ItemTemplate> Stânga de: <%# Eval("Autor") %> <br /> <b><%# Eval("Mesaj")
%></b> <br /> Left On: <%# Eval("Trimis") %> </ItemTemplate> </asp: TemplateField>
</Columns> </asp:GridView>

De asemenea, adaugă unele informații de stil care nu sunt incluse aici (deoarece nu este necesar să înțelegeți
logica programului). De fapt, aceste stiluri au fost aplicate în Visual Studio utilizând caracteristica Auto Format din
GridView.
În ceea ce privește intrările, pagina cărții de oaspeți utilizează subfolderul (App_Data\GuestBook) pentru a
stoca o colecție de fișiere. Fiecare fișier reprezintă o intrare separată în cartea de oaspeți. Prin plasarea
folderului Cartea de oaspeti in folderul App_Data, aplicatia web se asigura ca utilizatorul nu poate accesa
direct niciunul dintre fisierele cartii de oaspeti, deoarece serverul web nu va permite acest lucru. Cu toate
acestea, o abordare și mai bună ar fi, de obicei, crearea unui tabel Cartea de oaspeți într-o bază de date și
transformarea fiecărei intrări într-o înregistrare separată. Codul paginii web este următorul:
carte de oaspeti publica partiala : System.Web.UI.Page
{
șir privat guestBookName;

protejat void Page_Load(Object sender, EventArgs e) {


guestBookName = Server.MapPath("~/App_Data/GuestBook"); dacă
(!acest. IsPostBack) { GuestBookList.DataSource = GetAllEntries();
GuestBookList.DataBind(); } }

601
CAPITOLUL FIȘIERE ȘI FLUXURI
17

protejat void cmdSubmit_Click(Object sender, EventArgs e) {

} // Creați un nou obiect BookEntry. BookEntry


newEntry = nou BookEntry(); newEntry.Author
= txtName.Text; newEntry.Submitted =
DateTime.Now; newEntry.Message =
txtMessage.Text;

Lăsați procedura SaveEntry să creeze fișierul corespunzător. încercați


{ SaveEntry(newEntry); } captură (Excepție eronată) { // A apărut o
eroare. Notificați utilizatorul și nu ștergeți afișajul //.

lblError.Text = eroare. Mesaj + " Fișierul nu este salvat.";


restitui; }

Reîmprospătați afișajul.
GuestBookList.DataSource = GetAllEntries();
GuestBookList.DataBind(); txtName.Text = "";
txtMessage.Text = "";

privat List<BookEntry> GetAllEntries() { // Returnați un ArrayList care conține obiecte BookEntry // pentru fiecare
fișier din directorul GuestBook.

Această metodă se bazează pe metoda GetEntryFromFile().


Intrări Listă<BookEntry> = nou List<BookEntry>();

încercați { DirectoryInfo guestBookDir = new DirectoryInfo(guestBookName);


foreach (FileInfo fileItem in guestBookDir.GetFiles()) { try { entries. Adăugare
(GetEntryFromFile(fileItem)); } captură (Excepție err) { // A apărut o eroare la
apelarea GetEntryFromFile(). // Ignorați acest fișier deoarece nu poate fi
citit. } } } captură (Excepție err) } { // A apărut o eroare la apelarea GetFiles().

602
CAPITOLUL FIȘIERE ȘI FLUXURI
17

Ignorați această eroare și lăsați colecția de intrări goală. } intrări de


întoarcere;

privat BookEntry GetEntryFromFile(FileInfo entryFile) { // Transformați informațiile fișierului într-un obiect de intrare în carte.
BookEntry newEntry = nou BookEntry(); StreamReader r = entryFile.OpenText(); newEntry.Author = r.ReadLine();
newEntry.Submitted = DateTime.Parse(r.ReadLine()); newEntry.Message = r.ReadLine(); r.Close(); } returnați newEntry;

vid privat SaveEntry(BookEntry entry) { // Creați un fișier nou pentru această intrare, cu un nume de fișier care ar trebui să fie
unic din punct de vedere statistic.

Aleatoriu aleatoriu = nou Aleatoriu(); string fileName = guestBookName + @"\";


fileName + = DateTime.Now.Ticks.ToString() + aleatoriu. Următor(100). ToString();
FileInfo newFile = nou FileInfo(numefișier); StreamWriter w = newFile.CreateText();

Scrieți informațiile în fișier.


w.WriteLine(intrare. Autor);
w.WriteLine(intrare. Trimis.ToString());
w.WriteLine(intrare. Mesaj);
w.Close();
}
}

Disecarea codului . . .
• Codul utilizează fișiere text, astfel încât să puteți revizui cu ușurință informațiile pe
cont propriu cu Notepad. Puteți utiliza fișiere binare la fel de ușor, ceea ce ar
economisi o cantitate mică de spațiu.
• Numele fișierului pentru fiecare intrare este generat utilizând o combinație între data
și ora curentă (în bife) și un număr aleatoriu. Practic, acest lucru face imposibilă
generarea unui fișier cu un nume de fișier duplicat.
• Acest program utilizează tratarea erorilor pentru a se apăra împotriva posibilelor probleme. Cu toate
acestea, erorile sunt tratate într-un mod diferit, în funcție de momentul în care apar. Dacă apare o
eroare la salvarea unei intrări noi în metoda cmdSubmit_Click(), utilizatorul este avertizat cu privire la
problemă, dar afișajul nu este actualizat. În schimb, informațiile furnizate de utilizator sunt lăsate în
controale, astfel încât operațiunea de salvare să poată fi încercată din nou. Când citiți fișierele
existente în metoda cmdGetAllEntries_Click(), pot apărea două probleme și sunt tratate utilizând

603
CAPITOLUL FIȘIERE ȘI FLUXURI
17

blocuri de excepții separate. O problemă se poate întâmpla atunci când codul apelează GetFiles()
pentru a regăsi lista de fișiere. În această situație, problema este ignorată, dar nu se găsesc fișiere
și astfel nu se afișează intrări în cartea de oaspeți. Dacă acest pas reușește, poate apărea o
problemă la citirea fiecărui fișier în metoda GetEntryFromFile(). În această situație, fișierul care a
cauzat problema este ignorat, dar codul continuă și încearcă să citească fișierele rămase.

Notă Codul de tratare a erorilor din acest exemplu face o treabă bună de recuperare din pragul dezastrului și permite utilizatorului să continue să lucreze, atunci când este posibil. Cu toate acestea, este posibil ca codul de tratare a erorilor să nu facă suficient pentru a vă avertiza că există o problemă. Dacă problema este o întâmplare ciudată, acest comportament este bine. Dar dacă problema este un simptom al unei probleme mai profunde în aplicația dvs. web, ar trebui să știți despre

aceasta.

Pentru a vă asigura că problemele nu sunt trecute cu vederea, puteți alege să afișați un mesaj de
eroare în pagină atunci când apare o excepție. Chiar mai bine, codul dvs. ar putea crea în liniște o
intrare în jurnalul de evenimente care înregistrează problema (așa cum se explică în capitolul 7). În
acest fel, puteți afla despre problemele care au apărut și le puteți corecta mai târziu.

• Proiectarea atentă se asigură că acest program izolează scrierea și citirea codului de


fișiere în funcții separate, cum ar fi SaveEntry(), GetAllEntries() și GetEntryFromFile().
Pentru o organizare și mai bună, puteți muta aceste rutine într-o clasă separată sau
chiar într-o componentă separată. Acest lucru vă va permite să utilizați
ObjectDataSource pentru a reduce codul de legare a datelor. Pentru mai multe
informații, citiți capitolul 22.

Permiterea încărcării fișierelor


Deși ați văzut exemple detaliate despre cum să lucrați cu fișiere și directoare pe serverul web, nu ați luat
încă în considerare întrebarea cum să permiteți încărcarea fișierelor. Problema cu încărcarea fișierelor este
că aveți nevoie de o modalitate de a prelua informații de la client - și, după cum știți deja, tot codul
ASP.NET se execută pe server.

Controlul FileUpload
Din fericire, ASP.NET include un control care permite utilizatorilor site-ului web să încarce fișiere pe serverul web. Odată
ce serverul web primește datele fișierului postat, depinde de aplicația dvs. să le examineze, să le ignore sau să le salveze
într-o bază de date back-end sau într-un fișier de pe serverul web. Controlul FileUpload face acest lucru și reprezintă
eticheta HTML <input type="file">.
Declararea controlului FileUpload este ușoară. Nu expune proprietăți sau evenimente noi pe care le puteți
utiliza prin eticheta de control:
<asp:FileUpload ID="Uploader" runat="server" />
Eticheta <input type="file"> nu vă oferă prea multe opțiuni în ceea ce privește interfața cu utilizatorul (este limitată la o casetă
de text care conține un nume de fișier și un buton Răsfoire). Când utilizatorul face clic pe Răsfoire, browserul prezintă o casetă

604
CAPITOLUL FIȘIERE ȘI FLUXURI
17

de dialog Deschidere și permite utilizatorului să aleagă un fișier. Această parte este cablată în browser și nu puteți modifica
acest comportament. Odată ce utilizatorul selectează un fișier, numele fișierului este completat în caseta de text
corespunzătoare. Cu toate acestea, fișierul nu este încărcat încă, ceea ce se întâmplă mai târziu, când pagina este postată
înapoi. În acest moment, toate datele din toate controalele de intrare (inclusiv datele fișierului) sunt trimise către server. Din
acest motiv, este obișnuit să adăugați un buton pentru a posta înapoi pagina.
Pentru a obține informații despre conținutul fișierului postat, puteți accesa obiectul FileUpload.PostedFile.
Puteți salva conținutul apelând metoda PostedFile.SaveAs():
Uploader.PostedFile.SaveAs(@"c:\Uploads\newfile");
Figura 17-6 prezintă o pagină web completă care demonstrează modul de încărcare a unui fișier specificat
de utilizator. Acest exemplu introduce o întorsătură - permite încărcarea numai a acelor fișiere cu extensiile
.bmp, .gif și .jpg.

Figura 17-6. Un simplu încărcător de fișiere

Iată codul pentru pagina de încărcare:

public parțial clasa UploadFile : System.Web.UI.Page


{
șir privat uploadDirectory;

protejat void Page_Load(expeditor obiect, EventArgs e) { // Plasați fișiere într-un subfolder de site web numit Încărcări.
uploadDirectory = Path.Combine (} Request.PhysicalApplicationPath, "Uploads");

protejat void cmdUpload_Click(expeditor obiect, EventArgs e) { //


Verificați dacă un fișier este într-adevăr trimis. dacă
(Uploader.PostedFile.FileName == "") } { lblInfo.Text = "Nu este

605
CAPITOLUL FIȘIERE ȘI FLUXURI
17

specificat niciun fișier."; altfel

{
} // Verificați extensia.
extensie șir = Path.GetExtension(Uploader.PostedFile.FileName);

comutator (extensie. ToLower()) { cazul ".bmp": cazul ".gif": cazul ".jpg": pauză; implicit: lblInfo.Text = "Acest tip de
fișier nu este permis."; } restitui;

Folosind acest cod, fișierul salvat își va păstra numele original //


fișier atunci când este plasat pe server. șir serverFileName =
Path.GetFileName( Uploader.PostedFile.FileName); șir
fullUploadPath = Path.Combine(uploadDirectory, serverFileName);

încercați {
Uploader.PostedFile.SaveAs(fullUploadPath);
lblInfo.Text = "File " + serverFileName; lblInfo.Text += "
încărcat cu succes în"; lblInfo.Text += fullUploadPath; }
captură (Excepție eroare) { lblInfo.Text = err. Mesaj; }

}
}

Disecarea codului . . .
• Fișierul salvat își păstrează numele original (partea client). Codul utilizează metoda
statică Path.GetFileName() pentru a transforma numele complet calificat furnizat de
FileUpload.PostedFile.FileName și pentru a prelua doar fișierul, fără cale.
• Obiectul FileUpload.PostedFile conține doar câteva proprietăți. O proprietate
interesantă este ContentLength, care returnează dimensiunea fișierului în octeți.
Puteți examina această setare și o puteți utiliza pentru a împiedica un utilizator să
încarce fișiere excesiv de mari.

606
CAPITOLUL FIȘIERE ȘI FLUXURI
17

DIMENSIUNEA MAXIMĂ A ÎNCĂRCĂRII UNUI FIȘIER

În mod prestabilit, ASP.NET va respinge o solicitare mai mare de 4 MB. Cu toate acestea, puteți
modifica acest maxim modificând setarea maxRequestLength din fișierul web.config. Aceasta setează
cel mai mare fișier permis în kiloocteți. Serverul web va refuza să proceseze solicitări mai mari.
Următoarea setare de eșantion configurează serverul să accepte fișiere de până la 8 MB:
<?xml version="1.0" encoding="utf-8" ?>
<configuration> <system.web> <!-- Alte setări omise
pentru claritate. --> <httpRuntime
maxRequestLength="8192" /> </system.web>
</configuration>

Fii atent, totuși. Când permiți o încărcare de 8 MB, codul tău nu va rula până când nu primești solicitarea
completă. Aceasta înseamnă că un server rău intenționat vă poate paraliza serverul trimițând mesaje mari
de solicitare către aplicația dvs. Chiar dacă aplicația dvs. respinge în cele din urmă aceste mesaje, firele
de proces ale lucrătorului ASP.NET vor fi în continuare legate în așteptarea finalizării solicitărilor. Acest tip
de atac se numește atac de refuz al serviciului și, cu cât este mai mare dimensiunea permisă a solicitării,
cu atât site-ul dvs. devine mai susceptibil.

Ultimul cuvânt
Deși bazele de date și site-urile web se potrivesc perfect, nimic nu vă împiedică să utilizați clasele din .NET
Framework pentru a accesa alte tipuri de date, inclusiv fișiere. De fapt, codul pe care îl utilizați pentru a
interacționa cu sistemul de fișiere este același cu cel pe care l-ați utiliza într-o aplicație desktop sau în orice
program .NET. Datorită .NET Framework, puteți rezolva în sfârșit problemele comune de programare în
același mod, indiferent de tipul de aplicație pe care îl creați.

607
Descărcați de la Wow! eBook <www.wowebook.com>
C A P I T O L U L 18

•■■

XML

XML este conceput ca un format universal pentru organizarea datelor. În multe cazuri, atunci când decideți să utilizați
XML, decideți să stocați datele într-un mod standardizat, mai degrabă decât să creați propriile convenții de format noi
(și pentru alți dezvoltatori, necunoscute). Locația reală a acestor date - în memorie, într-un fișier, într-un flux de rețea -
este irelevantă.
În acest capitol, veți învăța regulile de bază ale standardului XML. Veți învăța cum să citiți conținutul XML
utilizând clasele bibliotecii .NET și cum puteți crea și citi propriile documente XML. Veți studia, de
asemenea, unele dintre celelalte standarde care acceptă și extind regulile de bază ale XML, inclusiv
spațiile de nume XML, schema XML și XSLT.

XML explicat
Cel mai bun mod de a înțelege rolul pe care îl joacă XML este să luați în considerare evoluția unui format
de fișier simplu fără XML. De exemplu, luați în considerare un program simplu care stochează elementele
produsului ca listă într-un fișier. Spuneți că atunci când creați pentru prima dată acest program, decideți că
va stoca trei informații despre produs (ID, nume și preț) și veți utiliza un format simplu de fișier text pentru
depanare și testare ușoară. Formatul de fișier pe care îl utilizați arată astfel:
1 Președinte

49.33
2 mașină
43399.55

3 coș cu fructe
proaspete
49.99

Acesta este tipul de format pe care îl puteți crea utilizând clase .NET, cum ar fi StreamWriter. Este ușor de
lucrat cu - trebuie doar să scrieți toate informațiile, în ordine, de sus în jos. Desigur, este un format destul de
fragil. Dacă decideți să stocați o informație suplimentară în fișier (cum ar fi un semnalizator care indică dacă
un element este disponibil), codul vechi nu va funcționa. În schimb, poate fi necesar să recurgeți la
adăugarea unui antet care indică versiunea fișierului:

SuperProProductList
Versiunea 2.0

609
CAPITOLUL XML
18

49.33
Adevărat
2 mașină
43399.55

Coș cu fructe
proaspete True 3
49.99

Fals
Acum, puteți verifica versiunea fișierului atunci când îl deschideți și puteți utiliza în mod corespunzător un cod diferit de
citire a fișierelor. Din păcate, pe măsură ce adăugați tot mai multe versiuni posibile, codul de citire a fișierelor va deveni
incredibil de încurcat și este posibil să rupeți accidental compatibilitatea cu unul dintre formatele de fișiere anterioare fără
să vă dați seama. O abordare mai bună ar fi crearea unui format de fișier care să indice unde începe și se oprește fiecare
înregistrare de produs. Codul dvs. ar seta apoi doar câteva valori implicite adecvate dacă găsește informațiile lipsă într-un
format de fișier mai vechi.
Iată o soluție relativ brută care îmbunătățește SuperProProductList adăugând o secvență specială de
caractere (##Start##) pentru a arăta unde începe fiecare înregistrare nouă:

SuperProProductList Versiunea 3.0 ##Start## 1 Scaun

49.33
Adevărat

##Start##
2 Mașină
43399.55
Adevărat
##Start## 3 Coș cu
fructe proaspete
49.99 Fals

Una peste alta, acesta nu este un efort rău. Din păcate, puteți utiliza la fel de bine formatul de fișier binar în acest
moment - fișierul text devine greu de citit și este chiar mai greu de ghicit ce informație reprezintă fiecare valoare. Pe partea
de cod, veți avea nevoie, de asemenea, de câteva abilități de bază de verificare a erorilor. De exemplu, ar trebui să faceți
codul capabil să sară peste liniile goale introduse accidental, să detecteze o etichetă ##Start## lipsă și așa mai departe,
doar pentru a oferi un nivel de protecție de bază.
Problema centrală cu această soluție autohtonă este că reinventezi roata. În timp ce încercați să scrieți
codul de acces la fișiere de bază și să creați un format de fișier rezonabil de flexibil pentru o sarcină
simplă, alți programatori din întreaga lume își creează propriile soluții private, ad-hoc. Chiar dacă
programul tău funcționează bine și îl poți înțelege, alți programatori cu siguranță nu îl vor găsi ușor.

610
CAPITOLUL XML
18

Îmbunătățirea listei cu XML


Aici intervine XML în imagine. XML este o modalitate universală de a identifica orice tip de date folosind elemente. Aceste
elemente utilizează același tip de format găsit într-un fișier HTML, dar în timp ce elementele HTML indică formatarea,
elementele XML indică conținutul. (Deoarece un fișier XML se referă doar la date, nu există o modalitate standardizată de
a-l afișa într-un browser, deși Internet Explorer afișează o vizualizare pliabilă care vă permite să afișați și să ascundeți
diferite porțiuni ale documentului.)
SuperProProductList ar putea utiliza următoarea sintaxă XML mai clară:

<?xml version
= "1.0"?>
<SuperProProductList>
<Product>
<ID>1</ID>
<Nume>Președinte</Nume>
<Pret>49.33</Pret>
<Disponibil>True</Disponibil>
<Stare>3</Stare>
</Produs> <Produs>
<ID>2</ID> <Name>Car</Name>
<Price>43399.55</Price>
<Available>True</Available>

<Stare>3</Stare>
</Produs> <Produs>
<ID>3</ID> <Nume>Coș cu fructe
proaspete</Nume>
<Pret>49.99</Pret>
<Disponibil>Fals</Disponibil>
<Stare>4</Stare> </Produs>
</SuperProProductList>

Acest format este clar de înțeles. Fiecare articol de produs este inclus într-un element <Produs> și fiecare
informație are propriul element cu un nume adecvat. Elementele sunt imbricate cu mai multe straturi
adâncime pentru a arăta relațiile. În esență, XML oferă sintaxa elementului de bază, iar tu (programatorul)
definești elementele pe care vrei să le folosești. De aceea, XML este adesea descris ca un metalimbaj - este
un limbaj pe care îl utilizați pentru a vă crea propriul limbaj. În exemplul SuperProProductList, acest limbaj
XML particularizat definește elemente precum <Product>, <ID>, <Name> și așa mai departe. Cel mai bine,
atunci când citiți acest document XML în majoritatea limbajelor de programare (inclusiv cele din .NET
Framework), puteți utiliza parserele XML pentru a vă ușura viața. Cu alte cuvinte, nu trebuie să vă faceți griji
cu privire la detectarea locului în care începe și se oprește un element, prăbușirea spațiului alb și așa mai
departe (deși trebuie să vă faceți griji cu privire la capitalizare, deoarece XML este sensibil la majuscule). În
schimb, puteți citi fișierul în câteva obiecte de date XML utile, care fac navigarea în întregul document mult
mai ușoară. În mod similar, acum puteți extinde SuperProProductList cu mai multe informații folosind
elemente suplimentare și orice cod pe care l-ați scris deja va continua să funcționeze fără probleme.

611
CAPITOLUL XML
18

FIȘIERE XML VS. BAZELE

Puteți efectua multe activități cu XML - poate inclusiv unele lucruri pentru care nu a fost proiectat niciodată. Această carte nu
este destinată să vă învețe programarea XML, ci un design bun ASP.NET aplicațiilor. Pentru majoritatea programatorilor
ASP.NET, procesarea fișierelor XML este un înlocuitor ideal pentru rutinele personalizate de acces la fișiere și funcționează
cel mai bine în situațiile în care trebuie să stocați o cantitate mică de date pentru sarcini relativ simple.

Fișierele XML nu sunt un substitut bun pentru o bază de date, deoarece au aceleași limitări ca orice alt tip
de acces la fișiere. Într-o aplicație web, doar un singur utilizator poate actualiza un fișier la un moment dat
fără a provoca dureri de cap grave, indiferent dacă fișierul conține un document XML sau conținut binar.
Produsele de baze de date oferă un set mult mai bogat de caracteristici pentru gestionarea concurenței
multiutilizator și furnizarea de performanțe optimizate. Desigur, nimic nu vă împiedică să stocați date XML
într-o bază de date, pe care multe produse de baze de date o încurajează în mod activ. De fapt, cele mai
noi versiuni ale produselor de baze de date de top, cum ar fi SQL Server și Oracle, includ chiar și
caracteristici XML extinse care acceptă unele dintre standardele pe care le veți vedea în acest capitol.

Noțiuni de bază XML


O parte din popularitatea XML este rezultatul simplității sale. Când creați propriul document XML, trebuie să
vă amintiți doar câteva reguli:
• Elementele XML sunt compuse dintr-o etichetă de început (cum ar fi <Nume>) și o
etichetă de sfârșit (cum ar fi </Nume>). Conținutul este plasat între etichetele de început și
de sfârșit. Dacă includeți o etichetă de început, trebuie să includeți și o etichetă de final
corespunzătoare. Singura altă opțiune este combinarea celor două prin crearea unui
element gol, care include o bară oblică înainte la sfârșit și nu are conținut (cum ar fi <Nume
/>). Acest lucru este similar cu sintaxa pentru controalele ASP.NET.
• Spațiul alb dintre elemente este ignorat. Asta înseamnă că puteți utiliza în mod
liber filele și returnările hard pentru a vă alinia corect informațiile.
• Puteți utiliza numai caractere valide în conținut pentru un element. Nu puteți introduce
caractere speciale, cum ar fi parantezele unghiulare (< >) și ampersandul (&), drept
conținut. În schimb, va trebui să utilizați echivalentele entității (cum ar fi < și > pentru
paranteze unghiulare și & pentru ampersand). Aceste echivalente vor fi convertite automat
în caracterele originale atunci când le citiți în programul dvs. cu clasele .NET
corespunzătoare.
• Elementele XML sunt sensibile la litere mari și mici, astfel încât <ID> și <id>
sunt elemente complet diferite.
• Toate elementele trebuie imbricate într-un element rădăcină. În exemplul
SuperProProductList, elementul rădăcină este <SuperProProductList>. De îndată ce
elementul rădăcină este închis, documentul este terminat și nu puteți adăuga nimic
altceva după el. Cu alte cuvinte, dacă omiteți elementul <SuperProProductList> și
începeți cu un element <Product>, veți putea introduce informații pentru un singur
produs; acest lucru se datorează faptului că imediat ce adăugați < de închidere /
Produs>, documentul este complet. (HTML are o regulă similară și necesită ca tot
conținutul paginii să fie imbricat într-un element rădăcină <html>, dar majoritatea
browserelor vă permit să scăpați fără a urma această regulă.)

612
CAPITOLUL XML
18

• Fiecare element trebuie să fie complet închis. Cu alte cuvinte, atunci când deschideți
un subelement, trebuie să îl închideți înainte de a putea închide părintele.
<Product><ID></ID></Product> este valid, dar <Product><ID></Product></ID> nu este. Ca
regulă generală, indentați când deschideți un element nou, deoarece acest lucru vă va
permite să vedeți structura documentului și să observați dacă închideți accidental mai întâi
elementul greșit.
• Documentele XML trebuie să înceapă cu o declarație XML precum <?xml version="1.0"?>. Acest
lucru semnalează că documentul conține XML și indică orice codificare specială a textului. Cu toate
acestea, multe analizoare XML funcționează bine chiar dacă acest detaliu este omis.

Aceste cerinte ar trebui sa sune familiar - sunt aceleasi reguli pe care le-ati invatat pentru XHTML in capitolul 4. La
urma urmei, XHTML este doar un alt limbaj specializat care este construit folosind regulile standardizate ale XML.

Atâta timp cât îndepliniți aceste cerințe, documentul XML poate fi analizat și afișat ca arbore de bază.
Aceasta înseamnă că documentul este bine format, dar nu înseamnă că este valid. De exemplu, este posibil
să aveți în continuare elementele într-o ordine greșită (de exemplu, <ID><Product></Product></ID>) sau este
posibil să aveți un tip greșit de date într-un anumit câmp (de exemplu, <ID>Chair</ID><Name>2</Name).
Puteți impune aceste reguli suplimentare documentelor XML, așa cum veți vedea mai târziu în acest capitol
când luați în considerare schemele XML. Elementele sunt unitățile primare pentru organizarea informațiilor în
XML (așa cum s-a demonstrat cu exemplul SuperProProductList), dar nu sunt singura opțiune. De
asemenea, puteți utiliza atribute.

Atribute
Atributele adaugă informații suplimentare unui element. În loc să puneți informații într-un subelement, puteți
utiliza un atribut. În comunitatea XML, decizia de a folosi subelemente sau atribute – și ce informații ar
trebui să intre într-un atribut – este o chestiune de mare dezbatere, fără un consens clar. Iată exemplul
SuperProProductList cu atributele ID și Name în loc de subelementele ID și Name:

<?xml version="1.0"?>
<SuperProProductList> <Product > ID="1"
name="Chair" <Price>49.33</Price>
<Available>True</Available>
<Stare>3</Stare> </Produs>
<ID produs=" 2"
name="masina">
<Pret>43399.55</Pret>
<Disponibil>True</Disponibil>
<Stare>3</Stare> </Produs> < ID produs="3"
name="Coș cu fructe proaspete">

<Pret>49.99</Pret>
<Disponibil>Fals</Disponibil>
<Stare>4</Stare> </Produs>
</SuperProProductList>

613
CAPITOLUL XML
18

Desigur, ați văzut deja acest tip de sintaxă cu elemente HTML și controale ASP.NET serverului:

<asp:DropDownList id="lstBackColor" AutoPostBack="True"


width="194px" height="22px" runat="server" />

Atributele sunt, de asemenea, comune în fișierul de configurare:

<sessionState mode="Inproc" cookieless="false" timeout="20" />


Utilizarea atributelor în XML este mai strictă decât în HTML. În XML, atributele trebuie să aibă întotdeauna
valori, iar aceste valori trebuie să utilizeze ghilimele. De exemplu, <Nume produs="Scaun" /> este
acceptabil, dar <Nume produs=Scaun /> sau <Nume produs /> nu este. Cu toate acestea, aveți un pic de
flexibilitate - puteți utiliza ghilimele simple sau duble în jurul oricărei valori a atributului. Este convenabil să
utilizați ghilimele simple dacă știți că valoarea textului din interior va conține un citat dublu (ca în <Nume
produs = 'Scaun roșu "sfârâie" / >). Dacă valoarea textului are ghilimele simple și duble, utilizați ghilimele
duble în jurul valorii și înlocuiți ghilimelele duble din interiorul valorii cu &; echivalent entitate.

Ordinea bacșișului nu este importantă atunci când aveți de-a face cu atribute. Parserele XML tratează atributele ca o colecție de informații neordonate referitoare la un element. Pe de altă parte, ordinea elementelor este adesea importantă. Astfel, dacă aveți nevoie de o modalitate de aranjare a informațiilor și de păstrare a ordinii acestora sau dacă aveți o listă de elemente cu același nume, utilizați elemente, nu atribute.

Comentarii
De asemenea, puteți adăuga comentarii la un document XML. Comentariile merg aproape oriunde și sunt
ignorate în scopul procesării datelor. Comentariile sunt încadrate între paranteze de secvențele de caractere
<!-- și -->. Următoarea listă include trei comentarii valide:
<?xml version="1.0"?>
<SuperProProductList>
<!-- Acesta este un fișier de test. --> <ID produs="1" name="Chair">

<Preț>49.33 </Preț> <!-- De ce atât de scump? --


><Disponibil>Adevărat</Disponibil>
<Stare>3</Stare>
</Produs>
<!-- Alte produse omise din motive de claritate. -->
</SuperProProductList>

Singurul loc în care nu puteți pune un comentariu este încorporat într-o etichetă de început sau de sfârșit (ca
în <myData <!-- Un comentariu nu ar trebui să meargă aici --></myData>).

614
CAPITOLUL XML
18

Clasele XML
.NET oferă un set bogat de clase pentru manipularea XML în mai multe spații de nume care încep cu System.Xml. Unul
dintre cele mai confuze aspecte ale utilizării XML cu .NET este să decideți ce combinație de clase ar trebui să utilizați.
Multe dintre ele oferă funcționalități similare într-un mod ușor diferit, optimizate pentru scenarii specifice sau pentru
compatibilitatea cu standarde specifice.
Majoritatea exemplelor pe care le veți explora utilizează tipurile din spațiul de nume System.XML de bază. Clasele de
aici vă permit să citiți și să scrieți fișiere XML, să manipulați datele XML în memorie și chiar să validați documente XML.

În acest capitol, veți analiza următoarele opțiuni pentru tratarea datelor XML:
• Citirea și scrierea XML direct, la fel cum citiți și scrieți fișiere text folosind
XmlTextWriter și XmlTextReader. Pentru viteză și eficiență, aceasta este cea mai
bună abordare.
• Tratarea XML ca o colecție de obiecte în memorie folosind clasa XDocument.
Dacă aveți nevoie de mai multă flexibilitate decât oferă XmlTextWriter și
XmlTextReader sau doriți doar un model mai simplu și mai simplu (și nu trebuie să
stoarceți fiecare ultimă picătură de performanță), aceasta este o alegere bună.
• Utilizarea controlului XML pentru a transforma conținutul XML în HTML afișabil. În
situația corectă - când tot ce doriți să faceți este să afișați conținut XML utilizând o
foaie de stil XSLT predefinită - această abordare oferă o comandă rapidă utilă.

Notă Când vine vorba de XML, Microsoft este un pic schizofrenic. .NET Framework include cel puțin o duzină de moduri de a citi și manipula XML, inclusiv multe care sunt prea specializate sau limitate pentru a fi acoperite în acest capitol. În secțiunile următoare, veți petrece cea mai mare parte a timpului explorând cele două modalități cele mai practice de a lucra cu XML. În primul rând, veți învăța să utilizați clasele de bază XmlTextWriter și XmlTextReader, care garantează performanțe bune. În al doilea

rând, veți explora clasa XDocument, care poate simplifica procesarea XML complicată.

The XML TextWriter


Una dintre cele mai simple modalități de a crea sau citi orice document XML este de a utiliza clasele de bază
XmlTextWriter și XmlTextReader. Aceste clase funcționează ca rudele lor StreamWriter și StreamReader, cu excepția
faptului că scriu și citesc documente XML în loc de fișiere text obișnuite. Aceasta înseamnă că urmați același proces pe
care l-ați văzut în capitolul 17 pentru crearea unui fișier. Mai întâi, creați sau deschideți fișierul. Apoi, îi scrieți sau citiți
din ea, trecând de sus în jos. În cele din urmă, îl închideți și începeți să lucrați folosind datele preluate în orice mod
doriți.
Înainte de a începe acest exemplu, va trebui să importați spațiile de nume pentru tratarea fișierelor și
procesarea XML:

folosind System.IO; folosind System.Xml;

615
CAPITOLUL XML
18

Iată un exemplu care creează o versiune simplă a documentului SuperProProductList:

Plasați fișierul în subfolderul App_Data al site-ului web curent. Clasa


System.IO.Path facilitează construirea numelui complet al fișierului. fișier șir =
Path.Combine(Request.PhysicalApplicationPath,
@"App_Data\SuperProProductList.xml");
FileStream fs = nou FileStream (fișier, FileMode.Create);
XmlTextWriter w = nou XmlTextWriter(fs, null);

w.WriteStartDocument();
w.WriteStartElement ("SuperProProductList");
w.WriteComment("Acest fișier generat de clasa XmlTextWriter.");

Scrieți primul produs.


w.WriteStartElement ("Produs");
w.WriteAttributeString("ID", "1");
w.WriteAttributeString("Nume", "Scaun");

w.WriteStartElement ("Preț");
w.WriteString("49.33");
w.WriteEndElement();

w.WriteEndElement();

Scrieți al doilea produs.


w.WriteStartElement ("Produs");
w.WriteAttributeString("ID", "2");
w.WriteAttributeString("Nume", "Car");

w.WriteStartElement ("Preț");
w.WriteString("43399.55");

w.WriteEndElement();

w.WriteEndElement();

Scrieți al treilea produs.


w.WriteStartElement ("Produs");
w.WriteAttributeString("ID", "3");
w.WriteAttributeString("Nume", "Coș cu fructe proaspete");

w.WriteStartElement ("Preț");
w.WriteString("49.99");
w.WriteEndElement();

w.WriteEndElement();

Închideți elementul rădăcină.


w.WriteEndElement();
w.WriteEndDocument();
w.Close();

616
CAPITOLUL XML
18

Disecarea codului . . .
• Creați întregul document XML apelând metodele XmlTextWriter, în ordinea
corectă. Pentru a porni un document, începeți întotdeauna apelând
WriteStartDocument(). Pentru a-l termina, apelați WriteEndDocument().
• Următorul pas este scrierea elementelor de care aveți nevoie. Scrieți elemente în
trei pași. Mai întâi, scrieți eticheta de pornire (cum ar fi <Product>) apelând
WriteStartElement(). Apoi scrieți atribute, elemente și conținut text în interior. În cele
din urmă, scrieți eticheta finală (cum ar fi < / Product>) apelând WriteEndElement().

• Metodele pe care le utilizați funcționează întotdeauna cu elementul curent. Deci, dacă


apelați WriteStartElement() și îl urmați cu un apel la WriteAttributeString(), adăugați un
atribut acelui element. În mod similar, dacă utilizați WriteString(), introduceți conținut text în
interiorul elementului curent și dacă utilizați din nou WriteStartElement(), scrieți un alt
element, imbricat în interiorul elementului curent.
În unele privințe, acest cod este similar cu codul pe care l-ați folosit pentru a scrie un fișier text de bază. Cu toate
acestea, are câteva avantaje. Puteți închide elementele rapid și precis, parantezele unghiulare (< >) sunt incluse
automat pentru dvs., iar unele erori (cum ar fi închiderea prea devreme a elementului rădăcină) sunt prinse automat,
asigurând astfel un document XML bine format ca rezultat final.
Pentru a verifica dacă a funcționat codul, deschideți fișierul în Internet Explorer, care oferă automat o
vizualizare pliabilă pentru documentele XML (consultați Figura 18-1).

Figura 18-1. SuperProProductList.xml

617
CAPITOLUL XML
18

FORMATAREA XML-ului

În mod implicit, XmlTextWriter va crea un fișier XML care are toate elementele sale grupate împreună într-o
singură linie, fără returnări sau indentări utile. Nu vedeți această limitare în Figura 18-1, deoarece Internet
Explorer utilizează o foaie de stil pentru a oferi XML un aspect mai lizibil (și mai colorat). Cu toate acestea,
dacă deschideți documentul XML în Notepad, veți vedea diferența.
Deși formatarea suplimentară nu este necesară (și nu modifică modul în care vor fi procesate datele), aceasta
poate face o diferență semnificativă dacă doriți să citiți fișierele XML în Visual Studio, Notepad sau alt editor
de text. Din fericire, XmlTextWriter acceptă formatarea; Trebuie doar să o activați, după cum urmează:

Setați-l la indentarea ieșirii.


w.formatare = formatare.indentat;

Setați numărul de spații de indentare.


w.Indentare = 5;
Descărcați de la Wow! eBook <www.wowebook.com>

Cititorul de text XML


Citirea documentului XML în codul dvs. este la fel de ușoară cu clasa XmlTextReader corespunzătoare. XmlTextReader
se deplasează prin document de sus în jos, câte un nod la un moment dat. Apelați metoda Read() pentru a trece la
nodul următor. Această metodă returnează true dacă există mai multe noduri de citit sau false după ce a citit nodul final.
Nodul curent este furnizat prin proprietățile clasei XmlTextReader, cum ar fi NodeType și Name.

Un nod este o denumire care include comentarii, spații albe, etichete de deschidere, etichete de închidere,
conținut și chiar declarația XML din partea de sus a fișierului. Pentru a înțelege rapid nodurile, puteți utiliza
XmlTextReader pentru a parcurge întregul document de la început până la sfârșit și pentru a afișa fiecare
nod pe care îl întâlnește. Codul pentru această sarcină este următorul:
fișier șir = Path.Combine(Request.PhysicalApplicationPath,
@"App_Data\SuperProProductList.xml"); FileStream fs = nou
FileStream (fișier, FileMode.Open); XmlTextReader r = nou
XmlTextReader(fs);
Utilizați un StringWriter pentru a construi un șir de HTML care //
descrie informațiile citite din documentul XML. StringWriter writer
= nou StringWriter();
Analizați fișierul și citiți fiecare nod. în timp ce
(r.Read())
{
scriitor. Scrie("<b>Tip:</b> "); scriitor.
Scrie(r.NodeType.ToString()); scriitor.
Scrie("<br>");
Numele este disponibil la citirea etichetelor de deschidere și închidere // pentru
un element. Nu este disponibil atunci când citiți conținutul interior. dacă (r.Name

618
CAPITOLUL XML
18

!= "") { scriitor. Scrie("<b>Nume:</b> ");


scriitor. Scriere(r.Name); } scriitor.
Scrie("<br>");

Valoarea este atunci când citim conținutul interior.


dacă (r.Value != "") {scriitor. Scrie("<b>Valoare:</b> ");
scriitor. scrie(r.value); scriitor. Scrie("<br>"); }

if (r.AttributeCount > 0) { writer. Scrie("<b>Atribute:</b> "); pentru (int i = 0; i < r.AttributeCount; i++) { writer. Scrie(" "); scriitor.
Scrie(r.GetAttribute(i)); scriitor. Write(" "); } } scriitor. Scrie("<br>"); scriitor. Scrie("<br>");

}
Fs. Aproape();

Copiați conținutul șirului într-o etichetă pentru a-l afișa.


lblXml.Text = scriitor. ToString();
Pentru a testa acest lucru, încercați pagina de XmlText.aspx inclusă în mostrele online. Acesta produce
rezultatul prezentat în figura 18-2.

619
CAPITOLUL XML
18

Figura 18-2. Structura XML de citire

Următoarea este o listă a tuturor nodurilor găsite, scurtată pentru a include un singur produs:

Tip: XmlDeclaration
Nume: xml Valoare:
version= "1.0" Atribute:
1.0
Tip: Nume element:
SuperProProductList
Tip: Valoare comentariu: Acest fișier generat de clasa XmlTextWriter. Tip:

620
CAPITOLUL XML
18

Nume element: Atributele


produsului: 1 Scaun

Tip: Nume
element: Preț
Tip: Valoare text: 49.33

Tip: EndElement
Nume: Preț
Tip: EndElement
Nume: Produs
Tip: EndElement
Denumire: SuperProProductList
Dacă utilizați trucul de indentare descris anterior (în bara laterală "Formatarea XML"), veți vedea noduri
suplimentare care reprezintă biții de spațiu alb dintre elemente.
Într-o aplicație tipică, ar trebui să mergeți la pescuit pentru elementele care vă interesează. De exemplu,
este posibil să citiți informații dintr-un fișier XML, cum ar fi SuperProProductList.xml și să le utilizați pentru
a crea obiecte de produs pe baza clasei de produse afișate aici:
clasă publică Produs { public int ID {get; set;} șir public Nume {get; set;} zecimal public Preț {get; set;} }

Nimic nu este deosebit de special la această clasă - tot ce face este să vă permită să stocați trei informații conexe
(preț, nume și ID). Rețineți că această clasă utilizează proprietăți automate în locul variabilelor pentru membrii
publici, astfel încât informațiile sale pot fi afișate într-o pagină web cu ASP.NET legătură de date. O aplicație tipică
poate citi date dintr-un fișier XML și le poate plasa direct în obiectele corespunzătoare. Următorul exemplu (de
asemenea, o parte a paginii XmlWriterTest.aspx) arată cum puteți crea cu ușurință un grup de obiecte Produs pe
baza fișierului SuperProProductList.xml. Acest exemplu utilizează colecția generică Listă, deci va trebui să importați
spațiul de nume System.Collections.Generic.
Deschideți un flux în fișier.
fișier șir = Path.Combine(Request.PhysicalApplicationPath,
@"App_Data\SuperProProductList.xml"); FileStream fs = nou
FileStream (fișier, FileMode.Open); XmlTextReader r = nou
XmlTextReader(fs);
Creați o colecție generică de produse.
Listă<Produs> produse = listă nouă<produs>();
Buclă prin produse. în timp ce
(r.Read()) { if (r.NodeType ==
XmlNodeType.Element && r.Name ==

621
CAPITOLUL XML
18

"Product") {

} Produs nouProdus = Produs nou(); newProduct.ID =


Int32.Parse(r.GetAttribute("ID")); newProduct.Name =
r.GetAttribute("Nume");

Obțineți restul subetichetelor pentru acest produs. în


timp ce (r.NodeType != XmlNodeType.EndElement) {

r.citit();

Căutați subetichete de preț.


dacă (r.Name == "Price") { în timp ce (r.NodeType !=
XmlNodeType.EndElement) {

r.citit(); dacă (r.NodeType == XmlNodeType.Text) {


newProduct.Price = Decimal.Parse (r.Value); } } }

Puteți verifica alte noduri de produs // (cum ar fi


Disponibil, Stare etc.) aici. }

Adăugați produsul în listă. Produse.


Adăugare(ProdusNou);

Fs. Aproape();

Afișați documentul preluat.


gridResults.DataSource = produse;
gridResults.DataBind();

Disecarea codului . . .
• Acest cod utilizează o structură de buclă imbricată. Bucla exterioară iterează toate
produsele, iar bucla interioară caută prin toate elementele copil ale <Produsului>. (În acest
exemplu, codul procesează elementul <Price> și ignoră orice altceva.) Structura de buclă
păstrează codul bine organizat.
• Nodul EndElement vă avertizează când un nod este complet și bucla se poate
termina. Odată ce toate informațiile sunt citite pentru un produs, obiectul
corespunzător este adăugat în colecție.
• Toate informațiile sunt preluate din fișierul XML ca șir. Astfel, trebuie să utilizați
metode precum Int32.Parse() pentru a-l converti la tipul de date potrivit.

622
CAPITOLUL XML
18

• Legarea datelor este utilizată pentru a afișa conținutul colecției. Un set GridView
pentru a genera coloane creează automat tabelul prezentat în Figura 18-3.

Figura 18-3. Citirea conținutului XML

Notă
• XmlTextReader oferă mult mai multe proprietăți și metode. Acești membri suplimentari nu
adaugă funcționalități; Acestea permit o flexibilitate sporită. De exemplu, aveți posibilitatea să
citiți o porțiune dintr-un document XML într-un șir utilizând metode precum ReadString(),
ReadInnerXml() și ReadOuterXml(). Toți acești membri sunt documentați în referința bibliotecii
clasei din Ajutorul Visual Studio.

Lucrul cu documente XML în memorie


XmlTextReader și XmlTextWriter utilizează XML ca depozit de suport. Aceste clase sunt simplificate pentru
introducerea și ieșirea rapidă a datelor XML dintr-un fișier (sau din altă sursă). Când utilizați aceste clase,
deschideți fișierul XML, regăsiți datele de care aveți nevoie și utilizați aceste date pentru a crea obiectele
corespunzătoare sau pentru a completa controalele corespunzătoare. Scopul tău este să traduci XML în
ceva mai practic și mai ușor de utilizat. Restul codului nu are cum să știe că datele au fost extrase inițial
dintr-un document XML - și nu-i pasă.

Notă document
• Rețineți că termenii document XML și fișier XML sunt diferiți. Un XML este o colecție de
elemente structurate în conformitate cu regulile XML. Un document XML poate fi stocat în
aproape orice mod doriți - poate fi plasat într-un fișier, într-un câmp sau într-o bază de date
sau poate exista pur și simplu în memorie.

623
CAPITOLUL XML
18

Această abordare este ideală pentru stocarea blocurilor simple de date. De exemplu, puteți modifica pagina
cărții de oaspeți din capitolul anterior pentru a stoca intrările din cartea de oaspeți într-un format XML, ceea
ce ar oferi o standardizare mai mare, dar nu ar schimba modul în care funcționează aplicația. Codul pentru
serializarea și deserializarea datelor XML se va schimba, dar restul aplicației va rămâne neatins. Clasa
XDocument oferă o abordare diferită a datelor XML. Acesta oferă un model în memorie a unui întreg
document XML. Apoi puteți naviga prin întregul document, citind, inserând sau eliminând noduri în orice
locație. (Puteți găsi XDocument și toate clasele asociate în spațiul de nume System.Xml.Linq.)
Când utilizați această abordare, începeți prin încărcarea conținutului XML dintr-un fișier (sau altă sursă)
într-un obiect XDocument. XDocument conține întregul document simultan, deci nu este o abordare practică
dacă conținutul XML are o dimensiune de câțiva megaocteți. (Dacă aveți un document XML imens, clasele
XmlTextReader și XmlTextWriter oferă cea mai bună abordare.) Cu toate acestea, XDocument excelează
cu adevărat cu capacitățile de editare pe care vi le oferă. Folosind obiectul XDocument, puteți manipula
conținutul sau structura oricărei părți a documentului XML. Când ați terminat, puteți salva conținutul înapoi
într-un fișier. Spre deosebire de XmlTextReader și XmlTextWriter, clasa XDocument nu menține o
conexiune directă la fișier.

Notă
XDocument este . Cel mai modern instrument NET pentru manipularea XML în memorie. A fost introdus ca parte a LINQ (o caracteristică despre care veți afla în capitolul 24), în .NET 3.5. Înainte de aceasta, dezvoltatorii .NET au folosit o clasă similară, dar puțin mai ciudată, numită XmlDocument. Această distincție este importantă, deoarece XmlDocument este încă lovit în .NET 4 și există câteva clase și metode vechi care o așteaptă, în loc de XDocument. Cu toate acestea, deoarece XDocument este mai puternic și mai

convenabil, Microsoft vă recomandă să îl faceți instrumentul dvs. pentru o muncă XML serioasă.

Când utilizați clasa XDocument, documentul XML este creat ca o serie de obiecte .NET legate în memorie.
Figura 18-4 prezintă modelul obiectului. (Diagrama este ușor simplificată față de ceea ce veți găsi atunci
când începeți să utilizați clasa XDocument - și anume, nu afișează atributele, fiecare dintre acestea fiind
reprezentat de un obiect XAttribute.)

624
CAPITOLUL XML
18

Figura 18-4. Un document XML în memorie

Pentru a începe să construiți un alt document XML, trebuie să creați obiectele XDocument, XElement și
XAttribute care îl compun. Toate aceste clase au constructori utili care vă permit să le creați și să le
inițializați într-un singur pas. De exemplu, puteți crea un element și puteți furniza conținut text care ar trebui
plasat în interior folosind cod ca acesta:
XElement element = noul XElement("Preț", "23,99");

625
CAPITOLUL XML
18

Acest lucru este deja mai bun decât XmlTextWriter, care vă obligă să începeți un element, să introduceți conținutul
acestuia și să îl închideți cu trei instrucțiuni separate. Dar economiile de cod devin și mai dramatice atunci când luați în
considerare o altă caracteristică a claselor XDocument și XElement - capacitatea lor de a crea un arbore imbricat de
noduri într-o singură instrucțiune de cod.
Iată cum funcționează. Atât clasa XDocument, cât și clasa XElement includ un constructor care ia o
matrice de parametri pentru ultimul argument. Această matrice de parametri conține o listă de noduri
imbricate.

Notă O matrice de parametri este un parametru care este precedat de cuvântul cheie params. Acest parametru este întotdeauna ultimul parametru și este întotdeauna o matrice. Avantajul este că utilizatorii nu trebuie să declare matricea - în schimb, ei pot pur și simplu să abordeze câte argumente doresc, care sunt grupate automat într-o singură matrice.

Iată un exemplu care creează un element cu trei elemente imbricate și conținutul acestora:

XElement element = nou XElement("Produs", nou


XElement("ID", 3), nou XElement("Nume", "Coș cu
fructe proaspete"), noul XElement("Preț", "49,99")
);

Iată resturile XML pe care le creează acest cod:

<Produs>
3 <Nume>Coș cu fructe
proaspete</Nume>
49.99</produs>

Puteți extinde această tehnică pentru a crea un întreg document XML, complet cu elemente, conținut text,
atribute și comentarii. De exemplu, iată codul complet care creează documentul SuperProProductList.xml în
memorie. Când documentul este complet construit, codul îl salvează într-un fișier folosind metoda
XDocument.Save().
Construiți conținutul XML în memorie.
XDocument doc = nou XDocument( nou XDeclaration("1.0",
null, "da"), nou XComment("Creat cu clasa XDocument."),
nou XElement("SuperProProductList", nou
XElement("Produs", nou XAttribute("ID", 1), nou
XAttribute("Nume", "Scaun"), noul XElement("Preț", "49.33"),
noul XElement("Produs", noul XAttribute("ID", 2), noul
XAttribute("Nume", "Mașină"), noul XElement("Preț",
"43399.55"), noul XElement("Produs", nou XAttribute("ID",
3), noul XAttribute("Nume", "Coș cu fructe proaspete"), noul
XElement("Preț", "49,99") ) )

626
CAPITOLUL XML
18

);

Salvați documentul. Doc. Salvare (fișier);

Acest cod creează același conținut XML ca și codul XmlTextWriter pe care l-ați luat în considerare
mai devreme. Cu toate acestea, acest cod este mai scurt și mai ușor de citit.

Disecarea codului . . .
• Fiecare parte separată a documentului XML este creată ca obiect. Elementele
sunt create ca obiecte XElement, comentariile sunt create ca obiecte XComment,
iar atributele sunt reprezentate ca obiecte XAttribute.
• Spre deosebire de codul care utilizează XmlTextWriter, nu este nevoie să
închideți în mod explicit elementele.
• Un alt detaliu frumos este modul în care indentarea instrucțiunilor de cod reflectă
imbricarea elementelor din documentul XML. Dacă un element este urmat de altul și
ambele elemente au aceeași indentare, atunci cele două elemente sunt la același nivel (de
exemplu, un element <Produs> după altul). Dacă un element este urmat de altul și al
doilea element are o indentare mai mare, acesta este plasat în interiorul elementului
precedent (de exemplu, elementul <Preț> din elementul <Produs>). Același lucru este
valabil și pentru alte tipuri de noduri, cum ar fi comentariile și atributele. Această indentare
vă permite să vă uitați la cod și să luați rapid forma generală a documentului XML.

• Una dintre cele mai bune caracteristici ale clasei XDocument este că nu se bazează pe
niciun fișier subiacent. Când utilizați metoda Save(), fișierul este creat, se deschide un
flux, informațiile sunt scrise și fișierul este închis, toate într-o singură linie de cod. Aceasta
înseamnă că aceasta este probabil singura linie pe care trebuie să o puneți în interiorul
unui bloc de manipulare a erorilor de încercare / prindere.
Figura 18-5 prezintă fișierul scris de acest cod (așa cum este afișat de Internet Explorer).

627
CAPITOLUL XML
18
Descărcați de la Wow! eBook <www.wowebook.com>

Figura 18-5. Fișierul XML

Citirea unui document XML


XDocument facilitează citirea și navigarea conținutului XML. Puteți utiliza metoda statică XDocument.Load() pentru
a citi documente XML dintr-un fișier, URI sau flux și puteți utiliza metoda statică XDocument.Parse() pentru a
încărca conținut XML dintr-un șir.
Odată ce aveți XDocument cu conținutul dvs. în memorie, puteți săpa în arborele nodurilor folosind câteva
proprietăți și metode cheie ale clasei XElement și XDocument. Tabelul 18-1 enumeră cele mai utile
metode.
Tabelul 18-1. Metode utile pentru XElement și XDocument

Metodă Descriere

Atribute() Obține colecția de obiecte XAttribute pentru acest element.

Atribut() Primește XAttribute cu numele specific.

Elemente() Obține colecția de obiecte XElement care sunt conținute de acest element. (Acesta
este doar nivelul superior - aceste elemente pot conține, la rândul lor, mai multe
elemente.) Opțional, puteți specifica un nume de element și numai acele elemente
vor fi preluate.

Element() Obține singurul XElement conținut de acest element care are un nume specific (sau
nul dacă nu există nicio potrivire). Dacă există mai multe elemente de potrivire,
această metodă primește doar primul.

628
CAPITOLUL XML
18

Metodă Descriere

Descendenți() Obține colecția de obiecte XElement care sunt conținute de acest element și
(opțional) au numele pe care îl specificați. Spre deosebire de metoda Elements(), aceasta
metoda trece prin toate straturile documentului și găsește elemente la orice
nivelul ierarhiei.

Noduri() Primește toate obiectele XNode conținute de acest element. Aceasta include elemente
și alt conținut, cum ar fi comentariile. Cu toate acestea, spre deosebire de clasa XmlTextReader,
XDocument nu consideră atributele ca fiind noduri.

DescendențiNoduri() Obține tot obiectul XNode conținut de acest element. Această metodă este ca
Descendenți() prin faptul că detaliază toate straturile elementelor imbricate.

Aceste metode vă oferă flexibilitate suplimentară pentru a filtra doar elementele care vă interesează. De
exemplu, atunci când utilizați metoda Elements(), aveți două supraîncărcări din care să alegeți. Puteți obține
toate elementele fiu (caz în care nu ați furniza parametri) sau puteți obține doar acele elemente fiu care au un
anumit nume de element (caz în care ați specifica numele elementului ca șir). De exemplu, iată cum ați obține
elementul rădăcină <SuperProProductList> dintr-un XDocument care conține SuperProProductList.xml
complet:
Utilizați metoda Element(), deoarece există un singur element de potrivire. XElement
superProProductListElement = doc. element("SuperProProductList");

Apoi puteți utiliza acest element ca punct de plecare pentru a săpa mai adânc în document. De exemplu,
dacă doriți să găsiți elementele copil <Product> în <SuperProProductList>, adăugați acest cod:
Utilizați metoda Elements(), deoarece există mai multe elemente care se potrivesc. var
productElements = superProProductListElement.Elements("Produs")

Aici, codul folosește instrucțiunea var pentru a simplifica linia de cod. (Din punct de vedere tehnic, metoda Elements()
returnează o colecție IEnumerorable<XElement>. Acest design oferă XDocument mai multă flexibilitate. Aceasta
înseamnă că metoda Elements() poate returna orice colecție dorește, atâta timp cât colecția acceptă interfața
IEnumerorable<T>.)
Introducerea textului într-un XElement este ușoară. Pur și simplu trebuie să aruncați elementul la tipul de date
corespunzător, așa cum se arată aici:
XElement priceElement = productElement.Element("Preț"); preț zecimal
= (zecimal)priceElement;

Acest lucru funcționează deoarece clasa XElement definește operatori de conversie specializați. Când proiectați un
XElement la o valoare zecimală, de exemplu, XElement își regăsește automat valoarea internă și încearcă să o
convertească în zecimală.
Setarea conținutului textului în interiorul unui element este aproape la fel de ușoară. Pur și simplu atribuiți
noul conținut proprietății Valoare, așa cum se arată aici:
priceElement.Value = (zecimal)priceElement * 2;
Puteți utiliza aceeași abordare pentru a citi și seta atribute cu clasa XAttribute.
Iată o rutină simplă de cod care imită codul de procesare XML pe care l-ați văzut mai devreme cu
XmlTextReader. Scanează elementele disponibile, creează o listă de produse și o afișează într-o grilă.

629
CAPITOLUL XML
18

Încărcați documentul.
XDocument doc = XDocument.Load(fișier);

Treceți prin toate nodurile și creați lista de obiecte Produs . Listă<Produs> produse
= listă nouă<produs>();

foreach (element XElement în doc. Element("SuperProProductList"). Elemente("Produs"))


{
Produs nouProdus = Produs nou(); newProduct.ID =
(int)element. Atribut("ID"); newProduct.Name = (șir)element.
Atribut("Nume");
newProduct.Price = element (zecimal). element("preț");

Produse. Adăugare(ProdusNou);
}

Afișați rezultatele.
gridResults.DataSource = produse; gridResults.DataBind();

Clasa XElement oferă destul de mulți membri. De exemplu, veți găsi membri pentru trecerea rapidă de la un nod
la altul (FirstNode, LastNode, NextNode, PreviousNode și Parent), proprietăți pentru testarea prezenței copiilor
(HasElements), atribute (HasAttributes), conținut (IsEmpty) și metode pentru inserarea, eliminarea și manipularea
în alt mod a arborelui XML al nodurilor. De exemplu, utilizați Add() pentru a plasa un nou element fiu în interiorul
elementului curent (după orice conținut existent); utilizați AddFirst() pentru a plasa un nou element fiu în interiorul
elementului curent (înaintea oricărui conținut existent); utilizați AddAfterSelf() pentru a insera un element la
același nivel imediat după elementul curent; utilizați AddBeforeSelf() pentru a insera un element la același nivel
chiar înaintea elementului curent; și așa mai departe. De asemenea, puteți utiliza Remove(), RemoveNodes(),
ReplaceWith() și ReplaceNodes() pentru a elimina sau înlocui elemente și alte noduri.
Următorul exemplu arată cum puteți adăuga un produs nou la XDocument:

Creați elementul pentru noul produs. XElement


newProduct = noul XElement ("Produs", noul
XAttribute("ID", 4), noul XAttribute("Nume", "Lanterna
magică"), noul XElement("Preț", "76,95")
);

Adăugați elementul la sfârșitul listei curente de produse. Doc.


Element("SuperProProductList"). Adăugare(ProdusNou);

Sfat: Dacă utilizați clasa XDocument sau XmlTextReader depinde de o serie de factori. În general, utilizați XDocument atunci când doriți să vă ocupați direct de XML, mai degrabă decât să utilizați XML ca o modalitate de a persista unele informații. De asemenea, vă oferă posibilitatea de a modifica structura unui document XML și vă permite să răsfoiți informațiile XML într-un mod mai flexibil (nu doar de la început până la sfârșit). Pe de altă parte, XmlTextReader este cel mai bun atunci când aveți de-a face cu fișiere

XML mari, deoarece nu va încerca să încarce întregul document în memorie simultan.

630
CAPITOLUL XML
18

Căutarea într-un document XML


Una dintre cele mai frumoase caracteristici ale XDocument este suportul său de căutare, care vă permite să găsiți
noduri atunci când știți că sunt acolo - undeva - dar nu sunteți sigur câte potriviri există sau unde sunt elementele.

Pentru a căuta un XDocument, tot ce trebuie să faceți este să utilizați metoda Descendenți() sau
Descendenți(). Ambele metode vă permit să căutați prin întregul arbore de documente într-un singur pas.
De exemplu, iată cum puteți utiliza Descendenți() pe întregul document SuperProProductList.xml pentru a
obține o listă de prețuri:
XDocument doc = XDocument.Load(fișier);

Găsiți potrivirile.
rezultate var = doc. descendenți("Preț");

Afișați rezultatele.
lblXml.Text = "<b>Found " + rezultate. Count<XElement>(). ToString() + " Meciuri "; lblXml.Text
+= " pentru eticheta de preț: </b><br /><br />"; pentru fiecare (rezultatul XElement în rezultate)

{
lblXml.Text += rezultat. Valoare + "<br />";
}

Figura 18-6 prezintă rezultatul.

Figura 18-6. Căutarea într-un document XML

Metoda Descendenți() este excelentă dacă doriți să găsiți un element pe baza numelui său. Dacă doriți să
utilizați o căutare mai sofisticată, să potriviți doar o parte a unui nume sau să examinați doar o parte a unui
document, aveți două opțiuni. În primul rând, puteți scrie cod care trece prin toate nodurile din XDocument și
verifică fiecare. În al doilea rând, puteți utiliza caracteristica LINQ la XML pentru a efectua o interogare care
extrage obiecte XElement potrivite din XDocument. Aceasta este o potrivire naturală, deoarece clasa
XDocument a fost introdusă inițial ca parte a caracteristicii LINQ în .NET 3.5. Veți afla mult mai multe despre
LINQ, inclusiv cum să îl utilizați pentru a efectua căutări, în capitolul 24.

631
CAPITOLUL 18 XML

Validare XML
XML are un set bogat de standarde suport, dintre care multe depășesc cu mult scopul acestei cărți. Una dintre
cele mai utile din această familie de standarde este schema XML. XML Schema definește regulile la care trebuie
să se conformeze un anumit document XML, cum ar fi elementele și atributele permise, ordinea elementelor și
tipul de date al fiecărui element. Definiți aceste cerințe într-un document schemă XML (XSD).

Când creați un fișier XML pe cont propriu, nu trebuie să creați un fișier XSD corespunzător - în schimb, vă
puteți baza doar pe capacitatea codului dvs. de a se comporta corect. Deși acest lucru este suficient pentru
medii strict controlate, dacă doriți să deschideți aplicația altor programatori sau să îi permiteți să
interopereze cu alte aplicații, ar trebui să creați un XSD. Gândiți-vă astfel: XML vă permite să creați un
limbaj personalizat pentru stocarea datelor, iar XSD vă permite să definiți sintaxa limbajului pe care îl creați.

Spații de nume XML


Înainte de a putea crea un XSD, va trebui să înțelegeți un alt standard XML, numit spații de nume XML.

Ideea de bază din spatele spațiilor de nume XML este că fiecare limbaj de marcare XML are propriul spațiu de
nume, care este utilizat pentru a identifica în mod unic toate elementele conexe. Din punct de vedere tehnic, spațiile
de nume dezambiguizează elementele, clarificând din ce limbaj de marcare aparțin. De exemplu, puteți face
diferența între standardul SuperProProductList și catalogul de produse al altei organizații, deoarece cele două
limbaje XML ar utiliza spații de nume diferite.
Spațiile de nume sunt deosebit de utile în documentele compuse, care conțin secțiuni separate, fiecare cu
un tip diferit de XML. În acest scenariu, spațiile de nume asigură că un element dintr-un spațiu de nume nu
poate fi confundat cu un element din alt spațiu de nume, chiar dacă are același nume de element. Spațiile
de nume sunt, de asemenea, utile pentru aplicațiile care acceptă diferite tipuri de documente XML. Prin
examinarea spațiului de nume, codul dvs. poate determina cu ce tip de document XML lucrează și apoi îl
poate procesa în consecință.

Notă Spațiile
• limbi. Spațiilede
denume
numeNET
XMLsunt
nu sunt legate dede
o construcție spațiile de nume
cod utilizată .NET.
pentru Spațiile detipurilor.
organizarea nume XML
identifică XML diferite

Înainte de a putea plasa elementele XML într-un spațiu de nume, trebuie să alegeți un nume de identificare pentru acel spațiu
de nume. Majoritatea spațiilor de nume XML utilizează identificatori universali de resurse (URI). De obicei, aceste URI-uri
arată ca o adresă URL a unei pagini web. De exemplu, http://www.mycompany.com/mystandard este un nume tipic pentru un
spațiu de nume. Deși spațiul de nume pare să indice o locație validă pe web, acest lucru nu este necesar (și nu ar trebui
presupus).
Motivul pentru care URI-urile sunt utilizate pentru spațiile de nume XML se datorează faptului că este mai
probabil ca acestea să fie unice. De obicei, dacă creați un marcaj XML nou, veți utiliza un URI care indică
spre un domeniu sau un site web pe care îl controlați. În acest fel, puteți fi sigur că nimeni altcineva nu este
probabil să folosească acel URI. De exemplu, http://www.SuperProProducts.com/SuperProProductList
spațiului de nume este mult mai probabil să fie unic decât doar SuperProProductList dacă dețineți
domeniul www.SuperProProducts.com.

632
CAPITOLUL 18
XML

Sfat: Numele spațiului de nume trebuie să se potrivească exact. Dacă modificați scrierea cu majuscule într-o parte a unui spațiu de

nume, adăugați un caracter final sau modificați orice alt detaliu, acesta va fi interpretat ca un spațiu de nume diferit de parserul XML.

Pentru a specifica faptul că un element aparține unui anumit spațiu de nume, trebuie doar să adăugați atributul xmlns
la eticheta de pornire și să indicați spațiul de nume. De exemplu, elementul <Preț> afișat aici face parte din spațiul de
nume http://www.SuperProProducts.com/SuperProProductList:

<Price xmlns="http://www.SuperProProducts.com/SuperProProductList">
49.33 </pret>

Dacă nu faceți acest pas, elementul nu va face parte din niciun spațiu de nume.
Ar fi greoi dacă ar trebui să tastați URI-ul complet al spațiului de nume de fiecare dată când scrieți un element într-un
document XML. Din fericire, atunci când atribuiți un spațiu de nume în acest mod, acesta devine spațiu de nume
cel
implicit pentru toate elementele fiu. De exemplu, în documentul XML prezentat aici, elementul <SuperProProductList>
și toate elementele pe care le conține sunt plasate în
http://www.SuperProProducts.com/SuperProProductList spațiului de nume:

<?xml version="1.0"?> <SuperProProductList

xmlns="http://www.SuperProProducts.com/SuperProProductList">
<Produs>
<ID>1</ID>
<Nume>Președinte</Nume>
<Pret>49.33</Pret>
<Disponibil>True</Disponibil>
<Stare>3</Stare>
</Produs>

<!-- Alte produse omise. --> </SuperProProductList>

În documentele compuse, veți avea marcaje din mai multe limbi XML și va trebui să plasați secțiuni diferite în spații de
nume diferite. În această situație, puteți utiliza prefixele spațiului de nume pentru a sorta diferitele spații de nume.

Prefixele spațiului de nume sunt secvențe scurte de caractere pe care le puteți insera în fața unui nume de
etichetă pentru a indica spațiul său de nume. Definiți prefixul în atributul xmlns inserând două puncte (:)
urmat de caracterele pe care doriți să le utilizați pentru prefix. Iată documentul SuperProProductList rescris
pentru a utiliza prefixul super:
<?xml version="1.0"?>
<super:SuperProProductList
>
xmlns:super="http://www.SuperProProducts.com/SuperProProductList" < produs>
super: <super:ID>1</ super:ID>
< Nume>Scaun</ Nume> super: super:< Pret>49.33 </
Pret> super: super: < Disponibil>True</ Disponibil>

super : super:<super:Stare>3</
super:Stare>
CAPITOLUL 18 XML

</super: Produs>

<!-- Alte produse omise. --> </


SuperProProductList>
Super:
Prefixele spațiului de nume sunt pur și simplu folosite pentru a mapa un element la un spațiu de nume. Prefixul real pe
care îl utilizați nu este important atât timp cât rămâne consecvent în întregul document. Prin convenție, atributele care
definesc prefixele spațiului de nume XML sunt de obicei adăugate la elementul rădăcină al unui document XML.

Deși atributul xmlns arată ca un atribut XML obișnuit, nu este. Parserul XML îl interpretează ca o declarație
de spațiu de nume. (Motivul pentru care spațiile de nume XML utilizează atribute XML este unul istoric.
Acest design a asigurat că analizoarele XML vechi care nu înțelegeau spațiile de nume puteau citi în
continuare documente XML mai noi care le foloseau.)

Notă: Atributele acționează puțin diferit față de elemente atunci când vine vorba de spații de nume. Puteți utiliza spațiul de nume

prefixe atât cu elemente, cât și cu atribute. Cu toate acestea, atributele nu acordă nicio atenție
spațiului de nume implicit al unui document. Aceasta înseamnă că dacă nu adăugați un prefix de
spațiu de nume la un atribut, atributul nu va fi plasat în spațiul de nume implicit. În schimb, nu va
avea spațiu de nume.

Scrierea conținutului XML cu spații de nume


Puteți utiliza clasele XmlTextWriter și XDocument despre care ați învățat deja pentru a crea conținut XML care
utilizează un spațiu de nume.
XmlTextWriter include o versiune supraîncărcată a metodei WriteStartElement() care acceptă un URI
de spațiu de nume. Iată cum funcționează:

șir ns = "http://www.SuperProProducts.com/SuperProProductList";
w.WriteStartDocument();
w.WriteStartElement("SuperProProductList", ns);

" Scrie primul produs.


w.WriteStartElement("Produs" , )
ns ...

Singurul truc este să vă amintiți să utilizați spațiul de nume pentru fiecare element.
Clasa XDocument se ocupă de spațiile de nume folosind o abordare similară. Mai întâi, definiți un obiect
XNamespace. Apoi, adăugați acest obiect XNamespace la începutul numelui elementului de fiecare dată
când creați un XElement (sau un XAttribute) pe care doriți să îl plasați în acel spațiu de nume. Iată un
exemplu:
XNamespace ns = "http://www.SuperProProducts.com/SuperProProductList";
XDocument doc = noul XDocument( noul XDeclaration("1.0", null, "da"), noul
XComment("Creat cu clasa XDocument."), noul XElement(ns + "
SuperProProductList", noul XElement( "Product ", ns + noul XAttribute(" ID", 1),
noul XAttribute("Nume", "Președinte"),

634
CAPITOLUL 18
XML

noul XElement(ns "Price", "49.33")


+ ), ...

De asemenea, poate fi necesar să modificați codul de citire XML. Dacă utilizați XmlTextReader simplu,
viața este simplă, iar codul dvs. va funcționa fără modificări. Dacă este necesar, puteți utiliza proprietatea
XmlTextReader.NamespaceURI pentru a obține spațiul de nume al elementului curent (ceea ce este
important dacă aveți un document compus care îmbină elemente din diferite spații de nume). Dacă utilizați
clasa XDocument, trebuie să luați în considerare spațiul de nume XML atunci când căutați documentul. De
exemplu, atunci când utilizați metoda XmlElement.Element(), trebuie să furnizați numele complet calificat al
elementului adăugând obiectul XNamespace corespunzător la șir cu numele elementului:

XNamespace ns = "http://www.SuperProProducts.com/SuperProProductList"; ...

XElement superProProductListElement = doc. Element( "SuperProProductList");


ns +

Notă Din punct de vedere tehnic, nu este nevoie să utilizați clasa XNamespace, deși vă face codul mai clar. Când

adăugați XNamespace la un șir de nume de element, spațiul de nume este pur și simplu înfășurat în
acolade ondulate. Cu alte cuvinte, atunci când combinați http://www.somecompany.com/DVDList
spațiului de nume cu elementul nume Titlu, este echivalent cu șirul
{http://www.somecompany.com/DVDList}Titlu. Această sintaxă funcționează deoarece caracterele acolade
curbate nu sunt permise în numele elementelor obișnuite, deci nu există nicio posibilitate de confuzie.

Definiție schemă XML


Un XSD, sau schemă, definește ce elemente și atribute ar trebui să conțină un document și modul în care aceste
noduri sunt organizate (structura). De asemenea, poate identifica tipurile de date adecvate pentru tot conținutul.
Documentele schemă sunt scrise folosind o sintaxă XML cu nume specifice de elemente. Toate elementele XSD sunt
plasate în spațiul de nume http://www.w3.org/2001/XMLSchema. Adesea, acest spațiu de nume folosește prefixul xsd:
sau xs:, ca în exemplul următor.
Specificația completă XSD este în afara domeniului de aplicare al acestui capitol, dar puteți învăța multe
dintr-un exemplu simplu. Următorul este un fișier SuperProProductList.xsd ușor abreviat care definește
regulile pentru documentele SuperProProductList:
<?xml version="1.0"?> <xs:schema
targetNamespace="http://www.SuperProProducts.com/SuperProProductList"
xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" > <xs:element
name="SuperProProductList"> <xs:complexType> <xs:sequence maxHappens="unbounded"> <xs:element
name="Product"> <xs:complexType> <xs:sequence> <xs:element name="Price" type="xs:double" /> </xs:
sequence> <xs:attribute name="ID" use="required" type="xs:int" />
CAPITOLUL 18 XML

<xs:attribute name="Name" use="required" type="xs:string" /> </xs:complexType>


</xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>

La prima vedere, acest marcaj pare un pic intimidant. Cu toate acestea, nu este de fapt atât de complicat
pe cât pare. În esență, această schemă indică faptul că un document SuperProProductList constă dintr-o
listă de elemente <Product>. Fiecare element <Product> este un tip complex format dintr-un șir (Nume), o
valoare zecimală (Price) și un întreg (ID). Acest exemplu utilizează a doua versiune a documentului
SuperProProductList pentru a demonstra cum se utilizează atributele într-un fișier schemă.

Disecarea codului . . .
Examinând schema SuperProProductList.xsd, puteți afla câteva puncte importante:
• Documentele schemă utilizează propria formă de marcare XML. În exemplul anterior,
veți vedea rapid că toate elementele sunt plasate în
http://www.w3.org/2001/XMLSchema spațiul de nume utilizând prefixul spațiului de nume xs.
• Fiecare document schemă începe cu un element rădăcină <schemă>.
• Documentul schemă trebuie să specifice spațiul de nume al documentelor pe
care le poate valida. Specifică acest detaliu cu atributul targetNamespace pe
elementul rădăcină <schema>.
• Elementele din interiorul elementului <schema> descriu structura documentului țintă.
Elementul <element> reprezintă un element, în timp ce elementul <atribut> reprezintă un
atribut. Pentru a afla care este numele unui element sau atribut, uitați-vă la atributul name.
De exemplu, puteți spune destul de ușor că primul <element> are numele
SuperProProductList. Acest lucru indică faptul că primul element din documentul validat
trebuie să fie <SuperProProductList>.
• Dacă un element poate conține alte elemente sau are atribute, este considerat un tip
complex. Tipurile complexe sunt reprezentate într-o schemă de elementul
<complexType>. Cel mai simplu tip complex este o secvență, care este reprezentată
într-o schemă de elementul <secvență >. Este necesar ca elementele să fie întotdeauna în
aceeași ordine - ordinea setată în documentul schemă.
• La definirea elementelor, puteți defini numărul maxim de apariții ale unui element
(folosind atributul maxHappens) și numărul minim de apariții (folosind atributul
minHappens ). Dacă omiteți aceste detalii, valoarea implicită a ambelor este 1, ceea
ce înseamnă că fiecare element trebuie să apară exact o dată în documentul țintă.
Utilizați o valoare maxComes nelimitată dacă doriți să permiteți o listă nelimitată. De
exemplu, acest lucru permite să existe un număr nelimitat de elemente <Produs> în
catalogul SuperProProductList. Cu toate acestea, elementul <Preț> trebuie să apară
exact o dată în fiecare <Produs>
• La definirea unui atribut, puteți utiliza atributul use with a value of required pentru ca
atributul respectiv să devină obligatoriu.

636
CAPITOLUL 18
XML

• La definirea elementelor și atributelor, puteți specifica tipul de date utilizând


atributul type. Standardul XSD definește 44 de tipuri de date care se mapează
îndeaproape la tipurile de date de bază din .NET, inclusiv tipurile de date duble, int
și șir utilizate în acest exemplu.

Validarea unui document XML


Următorul exemplu vă arată cum să validați un document XML în raport cu o schemă, utilizând un
XmlReader care are încorporate caracteristici de validare.
Primul pas la efectuarea validării este importul spațiului de nume System.Xml.Schema, care conține tipuri
precum XmlSchema și XmlSchemaCollection:
folosind System.Xml.Schema;
Trebuie să efectuați doi pași pentru a crea cititorul de validare. Mai întâi, creați un obiect
XmlReaderSettings care indică în mod specific că doriți să efectuați validarea. Faceți acest lucru setând
proprietatea ValidationType și încărcând fișierul schemă XSD în colecția Schemas, așa cum se arată aici:

Configurați cititorul pentru a utiliza validarea.


Setări XmlReaderSettings = nou XmlReaderSettings(); Setări.
ValidationType = ValidationType.Schema;

Creați calea pentru fișierul schemă.


șir schemaFile = Path.Combine(Request.PhysicalApplicationPath,
@"App_Data\SuperProProductList.xsd");
Indicați că elementele din spațiul de nume //
http://www.SuperProProducts.com/SuperProProductList trebuie validate utilizând
fișierul schemă.
Setări. Schemas.Add("http://www.SuperProProducts.com/SuperProProductList", schemaFile);

În al doilea rând, trebuie să creați cititorul de validare utilizând metoda statică XmlReader.Create ().
Această metodă are mai multe supraîncărcări, dar versiunea utilizată aici necesită un FileStream (cu
documentul XML) și obiectul XmlReaderSettings care are setările de validare:
Deschideți fișierul XML.
FileStream fs = nou FileStream (fișier, FileMode.Open);

Creați cititorul de validare.


XmlReader r = XmlReader.Create (fs, setări);

XmlReader din acest exemplu funcționează în același mod ca XmlTextReader pe care l-ați utilizat până
acum, dar adaugă capacitatea de a verifica dacă documentul XML respectă regulile schemei. Acest cititor
lansează o excepție (sau ridică un eveniment) pentru a indica erorile pe măsură ce vă deplasați prin fișierul
XML. Următorul exemplu arată cum puteți crea un cititor de validare care utilizează fișierul
SuperProProductList.xsd pentru a verifica dacă XML în SuperProProductList.xml este valid:
Setați setările de validare.
Setări XmlReaderSettings = nou XmlReaderSettings(); Setări.
Schemas.Add("http://www.SuperProProducts.com/SuperProProductList", schemaFile);
CAPITOLUL XML
18

Setări. ValidationType = ValidationType.Schema;

Deschideți fișierul XML.


FileStream fs = nou FileStream (fișier, FileMode.Open);

Creați cititorul de validare.


XmlReader r = XmlReader.Create (fs, setări);

Citiți documentul.
în timp ce (r.Read())
{
Procesați documentul aici.
Dacă se găsește o eroare, se va arunca o excepție.
}
Fs. Aproape();

Folosind fișierul curent, acest cod va reuși și veți putea accesa fiecare nod din document. Cu toate
acestea, luați în considerare ce se întâmplă dacă faceți modificarea minoră prezentată aici:
<ID produs = "A" nume = "Scaun">
Descărcați de la Wow! eBook <www.wowebook.com>

Acum, când încercați să validați documentul, va fi aruncat un XmlSchemaException (din spațiul de


nume System.Xml.Schema), avertizându-vă cu privire la tipul de date nevalid, așa cum se arată în
Figura 18-7.

Figura 18-7. O excepție XmlSchemaException

638
CAPITOLUL XML
18

În loc să prindeți erori, puteți reacționa la evenimentul XmlReaderSettings.ValidationEventHandler. Dacă


reacționați la acest eveniment, vi se vor furniza informații despre eroare, dar nu va fi aruncată nicio excepție.
Pentru a conecta o rutină de tratare a evenimentelor la acest eveniment, aveți posibilitatea să atașați o rutină
de tratare a evenimentelor înainte de a crea XmlReader:
Conectați-vă la metoda numită ValidateHandler.
Setări. ValidationEventHandler += nou ValidationEventHandler(ValidateHandler);

Rutina de tratare a evenimentelor primește un obiect ValidationEventArgs ca parametru, care conține


excepția, un mesaj și un număr reprezentând severitatea:

public void ValidateHandler(Object sender, ValidationEventArgs e)


{
lblStatus.Text += "Eroare: " + e.Message + "<br>";
}

Pentru a testa validarea, puteți utiliza pagina XmlValidation.aspx din probele online. Vă permite să validați o
listă SuperProProductList validă, precum și alte două versiuni, una cu date incorecte și una cu un element
incorect (a se vedea figura 18-8).

Figura 18-8. Pagina testului de validare

Sfat Deoarece toate obiectele XmlReader procesează XML câte o linie odată, această abordare de validare funcționează cel mai bine și utilizează cea mai mică cantitate de memorie. Dar dacă aveți deja un XDocument în memorie, îl puteți valida într-un mod similar folosind metoda XDocument.Validate().

639
CAPITOLUL 18 XML

Afișare și transformare XML


Un alt standard asociat cu XML este XSL Transformations (XSLT). XSLT vă permite să creați foi de stil
care pot extrage o porțiune dintr-un document XML mare sau pot transforma un document XML într-un alt
tip de document XML. O utilizare și mai populară a XSLT este de a converti un document XML într-un
document HTML care poate fi afișat într-un browser.

• Notă eXtensible Stylesheet Language (XSL) este o familie de standarde pentru căutarea,
formatarea și transformarea documentelor XML. XSLT este standardul specific care se ocupă de
etapa de transformare.

XSLT este ușor de utilizat din punctul de vedere al bibliotecii de clase .NET. Tot ce trebuie să înțelegeți
este cum să creați un obiect XslCompiledTransform (găsit în spațiul de nume System.Xml.Xsl). Utilizați
metoda Load() pentru a specifica o foaie de stil și metoda Transform() pentru a afișa rezultatul într-un fișier
sau flux:
Definiți căile de fișier pe care le utilizează acest cod. Fișierul XSLT și fișierul
sursă // XML există deja, dar fișierul rezultat XML // va fi creat de acest cod.

șir xsltFile = Path.Combine(Request.PhysicalApplicationPath,


@"App_Data\SuperProProductList.xsl"); șir xmlSourceFile =
Path.Combine(Request.PhysicalApplicationPath,
@"App_Data\SuperProProductList.xml"); șir xmlResultFile =
Path.Combine(Request.PhysicalApplicationPath,
@"App_Data\TransformedFile.xml");
Încărcați foaia de stil XSLT.
XslCompiledTransform transformator = nou XslCompiledTransform();
transformator. Încărcare(xsltFile);

Creați un fișier XML transformat.


SuperProProductList.xml este punctul de plecare.
transformator. Transform(xmlSourceFile, xmlResultFile);
Cu toate acestea, acest lucru nu vă scutește de necesitatea de a învăța sintaxa XSLT. Încă o dată,
complexitatea XSLT nu este direct legată de programarea ASP.NET de bază, deci sunt în afara domeniului
de aplicare al acestei cărți. Cu toate acestea, pentru a începe cu XSLT, vă ajută să revizuiți un exemplu
simplu de foaie de stil. Următorul exemplu afișează o foaie de stil XSLT care transformă versiunea fără
spațiu de nume a documentului SuperProProductList într-un tabel HTML formatat:
<?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" >

<xsl:template match="SuperProProductList">
<html> <body> <table border="1">

640
CAPITOLUL XML
18

<xsl:apply-templates select="Product"/> </table>


</body> </html> </xsl:template>

<xsl:template match="Product"> <tr>


<td><xsl:value-of select="@ID"/></td>
<td><xsl:value-of select="@Name"/></td>
<td><xsl:value-of select="Price"/></td> </tr>
</xsl:template>

</xsl:foaie de stil>

Fiecare document XSLT are un element rădăcină xsl:stylesheet. Foaia de stil poate conține unul sau mai
multe șabloane (fișierul eșantion SuperProProductList.xslt are două). În acest exemplu, primul șablon caută
elementul rădăcină SuperProProductList. Când îl găsește, scoate etichetele necesare pentru a începe un
tabel HTML și apoi folosește comanda xsl: apply-templates pentru a se ramifica și a efectua procesarea
pentru orice elemente de produs conținute.
<xsl:template match="SuperProProductList"> <html>
<body> <table border="1"> <xsl:apply-templates
select="Product"/>

Când acest proces este finalizat, etichetele HTML pentru sfârșitul tabelului vor fi scrise:

</table> </body>
</html>
</xsl:template>

La procesarea fiecărui element <Product>, valoarea din atributul ID imbricat, atributul Name și elementul
<Price> este extrasă și scrisă la ieșire utilizând comanda xsl:value-of. Semnul at (@) indică faptul că
valoarea este extrasă dintr-un atribut, nu dintr-un subelement. Fiecare informație este scrisă într-un rând de
tabel.
<xsl:template match="Product"> <tr>
<td><xsl:value-of select="@ID"/></td>
<td><xsl:value-of select="@Name"/></td>
<td><xsl:value-of select="Price"/></td> </tr>
</xsl:template>

Pentru formatare mai avansată, puteți utiliza elemente HTML suplimentare pentru a formata un text cu
caractere aldine sau cursive.
Rezultatul final al acestui proces este fișierul HTML prezentat aici:

641
CAPITOLUL XML
18

<html> <body> <table


border="1"> <tr>

<td>1</td>
<td>președinte</td>
<td>49.33</td>
</tr> <tr>

<td>2</td>
<td>Car</td>
<td>43398.55</td>
</tr> <tr>
<td>3</td> <td>Coș cu fructe
proaspete</td>
<td>49.99</td> </tr>
</table> </body> </html>

În secțiunea următoare, veți analiza modul în care această ieșire apare într-un browser de internet.
În general, dacă nu sunteți sigur că aveți nevoie de XSLT, probabil că nu. .NET Framework oferă un set
bogat de instrumente pentru căutarea și manipularea fișierelor XML folosind obiecte și cod, care este cea
mai bună abordare pentru utilizarea XML la scară mică.

Sfat
Pentru a afla mai multe despre XSLT, luați în considerare excelenta carte a lui Jeni Tennison Beginning XSLT 2.0: From Novice to Professional (Apress, 2005).

Controlul web XML


Dacă utilizați o foaie de stil XLST, cum ar fi cea demonstrată în exemplul anterior, este posibil să vă întrebați
ce ar trebui să facă codul cu codul HTML generat. Puteți încerca să-l scrieți direct în browser sau să-l salvați
pe hard disk, dar aceste abordări sunt incomode, mai ales dacă doriți să afișați codul HTML generat într-o
pagină web normală ASP.NET care conține alte controale. Obiectul XslCompiledTransform doar convertește
fișiere XML - nu oferă nicio modalitate de a insera ieșirea în pagina dvs.
ASP.NET include un control web XML care umple golul și poate afișa conținut XML. Aveți posibilitatea să
specificați conținutul XML pentru acest control în mai multe moduri. De exemplu, puteți atribui un șir care
conține conținutul XML proprietății DocumentContent sau puteți specifica un șir care face referire la un fișier
XML utilizând proprietatea DocumentSource.
Afișați informațiile dintr-un fișier XML în controlul Xml.
XmlProducts.DocumentSource = Path.Combine(Request.PhysicalApplicationPath,
@"App_Data\SuperProProductList.xml");

642
CAPITOLUL XML
18

Dacă atribuiți fișierul SuperProProductList.xml controlului Xml, este posibil să fiți dezamăgiți. Rezultatul este
doar un șir de text interior (prețul fiecărui produs), îngrămădite împreună fără spațiu (a se vedea figura 18-9).

Figura 18-9. Conținut XML neformatat


Cu toate acestea, puteți aplica, de asemenea, o foaie de stil XSLT, fie atribuind un obiect
XslCompiledTransform proprietății Transformare, fie utilizând un șir care se referă la fișierul XSLT cu
proprietatea TransformSource:
Specificați un fișier XSLT.
XmlProducts.TransformSource = Path.Combine(Request.PhysicalApplicationPath,
@"App_Data\SuperProProductList.xsl");
Acum rezultatul este formatat automat în funcție de foaia de stil (a se vedea figura 18-10).

Figura 18-10. Conținut XML transformat

Ultimul cuvânt
Acum că turul XML și ASP.NET se apropie de sfârșit, ar trebui să aveți o înțelegere de bază a ceea ce este
XML, cum arată și de ce l-ați putea folosi într-o pagină web. XML reprezintă un nou instrument pentru
spargerea barierelor dintre companii și platforme - nu este altceva decât un model universal pentru stocarea
și comunicarea tuturor tipurilor de informații.

643
CAPITOLUL 18 XML

XML în sine este o inovație remarcabilă. Cu toate acestea, pentru a profita la maximum de XML, trebuie
să îmbrățișați alte standarde care vă permit să validați XML, să îl transformați și să îl căutați pentru
informații specifice. .NET Framework furnizează clase pentru toate aceste activități în spațiile de nume
sub ramura System.Xml. Pentru a continua explorarea, începeți cu o revizuire cuprinzătoare a
standardelor XML (cum ar fi cea furnizată la http://www.w3schools.com/xml) și apoi scufundați-vă în
biblioteca clasei.

644
PART5

• ■■

Securitatea site-ului web


C A P I T O L U L 19

• ■■

Noțiuni fundamentale de securitate

În mod obișnuit, site-ul dvs. ASP.NET este disponibil pentru oricine se conectează la serverul dvs. web, fie printr-o rețea
locală, fie prin Internet. Deși acest lucru este ideal pentru multe aplicații web (și se potrivește spiritului original al
internetului), nu este întotdeauna o alegere de design adecvată. De exemplu, un site de comerț electronic trebuie să
ofere o experiență de cumpărături sigură pentru a atrage clienți. Un site bazat pe abonament trebuie să limiteze
conținutul pentru a extrage o taxă. Și chiar și un site public larg deschis poate oferi unele resurse sau caracteristici care
nu ar trebui să fie disponibile pentru toți utilizatorii.
ASP.NET oferă un model de securitate extins care facilitează protejarea aplicațiilor web. Deși acest model de securitate
este puternic și profund flexibil, poate părea confuz din cauza numeroaselor straturi diferite pe care le include. O mare
parte din munca de securizare a aplicației dvs. nu este scrierea codului, ci determinarea locurilor potrivite pentru
implementarea strategiei de securitate.
În acest capitol, veți rezolva modelul de securitate ASP.NET încâlcit. Veți învăța două modalități de
autentificare a utilizatorilor: mai întâi, utilizând autentificarea formularelor (care este ideală pentru un site
web public care utilizează o bază de date particularizată) și apoi utilizând autentificarea Windows (care este
ideală pentru o aplicație intranet dintr-o rețea de firmă).

Înțelegerea securității
Primul pas în securizarea aplicațiilor dvs. este să decideți unde aveți nevoie de securitate și ce trebuie să protejeze. De
exemplu, poate fi necesar să blocați accesul pentru a proteja informațiile private. Sau, poate că trebuie doar să impuneți
un sistem de plată pentru conținut. Poate că nu aveți nevoie deloc de securitate, dar doriți o funcție opțională de
conectare pentru a oferi personalizare vizitatorilor frecvenți. Aceste cerințe vor determina abordarea pe care o utilizați.

Securitatea nu trebuie să fie complexă, dar trebuie să fie amplă și multistratificată. De exemplu, luați în considerare un
site de comerț electronic care permite utilizatorilor să vizualizeze rapoarte despre comenzile plasate recent. Probabil că
știți deja prima linie de apărare pe care acest site web ar trebui să o utilizeze - o pagină de conectare care forțează
utilizatorii să se identifice înainte de a putea vedea informații personale. În acest capitol, veți învăța cum să utilizați acest
tip de sistem de autentificare. Cu toate acestea, este important să vă dați seama că, pe cont propriu, acest strat de
protecție nu este suficient pentru a vă asigura cu adevărat sistemul. De asemenea, trebuie să protejați baza de date
back-end cu o parolă puternică și puteți alege chiar să criptați informațiile sensibile înainte de a le stoca (amestecând
astfel încât să nu poată fi citite fără cheia potrivită pentru a le decripta). Pași ca aceștia vă protejează site-ul web de alte
atacuri care depășesc sistemul de autentificare. De exemplu, acestea pot descuraja un angajat nemulțumit cu un cont în
rețeaua locală, un hacker care a obținut acces la rețeaua dvs. prin firewall-ul companiei sau un tehnician neglijent care
aruncă un hard disk utilizat pentru stocarea datelor fără a-l șterge mai întâi.

În plus, va trebui să vânați cu atenție punctele slabe din codul pe care l-ați scris. Un număr surprinzător de
site-uri web cad pradă unor atacuri relativ simple în care un utilizator rău intenționat pur și simplu
manipulează cu un argument șir de interogare sau un pic de HTML în pagina redată. În exemplul de comerț
electronic, trebuie să

647
CAPITOLUL 19 FUNDAMENTE DE SECURITATE

Asigurați-vă că un utilizator care se conectează cu succes nu poate vedea comenzile recente ale altui utilizator.
Imaginați-vă că ați creat o pagină ViewOrders.aspx care preia un argument șir de interogare numit userID, astfel:

http://localhost/InsecureStore/ViewOrders.aspx?userID=4191
Acest exemplu este un coșmar de securitate, deoarece orice utilizator poate modifica cu ușurință
parametrul userID editând adresa URL pentru a vedea informațiile altui utilizator. O soluție mai bună ar fi să
proiectați pagina ViewOrders.aspx astfel încât să obțină ID-ul de utilizator din identitatea de utilizator
conectată în prezent (un truc pe care îl veți învăța în acest capitol) și apoi să o utilizați pentru a construi
comanda corectă a bazei de date.

• Notă Un alt exemplu de vulnerabilitate de securitate introdusă de codificarea slabă este atacul
de injectare SQL foarte comun. Ați învățat să preveniți acest atac utilizând comenzi de baze de
date parametrizate în capitolul 14.

Atunci când proiectați având în vedere securitatea, este important să luați în considerare diferitele căi de
atac. Cu toate acestea, nu puteți anticipa întotdeauna potențialele probleme. Din acest motiv, este foarte
logic să vă stratificați securitatea. Mantra arhitecților de securitate poate fi rezumată astfel: "Nu forțați un
atacator să facă un lucru imposibil pentru a pătrunde în sistemul dvs. - forțați-l să facă mai multe".

Testarea și implementarea setărilor de securitate


După cum știți deja, site-urile web ASP.NET live utilizează software-ul de găzduire web IIS. Dar când
începeți să construiți și să testați aplicația web, vă bazați pe serverul web încorporat Visual Studio.
Această diferență are două efecte asupra securității site-ului web:

Contul de utilizator al site-ului web: Când executați un site web în Visual Studio, serverul web de testare utilizează
contul de utilizator Windows. Drept urmare, codului i se permite să efectueze sarcini (citirea unui fișier, scrierea într-un
jurnal, conectarea la o bază de date și așa mai departe) care ar putea să nu fie permise pe un server web live. Pentru a
vă asigura că totul continuă să funcționeze atunci când implementați aplicația web, trebuie să știți mai multe despre
modul în care IIS și ASP.NET utilizează conturile Windows - un subiect pe care îl veți aborda în capitolul 26.

Configurație de securitate: O altă diferență constă în modul în care configurați setările de securitate,
cum ar fi tipul de autentificare utilizat de aplicația web și regulile de autorizare care guvernează cine
poate accesa diferite pagini. Într-un site de testare, puteți aplica aceste setări în două moduri: editând
fișierul web.config sau executând instrumentul de administrare a site-ului web (WAT), pe care l-ați
întâlnit pentru prima dată în capitolul 5. Într-o aplicație implementată, puteți continua să utilizați setările
de securitate pe care le-ați setat în timpul dezvoltării, puteți edita manual web.config sau puteți utiliza
instrumentul convenabil de configurare IIS Manager.
În plus, există unele setări de securitate care intră în joc numai atunci când configurați un site web implementat. De
exemplu, utilizați IIS Manager pentru a activa Secure Sockets Layer (SSL), un standard de criptare care se asigură că
hackerii rău intenționați nu pot trage cu urechea la mesajele trimise între utilizator și serverul web. De asemenea, aveți
posibilitatea să configurați protocoalele specifice pe care IIS le utilizează pentru autentificarea Windows (cu alte
cuvinte, standardul exact de securitate utilizat pentru a transmite informații despre utilizator și a conecta pe cineva).

Acest capitol explică modul de aplicare a securității unui site web în timpul dezvoltării. Conceptele și
detaliile pe care le veți învăța se vor aplica la fel de bine unei aplicații implementate. Cu toate acestea, dacă
doriți să ajustați configurația de securitate după ce implementați aplicația sau dacă doriți să obțineți mai
multe progrese cu setările de autentificare SSL și Windows, merită să aflați despre IIS Manager. Capitolul
26 conține toate detaliile.

648
CAPITOLUL FUNDAMENTE DE SECURITATE
19

Autentificare și autorizare
Două concepte formează baza oricărei discuții despre securitate:

Autentificare: Acesta este procesul de determinare a identității unui utilizator și de forțare a utilizatorilor să dovedească
cine pretind că sunt. De obicei, aceasta implică introducerea acreditărilor (de obicei un nume de utilizator și o parolă)
într-un fel de pagină sau fereastră de conectare. Aceste acreditări sunt apoi autentificate pentru conturile de utilizator
Windows de pe un computer, o listă de utilizatori dintr-un fișier sau o bază de date back-end.

Autorizare: Odată ce un utilizator este autentificat, autorizarea este procesul prin care se determină
dacă acel utilizator are suficiente permisiuni pentru a efectua o anumită acțiune (cum ar fi vizualizarea
unei pagini sau preluarea informațiilor dintr-o bază de date). Windows impune unele verificări de
autorizare (de exemplu, atunci când deschideți un fișier), dar codul dvs. va dori probabil să impună
propriile verificări (de exemplu, atunci când un utilizator efectuează o activitate în aplicația web, cum ar
fi trimiterea unei comenzi, atribuirea unui proiect sau oferirea unei promoții).
Autentificarea și autorizarea sunt cele două pietre de temelie ale creării unui site securizat bazat pe utilizator.
Sistemul de operare Windows oferă o analogie bună. Când porniți computerul pentru prima dată, furnizați un
ID de utilizator și o parolă, autentificându-vă astfel în sistem. După acel moment, de fiecare dată când
interacționați cu o resursă restricționată (cum ar fi un fișier, o bază de date, o cheie de registry etc.),
Windows efectuează în liniște verificări de autorizare pentru a se asigura că contul dvs. de utilizator are
drepturile necesare. Puteți utiliza două tipuri de autentificare pentru a securiza un site web ASP.NET:
Autentificarea formularelor: Cu autentificarea formularelor, ASP.NET este responsabil de autentificarea utilizatorilor,
urmărirea acestora și autorizarea fiecărei solicitări. De obicei, autentificarea formularelor funcționează împreună cu o
bază de date în care stocați informații despre utilizator (cum ar fi numele de utilizator și parolele), dar aveți
flexibilitate completă. Puteți chiar să stocați informații despre utilizator într-un fișier text simplu sau să scrieți un cod
de conectare personalizat care apelează un serviciu la distanță. Autentificarea formularelor este cel mai bun și mai
flexibil mod de a rula un site de abonament sau un magazin de comerț electronic.
Autentificare Windows: Cu autentificarea Windows, serverul web obligă fiecare utilizator să se conecteze ca
utilizator Windows. (În funcție de configurația specifică pe care o utilizați, acest proces de conectare poate
avea loc automat, așa cum se întâmplă în serverul web de testare Visual Studio sau poate necesita ca
utilizatorul să introducă un nume și o parolă într-o casetă de dialog de conectare.) Acest sistem necesită ca
toți utilizatorii să aibă conturi de utilizator Windows pe server (deși utilizatorii ar putea partaja conturi). Acest
scenariu nu este potrivit pentru o aplicație web publică, dar este adesea ideal cu un intranet sau un site
specific companiei, conceput pentru a furniza resurse pentru un set limitat de utilizatori.

Vă veți concentra pe aceste două abordări în acest capitol. În primul rând, veți explora modelul de
autentificare a formularelor, care este perfect pentru site-urile web accesibile publicului. Apoi, veți lua în
considerare autentificarea Windows, ceea ce are sens în medii de rețea mai mici, unde aveți un grup de
utilizatori cunoscuți.

Autentificarea formularelor
În programarea ASP de modă veche, dezvoltatorii trebuiau să-și creeze propriile sisteme de securitate. O abordare
comună a fost inserarea unui mic fragment de cod la începutul fiecărei pagini securizate. Acest cod ar verifica
existența unui cookie personalizat. Dacă cookie-ul nu ar exista, utilizatorul ar fi redirecționat către o pagină de
conectare, unde cookie-ul ar fi creat după o autentificare reușită.
ASP.NET utilizează aceeași abordare în modelul său de autentificare a formularelor. Sunteți în continuare responsabil
pentru crearea paginii de conectare (deși puteți utiliza un set de controale special concepute pentru a vă ajuta, așa cum

649
CAPITOLUL FUNDAMENTE DE SECURITATE
19

este descris în capitolul 20). Cu toate acestea, nu este necesar să creați manual cookie-ul de securitate sau să îl verificați în
pagini securizate, deoarece ASP.NET gestionează automat aceste sarcini. De asemenea, beneficiați de suportul ASP.NET
pentru algoritmi sofisticați de validare, care fac aproape imposibil ca utilizatorii să-și falsifice propriile cookie-uri sau să încerce
alte trucuri de hacking pentru a vă păcăli aplicația să le ofere acces.
Figura 19-1 prezintă o diagramă de securitate simplificată a modelului de autentificare a formularelor în ASP.NET.

Figura 19-1. Autentificarea formularelor ASP.NET

Pentru a implementa securitatea bazată pe formulare, trebuie să urmați trei pași:


1. Setați modul de autentificare la autentificarea formularelor din fișierul web.config.
(Dacă preferați un instrument grafic, puteți utiliza WAT în timpul dezvoltării sau
IIS Manager după implementare.)
2. Restricționați utilizatorii anonimi de la o anumită pagină sau director din aplicația dvs.
3. Creați pagina de conectare.
Veți parcurge acești pași în secțiunile următoare.

650
CAPITOLUL FUNDAMENTE DE SECURITATE
19

Setări Web.config
Definiți tipul de securitate în fișierul web.config utilizând eticheta <autentificare>. Următorul exemplu
configurează aplicația pentru a utiliza autentificarea formularelor utilizând eticheta <autentificare>. De
asemenea, setează câteva dintre cele mai importante setări utilizând o etichetă imbricată <formulare>. Și
anume, stabilește numele cookie-ului de securitate, durata de timp în care acesta va fi considerat valid (în
minute) și pagina care permite utilizatorului să se conecteze.
<configuration> <system.web> <authentication
mode="Forms"> <forms name="MyAppCookie"
loginUrl="~/Login.aspx" protection="All"
timeout="30" path="/" /> </authentication> ...

</system.web> </configuration>

Tabelul 19-1 descrie aceste setări. Toate furnizează valori implicite, deci nu trebuie să le setați în mod
explicit. Pentru o listă completă a atributelor acceptate, consultați Ajutorul Visual Studio.

Tabelul 19-1. Setări de autentificare a formularelor

Atribut Descriere

nume Numele cookie-ului HTTP de utilizat pentru autentificare (implicit este . ASPXAUTH).
Dacă mai multe aplicații rulează pe același server web, ar trebui să dați cookie-ului de
securitate al fiecărei aplicații un nume unic.

loginUrl Pagina dvs. de conectare personalizată, unde utilizatorul este redirecționat dacă nu este
găsit niciun cookie de autentificare valid. Valoarea implicită este Login.aspx.

protecție Tipul de criptare și validare utilizat pentru cookie-ul de securitate (poate fi Toate, Niciuna,
Criptare sau Validare). Validarea asigură că cookie-ul nu este modificat în timpul
tranzitului și criptarea (de obicei Triple-DES) este utilizată pentru a codifica conținutul
acestuia. Valoarea implicită este All.
Timeout Numărul de minute de inactivitate înainte de expirarea cookie-ului. ASP.NET va reîmprospăta
cookie-ul de fiecare dată când primește o solicitare. Valoarea implicită este 30.

drum Calea pentru cookie-urile emise de aplicație. Valoarea implicită (/) este recomandată,
deoarece neconcordanțele de caz pot împiedica trimiterea cookie-ului împreună cu o solicitare.

Reguli de autorizare
Dacă efectuați aceste modificări în fișierul web.config al unei aplicații și solicitați o pagină, veți observa că nu
se întâmplă nimic neobișnuit, iar pagina web este servită în mod normal. Acest lucru se datorează faptului că,
deși ați activat autentificarea formularelor pentru aplicația dvs., nu ați restricționat utilizatorii anonimi. Cu alte
cuvinte, ați ales sistemul pe care doriți să îl utilizați pentru autentificare, dar momentan niciuna dintre paginile
dvs. nu are nevoie de autentificare.

651
CAPITOLUL FUNDAMENTE DE SECURITATE
19

Pentru a controla cine poate și cine nu poate accesa site-ul dvs. web, trebuie să adăugați reguli de control al accesului
la secțiunea <autorizare> a fișierului web.config. Iată un exemplu care dublează comportamentul implicit:

<configuration> <system.web> <authentication mode="Forms"> <forms loginUrl="~/Login.aspx" /> </authentication>

<authorization> <allow users="*" /> </authorization> ...

</system.web> </configuration>

Asteriscul (*) este un metacaracter care permite în mod explicit tuturor utilizatorilor să utilizeze aplicația, chiar și celor
care nu au fost autentificați. Dar chiar dacă nu includeți această linie în fișierul web.config al aplicației dvs., acesta este în
continuare comportamentul pe care îl veți vedea, deoarece setările implicite ale ASP.NET permit tuturor utilizatorilor. (Din
punct de vedere tehnic, acest comportament se întâmplă deoarece există o regulă <allow users="*"> în fișierul rădăcină
web.config. Dacă sunteți curioși, puteți găsi acest fișier într-un director precum c:\Windows\Microsoft.NET\Framework\
[Version]\Config, unde [Version] este versiunea de ASP.NET instalată, cum ar fi v4.0.30319.) Pentru a schimba acest
comportament, trebuie să adăugați în mod explicit o regulă mai restrictivă, așa cum se arată aici:

<autorizare>
<deny users="?" />
</autorizare>
Semnul întrebării (?) este un metacaracter care se potrivește cu toți utilizatorii anonimi. Prin includerea acestei reguli în
fișierul web.config, specificați că utilizatorii anonimi nu sunt permiși. Fiecare utilizator trebuie să fie autentificat și fiecare
solicitare a utilizatorului va necesita cookie-ul de securitate. Dacă solicitați acum o pagină în directorul de aplicații,
ASP.NET va detecta că solicitarea nu este autentificată. Apoi va redirecționa solicitarea către pagina de conectare
specificată de atributul loginUrl din fișierul web.config. (Dacă încercați acest pas chiar acum, procesul de redirecționare
va cauza o eroare, cu excepția cazului în care ați creat deja pagina de conectare.)

Acum luați în considerare ce se întâmplă dacă adăugați mai multe reguli la secțiunea de autorizare:

<authorization> <allow users="*" /> <deny users="?" /> </authorization>

La evaluarea regulilor, ASP.NET scanează lista de sus în jos și apoi continuă cu setările din orice fișier .config
moștenit dintr-un director părinte, terminând cu setările din fișierul machine.config de bază. De îndată ce găsește o
regulă aplicabilă, își oprește căutarea. Astfel, în cazul precedent, se va stabili că regula <permite utilizatorilor = "* ">
se aplică cererii curente și nu va evalua a doua linie. Aceasta înseamnă că aceste reguli vor permite tuturor
utilizatorilor, inclusiv utilizatorilor anonimi. Dar luați în considerare ce se întâmplă dacă aceste două linii sunt
inversate:
<authorization> <deny
users="?" /> <allow
users="*" />
</authorization>

652
CAPITOLUL FUNDAMENTE DE SECURITATE
19

Acum, aceste reguli vor refuza utilizatorii anonimi (prin potrivirea primei reguli) și vor permite tuturor celorlalți
utilizatori (prin potrivirea celei de-a doua reguli).

Controlul accesului la anumite directoare


Un design comun al aplicației este plasarea fișierelor care necesită autentificare într-un director separat. Cu
ASP.NET fișiere de configurare, această abordare este ușoară. Doar lăsați setările implicite <authorization> în
directorul părinte normal și adăugați un fișier web.config care specifică setări mai stricte în directorul securizat.
Acest web.config trebuie pur și simplu să refuze utilizatorii anonimi (toate celelalte setări și secțiuni de
configurare pot fi omise).
<!-- Acest fișier web.config se află într-un subfolder. -->
<configuration> <system.web> <authorization> <deny
users="?" /> </authorization> </system.web>
</configuration>

Notă
Nu puteți modifica setările etichetei <autentificare> în fișierul web.config al unui subdirector din aplicație. În schimb, toate directoarele din aplicație trebuie să utilizeze același sistem de autentificare. Cu toate acestea, fiecare director poate avea propriile reguli de autorizare.

Controlul accesului la anumite fișiere


În general, setarea permisiunilor de acces la fișiere în funcție de director este cea mai curată și mai ușoară
abordare. Cu toate acestea, aveți, de asemenea, opțiunea de a restricționa anumite fișiere adăugând etichete
<locație> la fișierul web.config. Etichetele de locație se află în afara etichetei principale <system.web> și sunt
imbricate direct în eticheta de bază <configuration>, așa cum se arată aici:
<configuration> <system.web> <authentication
mode="Forms"> <forms loginUrl="~/Login.aspx" />
</authentication>

<authorization> <allow
users="*" />
</authorization> ...
</system.web>

<location path ="SecuredPage.aspx">


<system.web> <authorization> <deny
users="?" /> </authorization>

653
CAPITOLUL FUNDAMENTE DE SECURITATE
19

</system.web>
</locație>

<location path ="AnotherSecuredPage.aspx">


<system.web> <authorization> <deny
users="?" /> </authorization> </system.web>

</locație>
</configurare>

În acest exemplu, toate fișierele din aplicație sunt permise, cu excepția SecuredPage.aspx și
AnotherSecuredPage.aspx, care au o regulă suplimentară de acces care refuză utilizatorii anonimi.
Observați că, chiar și atunci când utilizați mai multe secțiuni <locație> pentru a furniza seturi diferite de
reguli de autorizare, includeți în continuare o singură secțiune <autentificare>. Acest lucru se datorează
faptului că o aplicație web poate utiliza un singur tip de autentificare.

Sfat
De asemenea, puteți utiliza etichetele de locație pentru a seta reguli pentru un anumit subdirector. Depinde de dvs. dacă doriți să utilizați această abordare sau preferați să creați fișiere web.config separate pentru fiecare subdirector, așa cum este descris în secțiunea anterioară.

Controlul accesului pentru anumiți utilizatori


Regulile <permite> și <refuză> nu trebuie să utilizeze metacaracterele cu asterisc sau semn de întrebare.
În schimb, pot identifica în mod specific un nume de utilizator sau o listă de nume de utilizator separate prin
virgulă. De exemplu, următoarea listă restricționează în mod specific accesul a trei utilizatori. Acești
utilizatori nu vor putea accesa paginile din acest director. Toți ceilalți utilizatori autentificați vor fi permiși.
<authorization> <deny users="?" />
<deny users="matthew,sarah" /> <deny
users="john" /> <allow users="*" />
</authorization>

Veți observa că prima regulă din acest exemplu neagă toți utilizatorii anonimi. În caz contrar, următoarele reguli nu
vor avea niciun efect, deoarece utilizatorii nu vor fi obligați să se autentifice.
Următoarele reguli permit în mod explicit doi utilizatori. Tuturor celorlalte solicitări ale utilizatorilor li se va
refuza accesul, chiar dacă sunt autentificate.

<authorization> <deny users="?" /> <allow users="matthew,sarah" /> <deny users="*" /> </authorization>

654
CAPITOLUL FUNDAMENTE DE SECURITATE
19

Nu confundați aceste nume de utilizator cu numele conturilor de utilizator Windows care sunt configurate pe
serverul web. Atunci când utilizați autentificarea formularelor, modelul de securitate al aplicației este separat
de sistemul de cont de utilizator Windows. Aplicația dvs. atribuie numele de utilizator atunci când un utilizator
se conectează prin pagina de conectare. Adesea, veți alege nume de utilizator care corespund ID-urilor
dintr-o bază de date. Singura cerință este ca numele dvs. de utilizator să fie unice.

CÂȘTIGUL
Aveți un alt mod de a configura regulile de autentificare și autorizare. În loc să editați manual fișierul
web.config, puteți utiliza WAT din Visual Studio. WAT vă ghidează prin proces, deși veți găsi că este încă
important să înțelegeți ce modificări se fac de fapt în fișierul web.config. De asemenea, este adesea mai
rapid să introduceți manual o listă de reguli de autorizare, mai degrabă decât să utilizați WAT.
Pentru a utiliza WAT pentru acest tip de configurare, selectați Website ASP.NET Configuration din the menu. Apoi,
faceți clic pe fila Securitate. Veți vedea fereastra prezentată în Figura 19-2, care vă oferă linkuri pentru a seta tipul de
autentificare, a defini regulile de autorizare (utilizând secțiunea Reguli de acces) și a activa securitatea bazată pe roluri.
(Securitatea bazată pe roluri este o caracteristică opțională de nivel superior pe care o puteți utiliza cu autentificarea
formularelor. Veți afla mai multe despre cum funcționează și cum să îl activați în capitolul următor.)

Figura 19-2. Fila Securitate din WAT

Pentru a seta o aplicație pentru a utiliza autentificarea formularelor, urmați acești pași:
1. Faceți clic pe Selectați tipul de autentificare.

655
CAPITOLUL FUNDAMENTE DE SECURITATE
19

2. Alegeți opțiunea De pe Internet. (Dacă alegeți în schimb Dintr-o rețea locală,


veți ajunge să utilizați abordarea de autentificare Windows încorporată descrisă
mai târziu în secțiunea "Autentificare Windows".)
3. Faceți clic pe Terminat. Eticheta <authorization> corespunzătoare va fi
creată în fișierul web.config.

Sfat: opțiunile Selectați autentificarea sunt formulate într-un mod ușor înșelător. Este adevărat că aplicațiile care au utilizatori care se conectează de pe tot internetul vor folosi cu siguranță autentificarea formularelor. Cu toate acestea, aplicațiile care rulează într-o rețea locală pot utiliza și autentificarea formularelor - totul depinde de modul în care se conectează și dacă doriți să utilizați informațiile din conturile existente. Cu alte cuvinte, un intranet local vă oferă opțiunea de

a utiliza autentificarea Windows, dar nu o solicită.

Apoi, este timpul să definiți regulile de autorizare. Pentru aceasta, faceți clic pe linkul Creare reguli de acces.
(De asemenea, puteți modifica regulile existente făcând clic pe linkul Gestionare reguli de acces.) Folosind
pagina ușor complicată prezentată în Figura 19-3, aveți posibilitatea de a crea o regulă care să permită sau să
restricționeze anumiți utilizatori la întregul site sau la o anumită pagină sau subfolder. De exemplu, regula din
Figura 19-3 va refuza utilizatorului jenny din întregul site odată ce faceți clic pe OK pentru a-l adăuga.

Figura 19-3. Adăugarea unei reguli de autorizare

656
CAPITOLUL FUNDAMENTE DE SECURITATE
19

Pentru a gestiona mai multe reguli, va trebui să faceți clic pe linkul Gestionare reguli de acces. Acum veți
avea șansa de a schimba ordinea regulilor (și, prin urmare, prioritatea, așa cum este descris mai devreme),
așa cum se arată în Figura 19-4. Dacă aveți un număr mare de reguli de creat, este posibil să descoperiți că
este mai ușor să editați manual fișierul web.config. S-ar putea să doriți doar să creați o regulă inițială pentru a
vă asigura că este în locul potrivit și apoi să copiați și să lipiți calea spre succes.

Figura 19-4. Reguli de autorizare a comenzilor

Fila Securitate este puțin copleșitoare la prima vedere, deoarece include câteva caracteristici pe care nu le-ați
prezentat încă. De exemplu, fila Securitate vă permite, de asemenea, să creați și să gestionați înregistrări și
roluri de utilizator, atât timp cât sunteți dispus să utilizați structura de bază de date predefinită de care
ASP.NET necesită. Veți afla mai multe despre aceste detalii, care fac parte dintr-o caracteristică largă numită
membru, în capitolul următor. Pentru moment, vă veți concentra pe procesul de autentificare și autorizare.

Pagina de conectare
După ce ați specificat modul de autentificare și regulile de autorizare, trebuie să construiți pagina de conectare reală, care
este o pagină .aspx obișnuită care solicită informații de la utilizator și decide dacă utilizatorul trebuie autentificat.

ASP.NET oferă o clasă specială FormsAuthentication în spațiul de nume System.Web.Security, care oferă
metode statice care ajută la gestionarea procesului. Tabelul 19-2 descrie cele mai importante metode din
această clasă.

657
CAPITOLUL FUNDAMENTE DE SECURITATE
19

Tabelul 19-2. Membrii clasei FormsAuthentication

Membru Descriere

FormsCookieName O proprietate doar în citire care furnizează numele autentificării formularelor


prăjitură.

FormsCookiePath O proprietate doar în citire care furnizează calea setată pentru formulare
Cookie de autentificare.

Autentificare() Verifică un nume de utilizator și o parolă în raport cu o listă de conturi care pot fi
introdus în fișierul web.config.

RedirectFromLoginPage() Loghează utilizatorul într-o aplicație ASP.NET prin crearea cookie-ului,


atașarea acestuia la răspunsul curent și redirecționarea utilizatorului către pagină
solicitat inițial.

Deconectare() Deconectează utilizatorul din aplicația ASP.NET eliminând curentul


Descărcați de la Wow! eBook <www.wowebook.com>

cookie criptat.

SetAuthCookie () Loghează utilizatorul într-o aplicație ASP.NET prin crearea și atașarea


Cookie de autentificare a formularelor. Spre deosebire de RedirectFromLoginPage()
metodă, nu redirecționează utilizatorul înapoi la pagina solicitată inițial.

GetRedirectUrl() Furnizează adresa URL a paginii solicitate inițial. Ai putea folosi acest lucru
cu SetAuthCookie() pentru a conecta un utilizator la o aplicație și a face o
decideți în codul dvs. dacă să redirecționați către pagina solicitată sau să utilizați un
pagină implicită mai potrivită.

GetAuthCookie() Creează modulul cookie de autentificare, dar nu îl atașează la modulul curent


răspuns. Puteți efectua personalizarea suplimentară a cookie-urilor și apoi
adăugați-l manual la răspuns, așa cum este descris în capitolul 8.

HashPasswordForStoringIn Criptează un șir de text utilizând algoritmul specificat (SHA1 sau MD5).
ConfigFile() Această valoare hash oferă o modalitate sigură de a stoca o parolă criptată
într-un fișier sau într-o bază de date.

O simplă pagină de conectare poate pune aceste metode la lucru cu puțin cod. Pentru a încerca, începeți prin activarea
autentificării formularelor și refuzarea utilizatorilor anonimi în web.config, așa cum este descris mai devreme:

<configuration> <system.web> <authentication


mode="Forms"> <forms loginUrl="~/Login.aspx" />
</authentication>

<authorization> <deny
users="?" /> <allow

658
CAPITOLUL FUNDAMENTE DE SECURITATE
19

users="*" />
</authorization> ...
</system.web> </configuration>

Acum, utilizatorii vor fi redirecționați către o pagină de conectare numită Login.aspx pe care trebuie să o
creați. Figura 19-5 prezintă un exemplu de pagină simplă de conectare pe care ați putea să o construiți.

Figura 19-5. O pagină tipică de conectare

Când utilizatorul face clic pe butonul Login, pagina verifică dacă utilizatorul a tastat parola Secret și apoi
utilizează metoda RedirectFromLoginPage() pentru a conecta utilizatorul. Iată codul complet al paginii:

public clasa partiala Login : System.Web.UI.Page


{
protejat void cmdLogin_Click(Object sender, EventArgs e) { if (txtPassword.Text.ToLower() == "secret") {
FormsAuthentication.RedirectFromLoginPage( txtName.Text, false); } else { lblStatus.Text = "Încercați din nou."; } }

}
Metoda RedirectFromLoginPage() necesită doi parametri. Primul stabilește numele utilizatorului. A doua este o variabilă
booleană care specifică dacă doriți să creați un cookie persistent (unul care rămâne pe hard disk-ul utilizatorului pentru o
perioadă mai lungă de timp). Cu toate acestea, într-un efort de a fi mai sigur, ASP.NET nu mai onorează această proprietate în

659
CAPITOLUL FUNDAMENTE DE SECURITATE
19

modul în care a fost proiectată inițial. (Veți afla mai multe despre cookie-urile persistente în scurt timp, în secțiunea numită
"Cookie-uri persistente".) Evident, abordarea utilizată în pagina de conectare nu este teribil de sigură - verifică pur și simplu
dacă utilizatorul furnizează o parolă codificată. Într-o aplicație reală, probabil că ați verifica numele de utilizator și parola
împotriva informațiilor dintr-o bază de date și ați conecta utilizatorul numai dacă informațiile se potrivesc exact. Puteți scrie
acest cod cu ușurință folosind programarea ADO.NET pe care ați învățat-o în partea 4, deși necesită un pic de cod obositor.
Veți lua în considerare modalități mai practice de a îndeplini această sarcină în capitolul următor.

Puteți testa acest cod cu site-ul web FormsSecurity inclus în codul descărcabil pentru acest capitol. Dacă
solicitați fișierul SecuredPage.aspx, veți fi redirecționat către Login.aspx. După introducerea parolei corecte,
veți reveni la SecuredPage.aspx.

Recuperarea identității utilizatorului


Odată ce utilizatorul este conectat, puteți prelua identitatea prin proprietatea încorporată a utilizatorului,
așa cum se arată aici:

protejat void Page_Load(Obiect expeditor, EventArgs e)


{
lblMessage.Text = "Ați ajuns la pagina securizată, ";
lblMessage.Text += User.Identity.Name + ".";
}
Nu este necesar să plasați codul în pagina de conectare. În schimb, puteți utiliza obiectul Utilizator pentru a examina
identitatea utilizatorului curent oricând trebuie să faceți acest lucru.
Figura 19-6 prezintă rezultatul rulării acestui cod.

Figura 19-6. Accesarea unei pagini securizate

Puteți accesa obiectul Utilizator din codul dvs., deoarece este o proprietate a obiectului Pagină curent.
Obiectul Utilizator furnizează informații despre utilizatorul conectat în prezent. Este destul de simplu - de
fapt, utilizatorul oferă o singură proprietate și o singură metodă:

660
CAPITOLUL FUNDAMENTE DE SECURITATE
19

• Proprietatea Identitate vă permite să regăsiți numele utilizatorului conectat și tipul de


autentificare utilizat.
• Metoda IsInRole() vă permite să determinați dacă un utilizator este membru al unui
anumit rol (și, prin urmare, ar trebui să i se acorde anumite privilegii). Veți folosi
IsInRole() mai târziu în acest capitol.

ÎNȚELEGEREA IDENTITĂȚILOR

Obiectul Utilizator este standardizat astfel încât să poată funcționa cu orice tip de sistem de autentificare. O
consecință a acestui design este că proprietatea User.Identity returnează un alt tip de obiect, în funcție de
tipul de autentificare pe care îl utilizați.
De exemplu, atunci când se utilizează autentificarea formularelor, obiectul identitate este o instanță a
clasei FormsIdentity. Când utilizați autentificarea Windows, obțineți în schimb un obiect WindowsIdentity.
Și dacă utilizatorul nu este conectat deloc, veți obține o identitate generică. (Toate aceste clase
implementează interfața IIdentity, care le standardizează.)
De cele mai multe ori, nu trebuie să vă faceți griji cu privire la această sleight of hand. Dar, ocazional,
poate doriți să proiectați proprietatea User.Identity la un tip mai specific pentru a obține acces la o
informație suplimentară. De exemplu, obiectul FormsIdentity furnizează tichetul de securitate (într-o
proprietate numită Ticket), care nu este disponibil prin interfața standard IIdentity. Acest tichet este o
instanță a clasei FormsAuthenticationTicket și oferă câteva detalii diverse, cum ar fi ora la care utilizatorul
s-a conectat și când va expira biletul. În mod similar, obiectul WindowsIdentity furnizează informații
suplimentare care se referă la conturile Windows (cum ar fi dacă utilizatorul curent utilizează un cont
invitat sau un cont de sistem). Veți vedea un exemplu al acestei tehnici mai târziu în acest capitol, în
secțiunea "Autentificare Windows".

Deconectarea
Orice aplicație web care utilizează autentificarea formularelor ar trebui să aibă, de asemenea, un buton
proeminent de deconectare care distruge cookie-ul de autentificare a formularelor:

private void cmdSignOut_Click(Expeditor obiect, EventArgs e)


{
FormsAuthentication.SignOut();
Response.Redirect("~/Login.aspx");
}

Sfat: În capitolul următor, veți învăța cum să simplificați viața cu comenzile de conectare. Aceste controale vă permit să construiți pagini de conectare (și alte tipuri de interfețe de utilizator legate de securitate) fără cod. Cu toate acestea, ele necesită o altă caracteristică - calitatea de membru - pentru a funcționa.

661
CAPITOLUL FUNDAMENTE DE SECURITATE
19

Cookie-uri persistente
În unele situații, este posibil să utilizați autentificarea formularelor pentru personalizare în loc de securitate. În această
situație, puteți decide să permiteți cookie-urile persistente. Un cookie de autentificare persistent rămâne pe hard disk-ul
utilizatorului și menține utilizatorul conectat ore întregi, zile sau săptămâni - chiar dacă utilizatorul închide și redeschide
browserul.
Crearea unui cookie persistent necesită un pic mai mult cod decât crearea unui cookie standard de autentificare a
formularelor. În loc să utilizați metoda RedirectFromLoginPage(), trebuie să creați manual biletul de autentificare,
să setați timpul de expirare, să îl criptați, să îl atașați la solicitare și apoi să redirecționați utilizatorul către pagina
solicitată. Toate aceste sarcini sunt ușoare, dar este important să le îndepliniți pe toate în ordinea corectă. (Când
utilizați cookie-uri nepersistente, RedirectFromLoginPage () se ocupă automat de toate aceste sarcini.)
Cookie-urile persistente prezintă, de asemenea, un potențial risc de securitate, deoarece un alt utilizator ar putea folosi
același computer pentru a accesa paginile securizate, fără a fi obligat să se autentifice. Dacă doriți să permiteți
utilizatorului să creeze un cookie persistent, ar trebui să îl faceți opțional, deoarece utilizatorul poate dori să acceseze
site-ul dvs. de pe un computer public sau partajat. În general, site-urile care utilizează această tehnică includ o casetă
de selectare cu text, cum ar fi Păstrează-mă conectat.
Următorul cod examinează o casetă de selectare numită chkPersist. Dacă este selectat, codul creează
un cookie persistent care durează 20 de zile (deși puteți modifica acest detaliu la orice interval de timp
doriți).
Efectuați autentificarea.
dacă (txtPassword.Text.ToLower() == "secret")
{
dacă (chkPersist.Checked) { // Utilizați un cookie
persistent care durează 20 de zile.

Timpul de expirare trebuie specificat ca număr de minute. int


timeout = (int)TimeSpan.FromDays(20). MinuteTotale;

Creați un bilet de autentificare.


FormsAuthenticationTicket ticket = nou
FormsAuthenticationTicket(txtName.Text, true, cookietimeout);
Criptați biletul (astfel încât oamenii să nu-l poată fura în timp ce călătorește pe
Internet).
șir encryptedTicket = FormsAuthentication.Encrypt(ticket);

Creați cookie-ul pentru bilet și puneți biletul în interior. Cookie HttpCookie = nou
HttpCookie (FormsAuthentication.FormsCookieName, encryptedTicket); Dați
cookie-ului și biletului de autentificare aceeași expirare. prăjitură. Expiră = bilet.
Expirare;

Atașați cookie-ul la răspunsul curent. Acum va călători înapoi la // client și apoi


înapoi la serverul web cu fiecare nouă solicitare.
HttpContext.Current.Response.Cookies.Set(cookie);
Trimiteți utilizatorul la pagina solicitată inițial.
string requestPage = FormsAuthentication.GetRedirectUrl(txtName.text, false);
Response.Redirect(requestPage, true); altfel { // Utilizați metoda standard de

662
CAPITOLUL FUNDAMENTE DE SECURITATE
19

autentificare.
FormsAuthentication.RedirectFromLoginPage(
txtName.Text, false); }

Este demn de remarcat faptul că metoda FormsAuthentication.SignOut() va elimina întotdeauna


cookie-ul de autentificare a formularelor, indiferent dacă este un cookie normal sau un cookie persistent.

Autentificare Windows
Cu autentificarea Windows, serverul web se ocupă de procesul de autentificare. ASP.NET pur și simplu pune această
identitate la dispoziția codului dvs. pentru verificările de securitate.
Când utilizați autentificarea Windows, forțați utilizatorii să se conecteze la IIS înainte de a li se permite să acceseze
conținut securizat în site-ul dvs. Informațiile de conectare ale utilizatorului pot fi transmise în mai multe moduri (în funcție
de mediul de rețea, browserul solicitant și modul în care este configurat IIS), dar rezultatul final este că utilizatorul este
autentificat utilizând un cont Windows local. De obicei, acest lucru face ca autentificarea Windows să fie cea mai potrivită
pentru scenariile intranet, în care un set limitat de utilizatori cunoscuți este deja înregistrat pe un server de rețea.

Pentru a implementa securitatea bazată pe Windows cu utilizatori cunoscuți, trebuie să urmați trei pași:
1. Setați modul de autentificare la autentificarea Windows în fișierul web.config.
(Dacă preferați un instrument grafic, puteți utiliza WAT în timpul dezvoltării sau IIS
Manager după implementare.)
2. Dezactivați accesul anonim pentru un director utilizând o regulă de autorizare.
3. Configurați conturile de utilizator Windows pe serverul dvs. web (dacă nu
sunt deja prezente).
Veți parcurge acești pași în secțiunile următoare.

Notă Serverul web încorporat Visual Studio nu acceptă utilizatori anonimi cu autentificare Windows. În schimb, Visual Studio vă conectează automat la serverul de testare utilizând contul Windows. Prin urmare, nu este nevoie să utilizați o regulă de autorizare care refuză utilizatorii anonimi. Cu toate acestea, este încă o practică bună să o adăugați, deoarece la un moment dat veți implementa aplicația la IIS și va trebui să refuzați în mod explicit utilizatorii anonimi.

Setări Web.config
Pentru a utiliza autentificarea Windows, trebuie să vă asigurați că elementul <autentificare> este setat
corespunzător în fișierul web.config. Iată cum:

<configuration>
<system.web>
<mod de autentificare = "Windows" / >

663
CAPITOLUL FUNDAMENTE DE SECURITATE
19

<autorizare>
<deny users="?" />
</authorization> ...
</system.web> </configuration>

În prezent, există o singură regulă de autorizare, care folosește semnul întrebării pentru a refuza toți utilizatorii
anonimi. Acest pas este esențial pentru autentificarea Windows (așa cum este pentru autentificarea formularelor).
Fără acest pas, utilizatorul nu va fi niciodată obligat să se conecteze.
În mod ideal, nici măcar nu veți vedea că are loc procesul de conectare. În schimb, Internet Explorer va transmite
acreditările utilizatorului Windows curent, pe care serverul web le utilizează automat. Serverul web integrat Visual Studio
funcționează întotdeauna în acest fel. IIS funcționează și în acest fel, cu condiția să fi configurat autentificarea Windows
integrată (așa cum este descris în capitolul 26) și browserul să o accepte.
De asemenea, puteți adăuga elemente <permite> și <refuzați> pentru a permite sau restricționa în mod
specific utilizatorii din anumite fișiere sau directoare. Spre deosebire de autentificarea formularelor, trebuie
să specificați numele serverului sau domeniului în care există contul. De exemplu, această regulă permite
contul de utilizator matthew, care este definit pe computerul numit WebServer:
<permite utilizatorilor = "WebServer \ matthew" / >
Pentru o comandă rapidă, puteți utiliza localhost (sau doar o perioadă) pentru a vă referi la un cont
de pe computerul curent, așa cum se arată aici:
<allow users=".\matthew" />
De asemenea, aveți posibilitatea să restricționați anumite tipuri de utilizatori, cu condiția ca conturile
lor să fie membre ale aceluiași grup Windows, utilizând atributul roluri:

<authorization> <deny users="?" /> <allow


roles=".\SalesAdministrator,.\SalesStaff" /> <deny
users=".\matthew" /> </authorization>

În acest exemplu, toți utilizatorii care sunt membri ai grupurilor SalesAdministrator sau SalesStaff vor fi autorizați
automat să acceseze paginile ASP.NET din acest director. Cererile de la utilizatorul matthew vor fi refuzate, cu
excepția cazului în care acesta este membru al grupului SalesAdministrator sau SalesStaff. Amintiți-vă, ASP.NET
examinează regulile în ordinea în care apar și se oprește atunci când găsește o potrivire. Inversarea acestor două linii
de autorizare ar asigura că utilizatorul Matthew a fost întotdeauna refuzat, indiferent de apartenența la grup.

De asemenea, puteți examina programatic apartenența la grup a unui utilizator în codul dvs., așa cum se
arată aici. Deoarece șirul include o bară oblică inversă, trebuie să vă amintiți să îl dublați sau puteți dezactiva
C# scăpând cu un semn precedent la (@).
protejat void Page_Load(Obiect expeditor, EventArgs e)
{
dacă (User.IsInRole(@"MyDomainName\SalesAdministrators")) { // Nu
faceți nimic; pagina ar trebui accesată normal, deoarece // utilizatorul are
privilegii de administrator.
}
altceva
{

664
CAPITOLUL 19 FUNDAMENTE DE
SECURITATE

Nu permiteți această pagină. În schimb, redirecționați către pagina de


pornire. Response.Redirect("Default.aspx");
}
}

În acest exemplu, codul verifică apartenența la un grup Windows particularizat numit SalesAdministrators.
Dacă doriți să verificați dacă un utilizator este membru al unuia dintre grupurile încorporate, nu este
necesar să specificați un computer sau un nume de domeniu. În schimb, utilizați această sintaxă:
dacă (User.IsInRole(@"BUILTIN\Administrators")) {
// (Codul merge aici.)

Pentru mai multe informații despre regulile <permite> și <refuză> și configurarea fișierelor și directoarelor
individuale, consultați discuția din secțiunea "Reguli de autorizare" de mai devreme în acest capitol. Rețineți
că nu aveți nicio modalitate de a regăsi o listă de grupuri disponibile pe serverul web (care ar încălca
securitatea), dar puteți afla numele rolurilor Windows implicite încorporate utilizând enumerarea
System.Security.Principal.WindowsBuiltInRole. Tabelul 19-3 descrie aceste roluri. Nu toate se vor aplica
ASP.NET utilizării, deși Administratorul, Oaspetele și Utilizatorul probabil că se vor aplica.
Tabelul 19-3. Roluri implicite Windows

Rol Descriere

Operator de cont Utilizatorii cu responsabilitatea specială de a gestiona conturile de utilizator pe un


computer sau domeniu.

Administrator Utilizatorii cu acces complet și nerestricționat la computer sau domeniu. (În cazul în
care computerul server web utilizează controlul contului de utilizator [UAC], Windows
va reține privilegiile de administrator de la conturile de administrator, pentru a reduce
riscul de viruși și alte coduri rău intenționate.)

BackupOperator Utilizatorii care pot suprascrie anumite restricții de securitate numai ca parte a
operațiunilor de copiere de rezervă sau restaurare.

Musafir Ca și rolul de utilizator, dar și mai restrictiv.

PowerUser Similar cu Administratorul, dar cu unele restricții.

Operator de imprimare Îmi place utilizatorul, dar cu privilegii suplimentare pentru preluarea controlului asupra unei imprimante.

Replicator Îmi place utilizatorul, dar cu privilegii suplimentare pentru a accepta reproducerea fișierelor într-un domeniu.

Operator de sistem Similar cu Administrator cu unele restricții. În general, operatorii de sistem


gestionează un computer.

Utilizator Utilizatorii sunt împiedicați să efectueze modificări la nivel de sistem și pot rula numai aplicații
certificate (consultați http://www.microsoft.com/windowsserver2008/en/us/isv.aspx pentru mai
multe informații).
CAPITOLUL FUNDAMENTE DE SECURITATE
19

Un test de autentificare Windows


Una dintre caracteristicile frumoase ale autentificării Windows este că nu este necesară nicio pagină de conectare. În
funcție de protocolul de autentificare pe care îl utilizați (consultați capitolul 26 pentru detalii complete), procesul de
conectare poate avea loc automat sau browserul poate afișa o casetă de dialog de conectare. În orice caz, nu trebuie
să efectuați nicio muncă suplimentară.
Puteți prelua informații despre utilizatorul conectat în prezent din obiectul Utilizator. După cum ați aflat mai devreme,
obiectul Utilizator furnizează informații de identitate prin proprietatea User.Identity. În funcție de tipul de autentificare, se
utilizează un obiect de identitate diferit și fiecare obiect de identitate poate furniza informații personalizate. Pentru a
obține câteva informații suplimentare despre identitatea utilizatorului care s-a conectat cu autentificarea Windows, aveți
posibilitatea să efectuați conversia obiectului generic IIdentity la un obiect WindowsIdentity (care este definit în spațiul de
nume System.Security.Principal).
Următoarea este o pagină de test eșantion care utilizează autentificarea Windows (a se vedea Figura 19-7).
Pentru a utiliza acest cod așa cum este scris, trebuie să importați spațiul de nume System.Security.Principal
(unde este definită clasa WindowsIdentity).
public parțial clasa SecuredPage : System.Web.UI.Page
{
protejat void Page_Load(Object sender, EventArgs e) { StringBuilder displayText = new System.Text.StringBuilder();
displayText.Append("Ați ajuns la pagina securizată, "); displayText.Append(User.Identity.Name);

WindowsIdentity winIdentity = (WindowsIdentity)User.Identity;


displayText.Append(".<br /><br />Tip de autentificare: ");
displayText.Append(winIdentity.AuthenticationType);
displayText.Append("<br />Anonim: ");
displayText.Append(winIdentity.IsAnonymous);
displayText.Append("<br />Autentificat: ");
displayText.Append(winIdentity.IsAuthenticated);
displayText.Append("<br />Guest: ");
displayText.Append(winIdentity.IsGuest); displayText.Append("<br
/>Sistem: "); displayText.Append(winIdentity.IsSystem);
displayText.Append("<br />Administrator: ");
displayText.Append(User.IsInRole(@"BUILTIN\Administrators"));
lblMessage.Text = displayText.ToString();
}
}

666
CAPITOLUL FUNDAMENTE DE SECURITATE
19

Figura 19-7. Regăsirea informațiilor de autentificare Windows

Ultimul cuvânt
În acest capitol, ați aflat despre arhitectura de securitate multistratificată din ASP.NET și despre modul în
care vă puteți proteja paginile web și serviciile web utilizând o pagină de conectare particularizată sau
autentificarea Windows. În capitolul următor, veți continua să vă dezvoltați cunoștințele, luând în considerare
câteva caracteristici suplimentare care vă pot simplifica viața și vă pot îmbunătăți securitatea. Veți învăța
cum să obțineți ASP.NET pentru a crea o bază de date de bază a utilizatorilor pentru site-ul dvs. (completată
cu criptarea parolei), scutindu-vă de crearea acesteia sau de scrierea oricărui cod ADO.NET. De asemenea,
veți extinde regulile de autorizare învățând cum puteți grupa utilizatorii autentificați în formulare în grupuri
logice, fiecăruia fiindu-i atribuite propriile permisiuni.

667
Descărcați de la Wow! eBook <www.wowebook.com>
C A P I T O L U L 20

•■■

Apartenență

În capitolul anterior, ați aflat cum puteți utiliza autentificarea formularelor ASP.NET ca piatră de temelie a securității
site-ului dvs. Cu autentificarea formularelor, puteți identifica utilizatorii și le puteți restricționa accesul la paginile pe care
nu ar trebui să le acceseze. Cel mai bun dintre toate, ASP.NET gestionează întregul proces pentru dvs. prin crearea și
verificarea cookie-ului de autentificare a formularelor.
Oricât de convenabilă ar fi autentificarea formularelor, nu este o soluție completă. Depinde încă de dvs. să vă ocupați de o
varietate de sarcini conexe. De exemplu, trebuie să mențineți o listă de utilizatori și să o verificați în timpul procesului de
autentificare. De asemenea, trebuie să creați pagina de conectare, să decideți cum să separați conținutul public de cel
privat și să decideți ce ar trebui să aibă voie să facă fiecare utilizator. Aceste sarcini nu sunt insurmontabile, dar pot fi
obositoare. De aceea, Microsoft adaugă un alt strat de caracteristici cadrului său de autentificare a formularelor. Acest
strat suplimentar este cunoscut sub numele de membru.
Caracteristicile de membru se încadrează în trei mari categorii:

Gestionarea înregistrărilor utilizatorilor: În loc să vă creați propria bază de date de utilizator, dacă
utilizați caracteristicile de membru, ASP.NET puteți crea și întreține acest catalog de informații despre
utilizatori. Poate chiar implementa reguli avansate (cum ar fi solicitarea adreselor de e-mail, punerea de
întrebări de securitate și implementarea parolelor puternice).
Controale de securitate: Fiecare site web securizat are nevoie de o pagină de conectare. Cu controalele de securitate
ASP.NET, nu trebuie să vă proiectați propriile opțiuni, în schimb, puteți utiliza o versiune gata făcută direct din secțiunea
Conectare din Toolbox. Și împreună cu controlul de bază Login sunt alte controale pentru afișarea conținutului securizat,
crearea de noi utilizatori și schimbarea parolelor. Cel mai bun dintre toate, puteți personaliza modul în care funcționează
fiecare control de securitate setând proprietăți și reacționând la evenimente.

Securitate bazată pe roluri: În multe site-uri web, trebuie să acordați permisiuni diferite diferiților
utilizatori. Desigur, viața ar fi mult prea complexă dacă ar trebui să mențineți un set diferit de setări
pentru fiecare utilizator, deci este util să asamblați utilizatorii în grupuri care definesc anumite
permisiuni. Aceste grupuri se numesc roluri, iar caracteristicile de membru ASP.NET includ instrumente
pentru crearea automată a unei baze de date cu informații despre roluri.
În acest capitol, veți explora toate aceste trei zone de caracteristici și veți vedea cum puteți crea site-uri
sigure cu cod surprinzător de mic.

Depozitul de date privind calitatea de membru


Caracteristica cheie de membru este capacitatea ASP.NET de a stoca acreditările utilizatorilor într-o bază de date. Ideea este
să faceți câteva alegeri cu privire la informațiile care vor fi stocate și la politica de securitate care va fi utilizată. Din acel

669
CAPITOLUL APARTENENȚĂ
20

moment, ASP.NET gestionează baza de date de utilizatori pentru dvs., adăugând informații noi despre utilizatori, verificând
acreditările atunci când utilizatorii încearcă să se conecteze și așa mai departe.
În mod clar, depozitul de date de membru are capacitatea de a reduce foarte mult cantitatea de cod pe care trebuie să o
scrieți. Puteți crea un site web securizat cu mult mai puțin cod și, prin urmare, mult mai puțină muncă. De asemenea, nu
trebuie să vă faceți griji cu privire la erorile inevitabile, deoarece modulul de membru ASP.NET este o componentă bine
cunoscută, testată cu atenție.
Deci, de ce nu ați dori să utilizați depozitul de date de membru? Există câteva motive posibile:

Nu doriți să stocați datele de utilizator într-o bază de date: teoretic, puteți stoca lista de utilizatori în orice tip
de depozit de date, de la un fișier XML la o bază de date Oracle. Din punct de vedere tehnic, fiecare magazin
de date necesită un furnizor de membru diferit. Cu toate acestea, ASP.NET include numai doi furnizori:
furnizorul SQL Server pe care îl veți utiliza în acest capitol și un furnizor pentru Active Directory. Dacă doriți
să utilizați un alt depozit de date, cum ar fi o altă bază de date relațională, va trebui să găsiți un abonament
corespunzător sau va trebui să renunțați complet la calitatea de membru.
Aveți nevoie de compatibilitate inversă: Dacă ați creat deja un tabel pentru a stoca informații despre utilizator,
poate fi prea dificil să comutați la depozitul de date de membru. Acest lucru se datorează faptului că
furnizorul de membru SQL Server se așteaptă la o structură de tabel specifică. Nu va funcționa cu tabelele
existente, deoarece vor avea o combinație subtil diferită de câmpuri și tipuri de date. Și chiar dacă nu trebuie
să păstrați structura curentă a tabelului, este posibil să descoperiți că este prea mult de lucru pentru a crea
din nou toate înregistrările de utilizator în depozitul de date de membru.
Doriți să gestionați informațiile despre utilizatori în non-ASP.NET aplicații: După cum veți vedea în acest capitol,
ASP.NET vă oferă un set puternic de obiecte pentru interacțiunea cu datele de membru. De exemplu, puteți să
actualizați înregistrările utilizatorilor, să ștergeți înregistrările utilizatorilor, să regăsiți înregistrările utilizatorilor pe
baza anumitor criterii și așa mai departe. Puteți utiliza chiar și obiectele de membru în alte tipuri de aplicații .NET (de
exemplu, puteți crea o aplicație Windows pentru a gestiona conturile de utilizator). Cu toate acestea, dacă creați o
altă aplicație în afara .NET care trebuie să efectueze aceste activități, este posibil să descoperiți că nu este la fel de
ușor, deoarece va trebui să înțelegeți structura tabelului de membri. În acest caz, este posibil să descoperiți că este
mai ușor să gestionați utilizatorii cu instrucțiuni SQL drepte care funcționează cu propriul tabel.

Dacă decideți să nu utilizați depozitul de date de membru, depinde de dvs. să scrieți ADO.NET cod pentru a prelua
înregistrările utilizatorilor și pentru a verifica acreditările utilizatorilor. Folosind aceste tehnici, vă puteți crea propriile
pagini de conectare pe calea cea grea, așa cum se explică în capitolul 19.
Înainte de a continua, trebuie să configurați site-ul web pentru a utiliza autentificarea formularelor
adăugând eticheta <formulare>. Iată ce trebuie să adăugați:

<configuration> <system.web>
<authentication mode="Forms" /> ...

</system.web> </configuration>

Opțional, puteți defini detalii suplimentare, cum ar fi locația paginii de conectare și timpul înainte de expirarea
cookie-ului de securitate, așa cum este descris în capitolul 19. De asemenea, poate doriți să adăugați o
regulă de autorizare care împiedică utilizatorii anonimi să acceseze o anumită pagină sau subfolder, astfel
încât să puteți testa mai bine securitatea site-ului dvs.

670
CAPITOLUL APARTENENȚĂ
20

Calitatea de membru cu SQL Server Express


Presupunând că decideți să utilizați calitatea de membru, trebuie să creați baza de date de membri. Dacă utilizați SQL
Server Express (versiunea gratuită de SQL Server inclusă în Visual Studio), activitatea este mult mai ușoară decât v-ați
putea aștepta. De fapt, totul se întâmplă automat.
În mod implicit, calitatea de membru este activată pentru fiecare site web nou pe care îl creați.
Furnizorul implicit de membru face următoarele ipoteze:
• Doriți să stocați baza de date de membru utilizând SQL Server Express.
• SQL Server Express este instalat pe computerul curent, cu numele de instanță
SQLEXPRESS. (De obicei, veți instala SQL Server Express atunci când executați
programul de instalare Visual Studio.)
• Depozitul dvs. de date de membru va fi un fișier numit aspnetdb.mdf, care va fi
stocat în subfolderul App_Data din directorul aplicației dvs. web. (Acest lucru se
bazează pe caracteristica de instanță de utilizator SQL Server Express, care este
discutată în capitolul 14.)
Aceste ipoteze au mult sens. Acestea vă permit să creați câte aplicații web doriți, păstrând în același timp bazele de
date ale utilizatorilor separate. Acest lucru se datorează faptului că fiecare site web va avea propriul fișier
aspnetdb.mdf. Aceste fișiere nu sunt niciodată înregistrate în SQL Server, ceea ce înseamnă că atunci când
deschideți o conexiune într-o altă aplicație, nu veți vedea zeci de baze de date de utilizatori. În schimb, singura
modalitate de a vă conecta la ele este să specificați calea fișierului în șirul de conexiune, ceea ce ASP.NET face.
Un alt avantaj al acestei configurări este că este potențial mai ușor să vă implementați site-ul web. Presupunând că serverul
web unde veți implementa aplicația are SQL Server Express, nu va trebui să modificați șirul de conexiune. De asemenea, nu
trebuie să efectuați pași suplimentari pentru a instala baza de date - pur și simplu copiați fișierul aspnetdb.mdf cu site-ul web.
Acesta este în mod clar un mare avantaj pentru companiile mari de găzduire web, deoarece altfel este destul de complex să
susțineți mai multe site-uri web, fiecare cu propria bază de date personalizată care trebuie instalată și întreținută.

Pentru a vedea cum funcționează acest lucru, vă ajută să creați un nou proiect web cu o pagină de test
simplă. Glisați controlul CreateUserWizard pe pagina dvs. din secțiunea Login din Toolbox. Acum rulați
pagina (prezentată în Figura 20-1), fără a adăuga niciun cod sau a configura controlul.

671

x
CAPITOLUL APARTENENȚĂ
20

Figura 20-1. Controlul CreateUserWizard

Completați toate casetele de text cu informații despre utilizator. Rețineți că, în mod implicit, trebuie să furnizați o parolă
care include cel puțin un caracter care nu este un număr sau o literă (cum ar fi un caracter de subliniere sau un
asterisc) și are cel puțin șapte caractere. După ce ați completat toate informațiile, faceți clic pe Creare utilizator.

În acest moment, controlul CreateUserWizard utilizează clasa ASP.NET Membership din culise pentru a
crea un utilizator nou. Furnizorul implicit de membru creează fișierul aspnetdb.mdf (dacă nu există deja),
apoi adaugă noua înregistrare de utilizator. După finalizarea acestui proces, controlul CreateUserWizard
afișează un mesaj care vă informează că utilizatorul a fost creat. În mod miraculos, toate acestea au loc
automat, chiar dacă nu ați configurat nimic în fișierul web.config și nu ați creat fișierul bazei de date în avans.
Pentru a vă asigura că utilizatorul a fost într-adevăr creat, puteți verifica fișierul aspnetdb.mdf. În Exploratorul
de soluții, faceți clic dreapta pe folderul App_Data și selectați Reîmprospătare folder. Veți vedea că fișierul
aspnetdb.mdf apare imediat. Folosind Visual Studio, poți chiar să sapi în conținutul fișierului aspnetdb.mdf.
Pentru aceasta, faceți dublu clic pe fișier în Exploratorul de soluții. Visual Studio va configura o conexiune
nouă și o va adăuga la Server Explorer din stânga. Folosind Server Explorer, puteți naviga liber prin baza de
date, examinând tabelele și procedurile stocate. (Rețineți că fereastra Server Explorer se numește Database
Explorer în Visual Studio Web Developer Express, chiar dacă fereastra funcționează în același mod.)
Puteți verifica tabelul aspnet_Users pentru a găsi înregistrarea de utilizator pe care ați creat-o. Doar faceți
clic dreapta pe numele tabelului și alegeți Afișare date tabel. Veți vedea ceva asemănător înregistrării
prezentate în Figura 20-2. Printre alte detalii, veți găsi un GUID generat aleatoriu care identifică în mod unic
utilizatorul și data la care utilizatorul a utilizat ultima dată aplicația web. Nu veți vedea întrebarea despre
parolă și parolă - acestea sunt stocate într-o înregistrare legată în tabelul aspnet_Membership și sunt
criptate pentru a preveni spionarea ocazională.

672
CAPITOLUL APARTENENȚĂ
20

Figura 20-2. O înregistrare de utilizator în baza de date aspnetdb.mdf

Notă La prima vedere, veți găsi că baza de date a membrilor include un număr amețitor de tabele. Unele dintre aceste tabele sunt pentru alte caracteristici asociate pe care le puteți utiliza sau nu, cum ar fi securitatea bazată pe roluri (discutată mai târziu în secțiunea "Securitate bazată pe roluri") și profilurile (discutate în capitolul 21).

Înainte de a vă scufunda în detaliu în restul caracteristicilor de membru ASP.NET, este important să luați în
considerare ce ar trebui să faceți dacă nu doriți depozitul implicit de date de membru. De exemplu, puteți
decide să stocați tabelele de membru într-o altă bază de date sau poate doriți să configurați una dintre
numeroasele opțiuni pentru furnizorul de membri. Veți învăța cum să faceți acest lucru în următoarele două
secțiuni.

Utilizarea versiunii complete de SQL Server


Dacă utilizați baza de date generată automat pentru SQL Server Express, nu trebuie să atingeți fișierul
web.config. În orice alt caz, va trebui să faceți un pic de modificare a configurației.

673
CAPITOLUL APARTENENȚĂ
20

Cel mai simplu caz este dacă utilizați versiunea completă de SQL Server. În acest caz, puteți utiliza în
continuare setările implicite de membru. Cu toate acestea, trebuie să modificați șirul de conexiune.

Sfat: Setările implicite de membru și șirul de conexiune locală sunt setate în fișierul machine.config. Puteți arunca o privire la acest fișier (și chiar editați-l pentru a actualiza setările pentru toate aplicațiile web de pe computer). Căutați în directorul c:\Windows\Microsoft.NET\Framework\[Version[\Config], unde [Version] este versiunea de ASP.NET instalată, cum ar fi v4.0.30319. Sau, dacă configurați o aplicație web implementată utilizând IIS pe un sistem de operare pe 64 de biți, căutați în directorul

c:\Windows\Microsoft.NET\Framework64\[Version[\Config.

Șirul de conexiune implicit utilizat cu calitatea de membru se numește LocalSqlServer. Puteți edita această
setare direct în machine.config. Cu toate acestea, dacă trebuie doar să îl modificați pentru o singură aplicație,
este mai bine să ajustați fișierul web.config pentru aplicația dvs. Mai întâi, trebuie să eliminați toate șirurile de
conexiune existente utilizând elementul <clar>. Apoi, adăugați șirul de conexiune LocalSqlServer - dar de
data aceasta cu valoarea corectă:
<configuration> <connectionStrings> <clear /> <add name="LocalSqlServer"
providerName="System.Data.SqlClient" connectionString="Data Source=localhost;
Securitate integrată=SSPI; Catalog inițial = aspnetdb "/ > < / connectionStrings> ...

</configurare>
Această secțiune <connectionStrings> elimină toate șirurile de conexiune și apoi creează un nou șir de conexiune. Acest
nou șir de conexiune se conectează la o bază de date numită aspnetdb pe computerul local. Singura captură este că
baza de date aspnetdb nu va fi creată automat. În schimb, va trebui să îl generați cu instrumentul de linie de comandă
aspnet_regsql.exe. În loc să căutați acest fișier, cel mai simplu mod de a-l lansa este să porniți promptul de comandă
Visual Studio (deschideți meniul Start și alegeți Toate programele
→ Visual→Studio ToolsVisual
Microsoft Visual Studio
Studio Command Prompt). Apoi puteți introduce comenzi care utilizează
2010
aspnet_regsql.
Puteți utiliza aspnet_regsql în două moduri. Dacă îl utilizați fără a adăuga parametri de linie de comandă, va
apărea un expert Windows care vă conduce prin proces. Vi se va solicita să furnizați informațiile de conexiune
pentru serverul bazei de date. Baza de date va fi numită aspnetdb, care este implicită recomandată.

Cealaltă opțiune este să specificați exact ce doriți să se întâmple utilizând comutatoarele de linie de comandă. Acest
lucru este util în special atunci când implementați aplicația - puteți utiliza aspnet_regsql ca parte a unui fișier batch de
configurare, care va crea apoi automat depozitul de date de membru. Aceasta este opțiunea pe care o veți utiliza dacă
doriți să alegeți numele bazei de date sau dacă doriți să instalați doar unele dintre tabelele bazei de date. În mod
implicit, instrumentul aspnet_regsql instalează tabele care pot fi utilizate pentru autentificarea utilizatorilor, autorizarea
bazată pe roluri, profiluri și personalizarea părților web. Acest lucru vă oferă flexibilitate maximă, dar este posibil să
simțiți că este exagerat dacă nu intenționați să utilizați unele dintre aceste caracteristici.
Tabelul 20-1 descrie cele mai importante opțiuni de linie de comandă. Iată un exemplu de linie de comandă
care se conectează la o instanță SQL Server fără nume pe computerul curent (utilizând parametrul -S), se
conectează la contul Windows curent (utilizând parametrul -E), instalează toate tabelele (utilizând parametrul
-A all) și le plasează pe toate într-o bază de date numită aspnetdb (care este implicit):

674
CAPITOLUL APARTENENȚĂ
20

aspnet_regsql -S (local) -E -A toate


Dacă doriți să utilizați o altă bază de date, trebuie să specificați numele bazei de date utilizând parametrul -d.

Sfat: Este o idee bună să instalați toate tabelele simultan (utilizând opțiunea –A toate ). În acest fel, baza dvs. de date va fi pregătită pentru caracteristica de profil discutată în capitolul următor. După ce ați terminat de testat aplicația și sunteți gata să creați baza de date finală, puteți crea o bază de date care are doar opțiunile pe care ați decis să le utilizați. (De exemplu, utilizați –A Domnul să folosească calitatea de membru și gestionarea rolurilor, dar nimic altceva.)

Tabelul 20-1. Comutatoare în linia de comandă pentru aspnet_regsql.exe

Comutator Descriere

-S NumeServer Specifică locația instanței SQL Server unde doriți să instalați baza de date.

-E Se conectează la server prin autentificarea Windows, utilizând contul Windows


conectat în prezent.

-U Nume utilizator Specificați numele de utilizator și parola de care aveți nevoie pentru a vă
și - P Parolă conecta la baza de date SQL Server. De obicei, veți folosi -E în schimb.

-Un Specifică caracteristicile pe care doriți să le utilizați (și determină tabelele bazei de
date create). Opțiunile valide pentru acest parametru sunt toate, m (membru), r
(securitate bazată pe roluri), p (profiluri), c (personalizare parte web) și w (pentru
dependențele memoriei cache a bazei de date cu SQL Server 2000).

-R Elimină bazele de date care acceptă caracteristicile specificate de parametrul -A.

-d DatabaseName Vă permite să specificați numele bazei de date în care vor fi create tabelele.
Dacă nu specificați acest parametru, se creează automat o bază de date
denumită aspnetdb.

-sqlexportonly Creează scripturi SQL pentru adăugarea sau eliminarea caracteristicilor specificate în
baza de date, dar nu creează efectiv tabelele din baza de date. În schimb, puteți rula
scriptul după aceea. Aceasta poate fi o tehnică utilă atunci când implementați aplicația.

675
CAPITOLUL APARTENENȚĂ
20

Notă Dacă
• Rulați implementați
aspnet_regsql site-ul web
pe serverul Web.la În
o companie de găzduire
schimb, probabil că va web,
trebuiprobabil că nu
să utilizați vi Server
SQL se va permite
Express. În acest caz, baza de date va fi implementată în folderul App_Data ca parte a aplicației
web și nu vor fi necesari pași suplimentari de configurare. Dacă gazda web nu acceptă SQL
Server Express, va trebui să utilizați un instrument precum SQL Server Management Studio
pentru a pregăti un fișier script .sql care instalează baza de date. Administratorii companiei de
găzduire web pot rula apoi fișierul script pentru a crea baza de date de care aveți nevoie.

Configurarea furnizorului de membru


Configurarea șirului de conexiune este cea mai simplă modificare pe care o puteți face atunci când configurați
depozitul de date de membru. Cu toate acestea, poate doriți să modificați și alte setări de membru. De
exemplu, puteți modifica politica implicită pentru parole.

Notă Ca și în cazul șirului de conexiune, furnizorul implicit de membru este definit în fișierul machine.config. Tu

Puteți edita fișierul machine.config pentru a modifica aceste valori implicite pentru toate
aplicațiile de pe computer, dar nu ar trebui, deoarece vă va complica viața atunci când
implementați aplicația. În schimb, ar trebui să efectuați modificările configurând un nou furnizor
de membru în fișierul web.config al aplicației dvs.

Pentru a configura furnizorul de abonament, trebuie să adăugați elementul <membru> la aplicația web. În
elementul <abonament> definiți un nou furnizor de abonamente cu setările personalizate. Apoi, setați atributul
defaultProvider al elementului <membership> astfel încât să se refere la furnizorul de membru după nume.

Iată structura de bază pe care trebuie să o urmați:

<configuration> <system.web> <membership


defaultProvider="MyMembershipProvider"> <providers> <!-- Ștergeți
toți furnizorii existenți. --> <clar />

<!-- Definiți-vă furnizorul, cu setări personalizate. --> <add


name="MyMembershipProvider" ... /> </providers> </membership> ...

</system.web> </configuration>

Desigur, partea interesantă sunt atributele pe care le utilizați în eticheta <add> pentru a configura furnizorul de abonament. Iată
un exemplu care definește un furnizor de membri cu setări de parolă relaxate. Primele trei atribute furnizează setările necesare
(numele, tipul și șirul de conexiune pentru furnizorul de abonament). Setările rămase elimină cerința pentru o întrebare de

676
CAPITOLUL APARTENENȚĂ
20

securitate și permit ca o parolă să fie la fel de scurtă ca un caracter și să conțină doar litere și cifre:

<membership defaultProvider="MyMembershipProvider">
<providers> <clear/> <add name="MyMembershipProvider"
type="System.Web.Security.SqlMembershipProvider"
connectionStringName="LocalSqlServer"
requiresQuestionAndAnswer="false"
minRequiredPasswordLength="1"
minRequiredNonalphanumericCharacters="0" /> </providers>
</membership>

Tabelul 20-2 descrie cele mai frecvent utilizate setări de membru.

Tabelul 20-2. Atribute pentru configurarea unui furnizor de membru

Atribut Descriere

nume* Specifică un nume pentru furnizorul de membri. Puteți alege orice


nume doriți. Acesta este numele pe care îl utilizați ulterior pentru a
face referire la furnizor (de exemplu, când setați atributul
defaultProvider). De asemenea, îl puteți utiliza pentru a obține
informații despre furnizor în mod programatic.

tip* Specifică tipul de furnizor de abonament. În acest capitol, veți utiliza


întotdeauna System.Web.Security.SqlMembershipProvider. ASP.NET
include, de asemenea, un ActiveDirectoryMembershipProvider, care
vă permite să utilizați caracteristicile de membru cu autentificarea
Windows printr-un server Active Directory. (Pentru mai multe informații
despre acest subiect, consultați Ajutorul Visual Studio.) În cele din
urmă, puteți utiliza un furnizor de membru personalizat pe care îl creați
dvs. sau un dezvoltator terț.
applicationName Specifică numele aplicației Web. Această setare este utilă în principal
dacă aveți mai multe aplicații web care utilizează aceeași bază de
date de membru. Dacă dați fiecăruia un nume de aplicație separat,
toate informațiile (inclusiv utilizatorul, profilurile și așa mai departe)
sunt complet separate, astfel încât să poată fi utilizate numai în
aplicația corespunzătoare.
connectionStringName* Specifică numele setării șirului de conexiune. Acesta trebuie să
corespundă unui șir de conexiune definit în secțiunea
<connectionStrings> din web.config sau machine.config.

descriere Permite o descriere opțională pentru furnizorul de membri.

677
CAPITOLUL APARTENENȚĂ
20

Atribut Descriere

passwordFormat Setează modul în care parolele sunt stocate în baza de date. Puteți
utiliza Clear (parolele sunt stocate ca atare, fără criptare), Encrypted
(parolele sunt criptate utilizând o cheie specifică computerului) sau
Hashed (parolele sunt hashed și valoarea hash este stocată în baza de
date). Hashing-ul parolelor oferă o protecție similară criptării acestora (și
anume, dacă vă uitați la hash, veți avea dificultăți în ingineria inversă a
parolei). Cu toate acestea, atunci când parolele sunt codificate, acestea
nu pot fi recuperate niciodată, ci doar resetate.
minRequiredPasswordLength Specifică lungimea minimă a unei parole. Dacă utilizatorul
introduce mai puține caractere la crearea unui cont, încercarea va
fi respinsă cu un mesaj de eroare.
minObligatoriuCaractere Specifică numărul de caractere nonalfanumerice (alte caractere decât cifre
nonalfanumerice și litere) pe care trebuie să le aibă parola. Dacă utilizatorul introduce mai
puține dintre aceste caractere la crearea unui cont, încercarea va fi
respinsă cu un mesaj de eroare. Deși solicitarea de caractere
Descărcați de la Wow! eBook <www.wowebook.com>

non-alfanumerice face parole mai puternice (mai puțin ghicitoare), poate,


de asemenea, să deruteze utilizatorii, determinându-i să-și uite parolele
mai des sau (mai rău) să le scrie într-un loc vizibil, unde ar putea fi furate.

maxInvalidPasswordAttempts Specifică de câte ori i se permite unui utilizator să introducă o parolă


nevalidă pentru conectarea sa înainte ca contul de utilizator să fie
blocat și să devină inaccesibil. Valoarea implicită este de 5 încercări.
passwordAttemptWindow Setează ora internă în care este măsurată maxInvalidPasswordTrys.
De exemplu, dacă setați o fereastră de 30 de minute, după 30 de
minute numărul de încercări de parolă nevalidă este resetat. Dacă
utilizatorul depășește maxInvalidPasswordAttempts în
passwordAttemptWindow, contul este blocat.
enablePasswordReset Determină dacă o parolă poate fi resetată, ceea ce este util dacă o
parolă este uitată.

enablePasswordRetrieval Determină dacă o parolă poate fi solicitată (și trimisă prin e-mail
utilizatorului), ceea ce este util dacă un utilizator uită o parolă. Această
caracteristică nu este acceptată niciodată dacă passwordFormat este
setat la hashed, deoarece parola nu este stocată în acest caz.
necesităÎntrebareșiRăspuns Determină dacă răspunsul de securitate pentru calitatea de membru va
fi necesar atunci când solicitați sau resetați o parolă de utilizator.

necesităUniqueEmail Dacă este fals, permite mai multor utilizatori să aibă aceeași adresă de
poștă electronică. Informațiile despre adresa de e-mail sunt întotdeauna
opționale. (Cu toate acestea, controlul CreateUserWizard necesită o
adresă de e-mail, cu excepția cazului în care setați RequireEmail la false.)

* Această setare este obligatorie.

678
CAPITOLUL APARTENENȚĂ
20

Acum că ați văzut setările pe care le puteți modifica, merită să întrebați care sunt valorile implicite. Dacă vă
uitați la secțiunea <membership> din fișierul machine.config, iată ce veți găsi:

<membership> <providers> <add name="AspNetSqlMembershipProvider"


type="System.Web.Security.SqlMembershipProvider ..."
connectionStringName="LocalSqlServer" enablePasswordRetrieval="false"
enablePasswordReset="true" requiresQuestionAndAnswer="true"
applicationName="/" requiresUniqueEmail="false"
passwordFormat="Hashed" maxInvalidPasswordAttempts="5"
minRequiredPasswordLength="7"
minRequiredNonalphanumericCharacters="1"
passwordAttemptWindow="10" /> </providers> </membership>

După cum puteți vedea, furnizorul implicit de membru este AspNetSqlMembershipProvider. Se conectează
utilizând șirul de conexiune LocalSqlServer și acceptă resetarea parolei, dar nu și recuperarea parolei.
Conturile necesită o întrebare de securitate, dar nu un e-mail unic. Parolele în sine sunt hash în baza de
date pentru securitate, astfel încât acestea nu pot fi recuperate. Parolele trebuie să aibă cel puțin șapte
caractere și cel puțin un caracter nonalfanumeric. În cele din urmă, dacă un utilizator face cinci încercări de
parolă nevalidă în zece minute, contul este dezactivat.

Crearea utilizatorilor cu WAT


După ce ați creat depozitul de date de membru și (opțional) ați configurat furnizorul de abonament, sunteți gata să
utilizați securitatea susținută de membru în aplicația dvs. După cum ați văzut deja, puteți crea utilizatori noi cu controlul
CreateUserWizard. Veți lua în considerare controlul CreateUserWizard și celelalte controale de securitate mai jos în
acest capitol. În primul rând, merită să luați în considerare celelalte opțiuni pentru configurarea listei de utilizatori.

O opțiune este utilizarea WAT. Alegeți Configurare ASP.NET site web pentru a lansa acest instrument. Următor
faceți clic pe fila Securitate. În colțul din stânga jos, o→
casetă indică câți utilizatori se află în prezent în
baza de date (a se vedea figura 20-3). Această casetă oferă, de asemenea, linkuri care vă permit să
examinați înregistrările de utilizator existente sau să adăugați altele noi.

679
CAPITOLUL APARTENENȚĂ
20

Figura 20-3. Gestionarea securității site-ului web cu WAT

Dacă doriți să răsfoiți lista curentă de utilizatori sau să actualizați o înregistrare de utilizator existentă, faceți
clic pe linkul Gestionare utilizatori. Pentru a adăuga utilizatori noi, faceți clic pe Creare utilizator. Veți vedea un
set de controale similare cu controlul CreateUserWizard utilizat anterior în pagina de testare (consultați Figura
20-4). După ce ați creat câțiva utilizatori, se recomandă să aruncați o altă privire la tabelele aspnet_Users și
aspnet_Membership din baza de date pentru a vedea cum arată înregistrările utilizatorilor.

680
CAPITOLUL 20
CALITATEA DE
MEMBRU

Figura 20-4. Crearea unui utilizator nou

Deși WAT este o modalitate perfect sensibilă de a adăuga înregistrări de utilizator, s-ar putea să descoperiți
că interfața web este puțin lentă dacă aveți un număr mare de utilizatori de creat. O altă opțiune este să
utilizați clasa de membru, așa cum se arată aici:
Creați o înregistrare de utilizator bazată pe numele de utilizator, parola și informațiile de poștă
electronică. Membership.CreateUser(nume de utilizator, parolă, e-mail);

Iată un exemplu cu valori codificate hard:


Membership.CreateUser("joes", "ignreto12__", "joes@domains.com");
Acest lucru creează un utilizator nou cu o singură linie de cod. Desigur, metoda CreateUser() are mai multe
supraîncărcări, pe care le puteți utiliza pentru a furniza detalii, cum ar fi întrebarea și răspunsul parolei.
Dacă nu ai modificat setările implicite pentru calitatea de membru, nu vei putea crea un cont decât dacă
furnizezi aceste detalii. În schimb, va trebui să utilizați această supraîncărcare mai complexă:
MembershipCreateStatus createStatus; Membership.CreateUser("joes",
"ignreto12__", "joes@domains.com", "Care este restaurantul tău preferat?",
"Saigon", true, out createStatus);
CAPITOLUL APARTENENȚĂ
20

Primii câțiva parametri se explică de la sine - iau numele de utilizator, parola, adresa de e-mail, întrebarea
despre parolă și răspunsul la parolă. Penultimul parametru ia o valoare booleană care determină dacă contul
primește semnalizatorul IsApproved. Dacă furnizați fals, contul nu va fi aprobat și, prin urmare, nu va fi activ
(și utilizabil) până când nu îl modificați utilizând metoda Membership.UpdateUser(). În supraîncărcarea mai
simplă, care nu include acest parametru, conturile sunt întotdeauna marcate ca aprobate.
Ultimul parametru returnează o valoare din enumerarea MembershipCreateStatus. Dacă această valoare nu
este MembershipCreateStatus.Success, a apărut o eroare la crearea înregistrării. Valoarea indică starea
exactă a erorii (de exemplu, o parolă care nu a fost suficient de puternică, o adresă de poștă electronică
dublată atunci când furnizorul de apartenență nu permite dubluri etc.). În supraîncărcarea mai simplă care nu
include MembershipCreateStatus, orice eroare are ca rezultat aruncarea unui obiect de excepție care are
aceleași informații.

Sfat: În mod clar, dacă trebuie să transferați un număr mare de conturi de utilizator dintr-o bază de date particularizată în depozitul de date de membru, cea mai rapidă opțiune ar fi să scrieți o rutină care să treacă prin înregistrările existente și să utilizați metoda CreateUser () pentru a le insera pe cele noi.

Clasele Membership și MembershipUser


Nu ar avea prea mult sens să utilizați depozitul de date de membru dacă totuși trebuie să scrieți manual ADO.NET
cod pentru a prelua sau modifica informațiile utilizatorului. De aceea, ASP.NET oferă un model mai convenabil, de
nivel superior, cu clasa de membru.
Calitatea de membru este o clasă utilă, plină de metode statice practice, cum ar fi CreateUser(). Îl puteți
găsi în spațiul de nume System.Web.Security. Tabelul 20-3 oferă un instantaneu al celor mai utile metode
statice.
Tabelul 20-3. Metode de membru

Metodă Descriere

CreateUser() Adaugă un utilizator nou la baza de date.

DeleteUser() Șterge un utilizator existent din baza de date. Specificați utilizatorul prin
numele de utilizator. De asemenea, puteți alege dacă doriți să ștergeți
toate datele asociate din alte tabele (implicit este să le eliminați).

UpdateUser() Actualizează baza de date cu informații noi pentru un anumit utilizator.

GetUser() Obține un anumit utilizator din baza de date, după numele de utilizator.

GetUserNameByEmail() Regăsește un nume de utilizator pentru utilizator care corespunde unei adrese de
poștă electronică date. Rețineți că adresele de e-mail duplicate sunt permise în
mod implicit, caz în care această metodă va găsi doar prima potrivire.

FindUsersByName() Obține utilizatori din baza de date de membri care se potrivesc cu un


nume de utilizator dat. Acest lucru acceptă potriviri parțiale, astfel încât
utilizatorul se va potrivi cu TestUser, User001 și așa mai departe.

682
CAPITOLUL APARTENENȚĂ
20

Metodă Descriere

FindUsersByEmail() Preia utilizatorii din baza de date de apartenență care corespund unui anumit mesaj de poștă electronică
adresă. De asemenea, puteți furniza o parte a unei adrese de poștă electronică (cum ar fi
nume domeniu), caz în care veți primi fiecare utilizator care are un e-mail
adresa care conține acest text.

GetAllUsers() Obține o colecție care reprezintă toți utilizatorii din baza de date. Un
Versiunea supraîncărcată a acestei metode vă permite să obțineți doar o porțiune
din lista completă de utilizatori (o singură pagină de utilizatori, pe baza unui index de pornire și
lungime).

GetNumberOfUsersOnline () Obține numărul de utilizatori conectați care accesează în prezent o aplicație.


Acest calcul presupune că un utilizator este online dacă ultima perioadă de activitate a utilizatorului respectiv

Ștampila se încadrează într-un termen stabilit (cum ar fi 20 de minute).

GeneratePassword() Generează o parolă aleatorie de lungimea specificată. Acest lucru este util
atunci când creați programatic noi înregistrări de utilizator.

ValidateUser() Verificați dacă numele de utilizator și parola furnizate există în


bază de date. Puteți utiliza această metodă pentru a scrie logica de autentificare în
pagina de conectare.

Clasa de membru oferă, de asemenea, proprietăți statice doar în citire care vă permit să regăsiți informații despre
configurația furnizorului de membru, așa cum este setat în fișierul de configurare. De exemplu, puteți prelua lungimea
necesară a parolei, numărul maxim de încercări de parolă și toate celelalte detalii descrise în tabelul 20-2.

Multe dintre aceste metode utilizează clasa MembershipUser, care reprezintă o înregistrare de utilizator. De
exemplu, când apelați GetUser(), primiți informațiile ca obiect MembershipUser. Dacă doriți să actualizați acel
utilizator, aveți posibilitatea să-i modificați proprietățile și apoi să apelați Membership.UpdateUser() cu
obiectul MembershipUser modificat.

Notă Obiectul MembershipUser combină detaliile din tabelul aspnet_Users și tabelul legat aspnet_ Apartenență. De exemplu, include întrebarea parolei. Cu toate acestea, răspunsul la parolă și parola în sine nu sunt disponibile.

Clasa MembershipUser oferă, de asemenea, propriul set mai mic de metode de instanță. Cele mai
importante sunt detaliate în tabelul 20-4.

683
CAPITOLUL APARTENENȚĂ
20

Tabelul 20-4. Metode de utilizare a calității de membru

Metodă Descriere

DeblocareUtilizator() Reactivează un cont de utilizator care a fost blocat pentru prea multe conectări nevalide
Încercări.

GetPassword() Preia o parolă de utilizator. Dacă este necesarÎntrebareșiRăspuns este adevărat în


configurația calității de membru (care este implicită), trebuie să furnizați
răspunsul la întrebarea privind parola pentru a prelua o parolă. Notă
că această metodă nu va funcționa deloc dacă setarea passwordFormat
este hashed sau dacă enablePasswordRetrieval este setat la false, care este, de asemenea,
implicit.

ResetPassword() Resetează o parolă de utilizator utilizând o parolă nouă, generată aleatoriu,


pe care această metodă le returnează. Dacă necesităÎntrebareșiRăspuns este adevărat în
Configurația calității de membru (care este implicită), trebuie să furnizați
Răspundeți la întrebarea despre parolă pentru a reseta o parolă. Poţi
Afișați noua parolă pentru utilizator sau trimiteți-o într-un e-mail.

ChangePassword() Modifică o parolă de utilizator. Trebuie să furnizați parola curentă pentru a


pentru a aplica unul nou.

ChangePasswordQuestion Modifică o parolă de utilizator, întrebare și răspuns. Trebuie să furnizați


AndAnswer() parola curentă pentru a schimba întrebarea de securitate.

Pentru a înțelege cum funcționează clasa de membru, puteți crea o pagină de test simplă care afișează o
listă a tuturor utilizatorilor din baza de date de membri. Figura 20-5 prezintă această pagină.

Figura 20-5. Obținerea unei liste de utilizatori

684
CAPITOLUL APARTENENȚĂ
20

Pentru a crea această pagină, trebuie doar să începeți prin definirea GridView. GridView va afișa o listă de
obiecte MembershipUser. Pentru fiecare utilizator, afișează valorile din proprietățile Nume utilizator și E-mail,
împreună cu un link Selectare. Iată marcajul care creează GridView (fără detaliile de formatare):

<asp:GridView ID="gridUsers" runat="server"


OnSelectedIndexChanged="gridUsers_SelectedIndexChanged"
AutoGenerateColumns="False" DataKeyNames="UserName" > <Columns>
<asp:BoundField DataField="UserName" HeaderText="User Name" />
<asp:BoundField DataField="Email" HeaderText="Email" /> <asp:CommandField
ShowSelectButton="True" /> </Columns> </asp:GridView>

Când pagina este încărcată pentru prima dată, apelează metoda Membership.GetAllUsers() și leagă
rezultatele la GridView, așa cum se arată aici:

vid protejat Page_Load(expeditor obiect, EventArgs e) {


gridUsers.DataSource = Membership.GetAllUsers();
gridUsers.DataBind(); }

Pentru a face exemplul mai interesant, atunci când este selectată o înregistrare, obiectul MembershipUser
corespunzător este regăsit. Acest obiect este apoi adăugat la o colecție, astfel încât să poată fi legat la
DetailsView pentru afișarea automată:
protejat void gridUsers_SelectedIndexChanged(expeditor obiect, EventArgs e) { List<MembershipUser> list = new
List<MembershipUser>(); listă. Add(Membership.GetUser(gridUsers.SelectedValue.ToString())); detailsUser.DataSource = listă;
detailsUser.DataBind(); }

Iată DetailsView care face treaba (din nou, fără detaliile de formatare):

<asp:DetailsView ID="detailsUser" runat="server"></asp:DetailsView>


Acest DetailsView utilizează crearea automată a rândurilor (deoarece AutoGenerateRows este implicit true). Drept
urmare, DetailsView afișează toate proprietățile MembershipUser.
Figura 20-6 prezintă informațiile disponibile într-o singură înregistrare. Printre alte detalii, puteți utiliza
obiectul MembershipUser pentru a verifica dacă un utilizator este online, când a accesat ultima dată
sistemul și care este adresa sa de e-mail.

685
CAPITOLUL APARTENENȚĂ
20

Figura 20-6. Informațiile dintr-un obiect MembershipUser

Autentificare cu calitatea de membru


Acum că ați trecut la calitatea de membru și toți utilizatorii sunt stocați în depozitul de date de membru,
trebuie să schimbați modul în care funcționează pagina de conectare. Viața devine acum mult mai simplă: în
loc să creați obiecte ADO.NET pentru a interoga o bază de date și a vedea dacă există o înregistrare de
utilizator care se potrivește, puteți lăsa clasa Membru să efectueze toată munca pentru dvs. Metoda de care
aveți nevoie este Membership.ValidateUser(). Este nevoie de un nume de utilizator și o parolă și returnează
true dacă există o potrivire validă în baza de date. Iată noul cod de care aveți nevoie în pagina de conectare:
vid protejat cmdLogin_Click(expeditor obiect, EventArgs e)
{

686
CAPITOLUL APARTENENȚĂ
20

dacă (Membership.ValidateUser(txtName.Text, txtPassword.Text)) } {


FormsAuthentication.RedirectFromLoginPage(txtName.Text, false); else } {
lblStatus.Text = "Nume de utilizator sau parolă nevalidă.";

De fapt, un pic de muncă are loc în spatele scenei. Dacă utilizați setările implicite ale furnizorului de
abonament, parolele sunt codificate. Asta înseamnă că atunci când apelați ValidateUser(), ASP.NET hash
parola nou furnizată utilizând același algoritm hash și apoi o compară cu parola hash stocată în baza de
date.

Conturi dezactivate
Un cont poate fi dezactivat în baza de date de membri în două moduri:

Contul nu este aprobat: acest lucru se întâmplă dacă creați un cont în mod programatic și furnizați false
pentru parametrul isApproved. Puteți face acest pas dacă doriți să creați automat un cont, dar permiteți
unui administrator să îl examineze înainte de a deveni live. Pentru a activa acest cont, trebuie să
obțineți un obiect MembershipUser pentru înregistrarea de utilizator corespunzătoare, să setați
MembershipUser.IsApproved la true și să apelați Membership.UpdateUser().
Contul este blocat: Acest lucru se întâmplă dacă utilizatorul face mai multe încercări de a accesa un cont
de utilizator cu o parolă nevalidă. În acest caz, trebuie să obțineți un obiect MembershipUser pentru
utilizator și să apelați MembershipUser.UnlockUser(). De asemenea, poate doriți să apelați
MembershipUser.ResetPassword() pentru a preveni o altă blocare.

Pentru a vă ajuta cu aceste activități, poate doriți să creați o pagină administrativă ca cea prezentată în Figura 20-6. De
exemplu, puteți permite unui utilizator să examineze toate conturile care nu sunt încă aprobate și să le aprobe dând clic
pe un buton.
În mod similar, dacă doriți să dezactivați un cont în orice moment, puteți să regăsiți un obiect MembershipUser pentru
acel utilizator și să setați proprietatea IsApproved la false. Cu toate acestea, nu aveți nici o modalitate de a bloca
programatic un cont de utilizator.
Probabil că vă gândiți deja la o gamă largă de pagini pe care le puteți crea folosind clasele Membership și
MembershipUser. De exemplu, puteți crea pagini care permit utilizatorilor să solicite o resetare a parolei sau
să verifice dacă sunt blocați. Cu toate acestea, este posibil să nu fie nevoie să creați toate aceste pagini,
deoarece ASP.NET include un set bogat de controale de securitate care automatizează multe activități
obișnuite. Veți afla mai multe despre controalele de securitate în secțiunea următoare.

Controalele de securitate
Caracteristicile de bază ale calității de membru economisesc timp remarcabil. Acestea vă permit să vă concentrați
asupra programării aplicației web, fără să vă faceți griji cu privire la gestionarea securității și crearea bazei de date
perfecte sau a informațiilor despre utilizator. În schimb, puteți utiliza clasele de membru de nivel superior și
MembershipUser pentru a face tot ce aveți nevoie.
Cu toate acestea, funcția de membru ASP.NET nu se oprește aici. Clasa Membership nu numai că simplifică sarcinile
comune de securitate, ci le și standardizează. Drept urmare, alte componente și controale pot utiliza clasa Membership
pentru a se integra cu modelul de securitate ASP.NET, fără a vă face griji cu privire la specificul fiecărei aplicații web.

687
CAPITOLUL APARTENENȚĂ
20

Puteți găsi cel mai bun exemplu al acestei noi flexibilități în controalele de securitate ale ASP.NET. Aceste controale
interacționează cu furnizorul de membru utilizând metodele claselor Membership și MembershipUser pentru a
implementa biți comuni ai interfețelor cu utilizatorul, cum ar fi o pagină de conectare, un set de controale de creare a
utilizatorului și un expert de recuperare a parolei. Tabelul 20-5 listează toate controalele de securitate ASP.NET care
funcționează cu calitatea de membru. În Visual Studio, puteți găsi aceste controale în secțiunea Login din Toolbox.

Tabelul 20-5. Controale de securitate

Controla Descriere

Login Afișează casetele de text familiare pentru numele de utilizator și parolă, cu un buton de conectare.

LoginStatus Afișează un buton de conectare, dacă utilizatorul nu este deja conectat, care redirecționează utilizatorul către
pagina de conectare configurată. În caz contrar, afișează un buton de deconectare. Poţi
Alegeți testul utilizat pentru butoanele de conectare și deconectare, dar cam atât.

LoginName Afișează numele de utilizator al utilizatorului conectat.

LoginView Afișează conținut diferit, în funcție de faptul dacă utilizatorul este conectat. Tu
poate chiar să utilizeze acest control pentru a afișa conținut diferit pentru diferite grupuri de utilizatori,
Descărcați de la Wow! eBook <www.wowebook.com>

sau roluri.

Recuperare parolă Permite utilizatorului să solicite o parolă prin e-mail sau să o reseteze. De obicei, utilizatorul
trebuie să furnizeze răspunsul la întrebarea de securitate pentru a obține parola.

Schimbare parolă Permite utilizatorului să seteze o nouă parolă (atâta timp cât utilizatorul poate furniza parola curentă
parola).

CreateUserWizard Permite unui utilizator să creeze o înregistrare nouă, completată cu adresa de e-mail și o
întrebare și răspuns cu parolă.

Există o modalitate simplă și o modalitate complexă de a utiliza majoritatea acestor controale. La modul cel mai simplu,
pur și simplu aruncați controlul pe o pagină, fără a scrie o linie de cod. (Ați văzut această abordare cu controlul
CreateUserWizard la începutul acestui capitol.) De asemenea, puteți să modificați proprietățile, să gestionați evenimente
și chiar să creați șabloane pentru a personaliza aceste controale.
În secțiunile următoare, veți arunca o privire mai atentă asupra controalelor de conectare,
PasswordRecovery și CreateUserWizard. Și mai târziu, în secțiunea "Securitate bazată pe roluri", veți
pune controlul LoginView la lucru pentru a afișa conținut diferit utilizatorilor din roluri diferite.

Controlul de conectare
Până în prezent, site-urile securizate pe care le-ați văzut au folosit pagini de conectare realizate manual. În multe site-uri
web, acest lucru este ceea ce veți dori - la urma urmei, vă oferă control complet pentru a ajusta interfața cu utilizatorul
exact așa cum doriți. Cu toate acestea, o pagină de conectare este standard, deci este logic ca ASP.NET să ofere
dezvoltatorilor câteva comenzi rapide suplimentare care le pot salva munca.
În acest sens, ASP.NET include un control de conectare care asociază un nume de utilizator și o casetă de
text pentru parolă cu un buton de conectare. Controlul Login adaugă, de asemenea, câteva caracteristici:
• Include controale de validare care împiedică postarea paginii înapoi până când nu au
fost introduse un nume de utilizator și o parolă. Acești validatori utilizează validarea
pe partea client dacă este acceptată de browser (cu ajutorul unui pic de JavaScript) și
validarea pe partea serverului, așa cum este descris în capitolul 9.

688
CAPITOLUL APARTENENȚĂ
20

• Gestionează automat procesul de conectare și redirecționare atunci când


utilizatorul se conectează cu succes. Dacă sunt introduse acreditări de conectare
nevalide, se afișează un mesaj de eroare.
• Oferă o casetă de selectare Ține-mă minte care, dacă este selectată, stochează un
cookie persistent care rămâne pe termen nelimitat pe computerul utilizatorului; prin
urmare, utilizatorul nu trebuie să se conecteze din nou la începutul fiecărei vizite.
Cu alte cuvinte, dacă controlul de bază Login este potrivit pentru nevoile dvs. (oferă interfața cu utilizatorul dorită),
nu va trebui să scrieți o linie de cod.
Pentru a încerca acest lucru, plasați controlul Login pe o pagină nouă. Asigurați-vă că această pagină este
denumită Login.aspx astfel încât să fie utilizată ca pagină de conectare implicită pentru autentificarea
formularelor (sau editați eticheta <formulare> pentru a alege o altă pagină de conectare, așa cum este
explicat în capitolul anterior). Apoi, rulați pagina. Veți vedea interfața de bază prezentată în Figura 20-7.

Figura 20-7. Controlul de conectare și o încercare de conectare nereușită

Deși controlul de conectare se ocupă automat de procesul de conectare pentru dvs., puteți interveni cu
propriul cod personalizat. Pentru a face acest lucru, trebuie să reacționezi la unul dintre evenimentele de
control al conectării, așa cum sunt enumerate în tabelul 20-6.
Tabelul 20-6. Evenimente ale controlului de conectare

Eveniment Descriere

Logare Ridicat înainte ca utilizatorul să fie autentificat.

LoggedIn Ridicat după ce utilizatorul a fost autentificat de control.

Eroare de conectare Ridicat atunci când încercarea de conectare eșuează (de exemplu, dacă
utilizatorul introduce parola greșită).

Autentifica Ridicat pentru a autentifica utilizatorul. Dacă gestionați acest eveniment, depinde de
dvs. să furnizați codul de conectare - controlul de conectare nu va efectua nicio acțiune.

689
CAPITOLUL APARTENENȚĂ
20

Evenimentele LoggingIn, LoggedIn și LoginError sunt utile în primul rând dacă doriți să actualizați alte controale
pentru a afișa anumite informații pe baza procesului de conectare. De exemplu, după prima eroare de
conectare, puteți alege să afișați un link care redirecționează utilizatorul către o pagină de recuperare a parolei:

vid protejat Login1_LoginError(expeditor obiect, EventArgs e)


{
lblStatus.Text = "Ți-ai uitat parola?";
lnkRedirectToPasswordRetrieval.Visible = adevărat;
}
Evenimentul Authenticate este cel mai important eveniment. Vă permite să vă scrieți propria logică de autentificare, așa
cum ați făcut în capitolul anterior. Acest lucru este de obicei util în două situații. În primul rând, poate doriți să completați
verificarea implicită în controlul Login cu alte cerințe (de exemplu, împiedicați utilizatorii să se conecteze la anumite ore
ale zilei, permiteți utilizatorilor să se conecteze numai dacă au introdus informații într-un alt control și așa mai departe).
Celălalt motiv pentru care ați putea gestiona evenimentul Authenticate este dacă nu utilizați deloc furnizorul de membru.
În acest caz, puteți utiliza în continuare controlul Login, atâta timp cât furnizați logica de autentificare.

În rutina de tratare a evenimentelor Autentificare, puteți verifica numele de utilizator și parola utilizând proprietățile
Nume utilizator și Parolă ale controlului Login. Apoi setați proprietatea autentificată a AuthenticateEventArgs la
adevărat sau fals. Dacă este adevărat, evenimentul LoggedIn este ridicat în continuare, apoi utilizatorul este
redirecționat către Login.DestinationPageUrl (sau pagina originală din care a venit utilizatorul dacă proprietatea
DestinationPageUrl nu este setată). Dacă setați autentificat la false, evenimentul LoginError este ridicat în
continuare și controlul afișează mesajul de eroare definit de proprietatea Login.FailureText. Iată o rutină de tratare
a evenimentelor pentru evenimentul autentificat care utilizează direct clasele de membru:
protejat void Login1_Authenticate(expeditor obiect, AuthenticateEventArgs e) { if (Membership.ValidateUser(Login1.UserName,
Login1.Password)) {

e.autentificat = adevărat; }
altceva {

e.autentificat = fals;
}}

Aceasta acoperă tot ce trebuie să știți despre interacțiunea cu controlul de conectare, dar puteți modifica multe
proprietăți pentru a configura aspectul controlului de conectare. Există chiar și un link de formatare automată pe care îl
puteți alege din fereastra Proprietăți (sau eticheta inteligentă) pentru a oferi controlului de conectare un lifting facial cu
un singur clic.
Cele mai puternice proprietăți de formatare pentru controlul Login sunt proprietățile stilului, care vă permit să
modificați fonturile, colorarea și alinierea pentru părți individuale ale controlului. Ați văzut deja stiluri la lucru
cu alte câteva controale, inclusiv Calendar (Capitolul 10) și GridView (Capitolul 16) și funcționează în același
mod cu controalele de securitate. Tabelul 20-7 detaliază proprietățile de stil ale controlului de conectare.

690
CAPITOLUL APARTENENȚĂ
20

Tabelul 20-7. Proprietățile de stil ale controlului de conectare

Stil Descriere

TitluTextStil Definește un stil pentru textul titlului controlului Login.

LabelStyle Definește stilul pentru etichetele Nume utilizator și Parolă.

TextBoxStyle Definește stilul casetelor text pentru numele de utilizator și parolă.

LoginButtonStyle Definește stilul pentru butonul de conectare.

FailureTextStyle Definește stilul pentru textul afișat dacă încercarea de conectare eșuează.

Stil casetă de selectare Definește proprietățile de stil pentru caseta de selectare Ține-mă minte.

ValidatorTextStyle Definește stiluri pentru controalele RequiredFieldValidator care validează numele de utilizator
și informații despre parolă. Aceste proprietăți de stil modifică modul în care textul de eroare
Arată. (În mod implicit, textul de eroare este pur și simplu un asterisc care apare lângă
Casetă text goală.)

HyperLinkStyle Configurează toate linkurile afișate de controlul Login. Aceasta include link-urile
care vă permit să creați o nouă înregistrare de utilizator, să regăsiți o parolă și așa mai departe. Acești
linkurile apar numai dacă ați setat CreateUserUrl și PasswordRecoveryUrl
proprietăți.

InstructionTextStyle Formatează Login.InstructionText, care este textul instrucțiunilor de ajutor pe care îl puteți adăuga
sub controlul Login. În mod implicit, controlul Login nu are text cu instrucțiuni.

Desigur, stilurile nu sunt singura caracteristică pe care o puteți modifica în controlul de conectare. Puteți ajusta mai
multe proprietăți pentru a modifica textul utilizat și pentru a adăuga linkuri. De exemplu, următoarea etichetă pentru
controlul Login ajustează formatarea și utilizează proprietățile CreateUserUrl și PasswordRecoveryUrl pentru a
adăuga linkuri la o pagină pentru înregistrarea unui utilizator nou și alta pentru recuperarea unei parole pierdute.
(Evident, va trebui să creați ambele pagini pentru ca linkurile să funcționeze.)

<asp:Login ID="Login1" runat="server" BackColor="#EFF3FB" BorderColor="#B5C7DE"


BorderPadding="4" BorderStyle="Solid" BorderWidth="1px" Font-Names="Verdana"
ForeColor="#333333" height="256px" width="368px" CreateUserText="Înregistrează-te
pentru prima dată" CreateUserUrl="Register.aspx" PasswordRecoveryText="Ai uitat
parola?" PasswordRecoveryUrl="PasswordRecovery.aspx" InstructionText= "Vă rugăm să
introduceți numele de utilizator și parola pentru conectarea la sistem." >

<TitleTextStyle BackColor="#507CD1" font-bold="true" font-size="large"


forecolor="white" height="35px" /> <instructionTextStyle font-italic="true"
ForeColor="Black" /> <LoginButtonStyle BackColor="White" BorderColor="#507CD1"
borderstyle="solid" borderwidth="1px" font-names="verdana" ForeColor="#284E98" />

691
CAPITOLUL APARTENENȚĂ
20

</asp:Autentificare>

Figura 20-8 prezintă controlul de conectare reînnoit. Tabelul 20-8 explică celelalte proprietăți ale autentificării
controla.

Figura 20-8. Un control de conectare formatat

Tabelul 20-8. Proprietăți utile ale controlului de conectare

Proprietate Descriere

TitleText Textul afișat în titlul controlului.

InstrucțiuneText Textul afișat chiar sub titlu, dar deasupra comenzilor de conectare. În
mod implicit, controlul Login nu are text cu instrucțiuni.

FailureText Textul afișat atunci când o încercare de conectare nu reușește.

UserNameLabelText Textul afișat înaintea casetei text nume utilizator.

PasswordLabelText Textul afișat înaintea casetei text pentru parolă.

Mesaj de eroare Mesajul de eroare afișat de RequiredFieldValidator dacă utilizatorul nu tastează


UsernameRequiredError un nume de utilizator. În mod implicit, acesta este pur și simplu un asterisc (*).

692
CAPITOLUL APARTENENȚĂ
20

Proprietate Descriere

PasswordRequiredError Mesajul de eroare afișat de RequiredFieldValidator dacă utilizatorul nu


Mesaj tastează o parolă. În mod implicit, acesta este pur și simplu un asterisc (*).

LoginButtonText Textul afișat pentru butonul de conectare.

LoginButtonType Tipul de control al butonului utilizat ca buton de conectare. Poate fi


afișat ca Link, Buton sau Imagine.

LoginButtonImageUrl URL-ul care indică imaginea pe care doriți să o afișați pentru butonul de conectare.
Trebuie să setați proprietatea LoginButtonStyle la Image pentru a utiliza această
proprietate.
DestinationPageUrl Pagina către care este redirecționat utilizatorul dacă încercarea de conectare reușește.
Această proprietate este necompletată în mod implicit, ceea ce înseamnă că controlul
Login utilizează infrastructura formularelor și redirecționează utilizatorul către pagina
solicitată inițial (sau către defaultUrl configurat în fișierul web.config).

DisplayRememberMe Determină dacă se va afișa caseta de selectare Ține-mă minte. Se recomandă să


eliminați această opțiune pentru a asigura o securitate mai strictă, astfel încât
utilizatorii rău intenționați să nu poată avea acces la site-ul dvs. prin intermediul
computerului altui utilizator.
RememberMeSet Setează valoarea implicită pentru caseta de selectare Ține-mă minte. În mod
implicit, această opțiune este setată la false, ceea ce înseamnă că caseta de
selectare nu este bifată inițial.
VisibleWhenLoggedIn Dacă este setat la false, controlul de conectare se ascunde automat dacă
utilizatorul este deja conectat. Dacă este setat la true (implicit), controlul
Login este afișat chiar dacă utilizatorul este deja conectat.

CreateUserUrl Furnizează o adresă URL către o pagină de înregistrare a utilizatorului.


Această proprietate este utilizată împreună cu CreateUserText.

CreateUserText Setează textul pentru un link către pagina de înregistrare a utilizatorului. Dacă
acest text nu este furnizat, acest link nu este afișat în controlul de conectare.

CreateUserIconUrl Furnizează o adresă URL către o imagine care va fi afișată alături


de linkul CreateUserText pentru înregistrarea utilizatorului.

HelpPageUrl Furnizează o adresă URL unei pagini cu informații de ajutor.

HelpPageText Setează textul pentru legătura către pagina de ajutor. Dacă acest text nu
este furnizat, acest link nu este afișat în controlul de conectare.

HelpPageIconUrl Furnizează o adresă URL unei imagini care va fi afișată alături de


HelpPageText pentru linkul paginii de ajutor.

PasswordRecoveryUrl Furnizează o adresă URL către o pagină de recuperare a parolei.

693
CAPITOLUL APARTENENȚĂ
20

Proprietate Descriere

PasswordRecoveryText Setează textul pentru legătura către pagina de recuperare a parolei. Dacă acest text nu este
furnizat, acest link nu este afișat în controlul de conectare.

PasswordRecoveryIcon Furnizează o adresă URL către o imagine care va fi afișată alături de


Adresa URL PasswordRecoveryText pentru linkul paginii de recuperare a parolei.

Pentru a completa exemplul din Figura 20-8, trebuie să creați paginile Register.aspx și
PasswordRecovery.aspx. În secțiunile următoare, veți afla cum puteți face acest lucru cu ușurință
folosind încă două dintre controalele de securitate ASP.NET.

Controlul CreateUserWizard
Ați utilizat deja controlul CreateUserWizard pentru a crea o înregistrare de utilizator de bază la începutul acestui
capitol. Acum că ați văzut flexibilitatea controlului de conectare, nu ar trebui să fie o surpriză să aflați că aveți la
fel de multe opțiuni pentru modificarea aspectului și comportamentului controlului CreateUserWizard.

Controlul CreateUserWizard funcționează în doi pași. Primul pas colectează informațiile de utilizator necesare pentru
a genera înregistrarea utilizatorului. Al doilea pas afișează un mesaj de confirmare după crearea contului.

În general, CreateUserWizard oferă un număr amețitor de proprietăți pe care le puteți ajusta. Cu toate
acestea, ajută la înțelegerea faptului că există într-adevăr doar trei tipuri de proprietăți:

Proprietăți de stil care formatează doar o secțiune a controlului: De exemplu, TitleTextStyle


configurează modul în care este formatat titlul textului.
Proprietăți care setează textul pentru control: De exemplu, puteți configura fiecare etichetă, textul de
succes și mesajele afișate în diferite condiții de eroare. De asemenea, puteți prelua sau seta valorile
din fiecare casetă text.
Proprietăți care ascund sau afișează o parte a controlului: De exemplu, puteți utiliza DisplaySideBar,
DisplayCancelButton și RequireEmail pentru a afișa sau a ascunde bara laterală, butonul de anulare și,
respectiv, caseta de text pentru e-mail.

Controlul CreateUserWizard oferă, de asemenea, un set familiar de evenimente, inclusiv CreatingUser,


CreatedUser și CreateUserError. Încă o dată, aceste evenimente sunt utile pentru sincronizarea altor
controale de pe pagină sau pentru suprascrierea procesului de creare a utilizatorului dacă decideți să nu
utilizați funcțiile de membru.

Sfat În mod implicit, utilizatorii nou creați sunt conectați automat. Puteți modifica acest comportament setând proprietatea CreateUserWizard.LoginCreatedUser la false. De asemenea, puteți seta proprietatea ContinueDestinationPageUrl pentru a seta adresa URL unde ar trebui să fie redirecționat utilizatorul după crearea noii înregistrări.

Destul de interesant, controlul CreateUserWizard moștenește de la controlul Wizard pe care l-ați explorat în capitolul 10. Drept
urmare, puteți adăuga câți pași suplimentari doriți, la fel cum puteți cu controlul Expert. Acești pași pot efectua alte activități,

694
CAPITOLUL APARTENENȚĂ
20

cum ar fi înscrierea utilizatorului pentru a primi un buletin informativ regulat. Cu toate acestea, procesul efectiv de
creare a utilizatorului trebuie să aibă loc întotdeauna într-un singur pas. De exemplu, luați în considerare marcajul
pentru CreateUserWizard de bază:

<asp:CreateUserWizard ID="CreateUserWizard1" runat="server">


<WizardSteps> <asp:CreateUserWizardStep runat="server">
</asp:CreateUserWizardStep> <asp:CompleteWizardStep
runat="server"> </asp:CompleteWizardStep> </WizardSteps>
</asp:CreateUserWizard>

În esență, CreateUserWizard este un control Wizard care acceptă două tipuri de pași specializați: un
CreateUserWizardStep unde sunt colectate informațiile despre utilizator și este creată înregistrarea utilizatorului și un
CompleteWizardStep unde este afișat mesajul de confirmare.
Următorul exemplu arată cum puteți adăuga un WizardStep obișnuit în această secvență. În acest caz,
pasul suplimentar oferă pur și simplu câteva opțiuni suplimentare pentru utilizatorul nou creat (și anume,
alegerea de a vă abona la buletine informative automate prin e-mail).
<asp:CreateUserWizard ID="CreateUserWizard1" runat="server"
DisplaySideBar="True" ... > <WizardSteps> <asp:CreateUserWizardStep
runat="server" title="Create User"> </asp:CreateUserWizardStep>

<asp:WizardStep runat="server" title="Aboneaza-te"> Doriți să vă înscrieți la


următoarele buletine informative?<br /> <br />

<asp:CheckBoxList ID="chkSubscription" runat="server">


<asp:ListItem>MSN Today</asp:ListItem> <asp:ListItem>VB
Planet</asp:ListItem> <asp:ListItem>The High-Tech
Herald</asp:ListItem> </asp:CheckBoxList> </asp:WizardStep>

<asp:CompleteWizardStep runat="server">
</asp:CompleteWizardStep> </WizardSteps>
</asp:CreateUserWizard>

Figura 20-9 prezintă primii doi pași. Observați că apare bara laterală (deoarece
proprietatea CreateUserWizard.DisplaySidebar este setată la true) pentru a afișa ordinea
pașilor.

695
CAPITOLUL APARTENENȚĂ
20

Figura 20-9. Un CreateUserWizard cu un pas particularizat

Depinde în continuare de dvs. să efectuați acțiunea corespunzătoare în codul dvs., reacționând la unul dintre
evenimentele CreateUserWizard. În acest caz, utilizați evenimentul FinishButtonClick, deoarece apare la ultimul pas
înainte de mesajul de finalizare. Dacă plasați pasul mai devreme în secvență, va trebui să reacționați la NextButtonClick.
În exemplul curent, poate doriți să adăugați aceste informații la tabelul de profil al utilizatorului. Veți învăța cum să utilizați
profilurile în capitolul următor.
Pentru aspect complet și putere de formatare, puteți converti unul dintre pașii CreateUserWizard într-un șablon.
Apoi, sunteți liber să rearanjați conținutul existent și să adăugați noi controale și conținut HTML. Cu toate acestea,
aveți grijă să nu eliminați niciunul dintre elementele necesare. CreateUserWizard va lansa o excepție dacă
încercați să o utilizați, dar vă lipsește una dintre casetele text necesare pentru informații despre cont.
Cel mai simplu mod de a converti un pas într-un șablon este să utilizați linkurile etichetelor inteligente. Mai întâi, selectați
CreateUserControl pe suprafața de proiectare a paginii web din Visual Studio. Apoi, faceți clic pe pictograma săgeată
care apare lângă colțul din dreapta sus pentru a afișa eticheta inteligentă. Apoi, selectați linkul Particularizare pas
utilizator sau linkul Personalizare pas finalizat, în funcție de pasul pe care doriți să îl modificați. ASP.NET va insera apoi
controalele într-un șablon din eticheta de control CreateUserWizard.
De exemplu, imaginați-vă că doriți să afișați opțiunile selectate de utilizator în pasul personalizat din
rezumatul final. În acest caz, se recomandă să adăugați un nou control Etichetă, așa cum se arată aici:

<asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">


<ContentTemplate> <table border="0" style="..."> <tr> <td align="center" colspan="2"
style="..."> Complete </td> </tr> <tr> <td> Contul dvs. a fost creat cu succes.<br /><br
/>

696
CAPITOLUL APARTENENȚĂ
20

V-ați abonat la: <asp:Label ID="lblSubscriptionList" runat="server"> </asp:Label> </td> </tr> <tr> <td align="right" colspan="2">
<asp:Button ID="ContinueButton" runat="server" BackColor="Alb" BorderColor="#507CD1" BorderStyle="solid"
BorderWidth="1px" CausesValidation="false" CommandName="continue" font-names="verdana" ForeColor="#284E98"
text="continue" validationgroup="createuserwizard1" /> </td> </tr> </tabel> </ContentTemplate> </asp:CompleteWizardStep>

Acum, când utilizatorul trece la ultimul pas, puteți completa eticheta cu informațiile din controlul CheckBoxList. Cu
toate acestea, deoarece controalele Etichetă și CheckBoxList sunt plasate într-un șablon, nu le puteți accesa direct
după nume. În schimb, trebuie să le extrageți din controlul CreateUserWizard. Pentru a obține eticheta, trebuie să
accesați pasul complet, să luați primul control pe care îl conține (care este șablonul de conținut) și apoi să utilizați
metoda FindControl () pentru a căuta eticheta. Pentru a obține CheckBoxList, efectuați o operațiune similară, cu
excepția faptului că utilizați metoda FindControl() a CreateWizardControl în sine, care caută toți pașii obișnuiți.
Iată codul care efectuează această activitate:

protejat void CreateUserWizard1_FinishButtonClick(expeditor obiect,


WizardNavigationEventArgs e)
{
Etichetă lbl = (Etichetă)CreateUserWizard1.CompleteStep.Controls[0]. FindControl(
"lblSubscriptionList"); CheckBoxList chk =
(CheckBoxList)CreateUserWizard1.FindControl( _ "chkSubscription");

selecție șir = ""; foreach (element ListItem în


chkSubscription.Items) { if (item. Selectat) selecție +=
"<br />" + element. Text; } lbl. Text = selecție;

Figura 20-10 prezintă pasul final.

697
CAPITOLUL APARTENENȚĂ
20
Descărcați de la Wow! eBook <www.wowebook.com>

Figura 20-10. Îmbunătățirea pasului complet cu conținut suplimentar

Controlul PasswordRecovery
Controlul PasswordRecovery este util atunci când utilizatorii își uită parolele. Le permite să-și recupereze
parola folosind un vrăjitor scurt.
Controlul PasswordRecovery conduce utilizatorul prin trei pași. În primul rând, solicită numele de utilizator.
Apoi, afișează întrebarea de securitate și solicită răspunsul (cu excepția cazului în care ați setat setarea
requiresQuestionAndAnswer la false în fișierul web.config, caz în care controlul PasswordRecovery omite
complet acest pas). În cele din urmă, controlul PasswordRecovery trimite un e-mail la adresa de e-mail a
utilizatorului. Dacă utilizați un format de parolă criptat sau golit (consultați tabelul 20-2), mesajul de poștă
electronică conține parola originală. Dacă utilizați formatul implicit de parolă hashed, se generează o nouă
parolă aleatorie și acea parolă este trimisă în e-mail. În orice caz, ultimul pas afișează un mesaj de
confirmare care vă informează că e-mailul a fost trimis. Figura 20-11 prezintă controlul PasswordRecovery în
acțiune.

698
CAPITOLUL APARTENENȚĂ
20

Figura 20-11. Solicitarea unei parole

699
CAPITOLUL APARTENENȚĂ
20

Pentru ca controlul PasswordRecovery să funcționeze, computerul trebuie să aibă un server SMTP


configurat corect, iar utilizatorul trebuie să aibă o adresă de poștă electronică în înregistrarea utilizatorului.

Notă Puteți configura serverul SMTP selectând controlul PasswordRecovery și selectând Administrare site web din eticheta inteligentă. Apoi, selectați fila Aplicație și faceți clic pe linkul Configurare setări poștă electronică SMTP.

Dacă aplicația nu îndeplinește aceste două cerințe - nu puteți trimite mesaje de poștă electronică sau nu aveți
garanția că utilizatorii au o adresă de poștă electronică - puteți afișa noua parolă direct în pagină. Cea mai
ușoară abordare este gestionarea evenimentului PasswordRecovery.SendingMail. Mai întâi, setați
proprietatea MailMessageEventArgs.Cancel la true pentru a împiedica trimiterea mesajului. Apoi, aveți
posibilitatea să regăsiți conținutul mesajului din obiectul MailMessageEventArgs.Message și să îl afișați pe
pagină. Iată un exemplu:
protejat void PasswordRecovery1_SendingMail(expeditor obiect, MailMessageEventArgs e)
{
e.Cancel = true; PasswordRecovery1.SuccessText =
e.Message.Body;
}

Când utilizați acest rutină de tratare a evenimentelor, veți vedea un pas final precum cel prezentat în Figura 20-12.

Figura 20-12. Afișarea parolei preluate sau regenerate

Desigur, pentru flexibilitate completă, vă puteți crea propria pagină care resetează parolele. Trebuie doar să
utilizați metodele claselor Membership și MembershipUser descrise mai devreme.

Securitate bazată pe roluri


Exemplele de autentificare pe care le-ați examinat până acum oferă o abordare de totul sau nimic care fie interzice, fie
permite unui utilizator. În multe cazuri, totuși, o aplicație trebuie să recunoască diferite niveluri de utilizatori. Unii utilizatori

700
CAPITOLUL APARTENENȚĂ
20

vor beneficia de un set limitat de capacități, iar altor utilizatori li se poate permite să efectueze modificări potențial periculoase
sau să utilizeze porțiunile administrative ale unui site web.
Pentru a permite acest tip de acces pe mai multe niveluri, aveți nevoie de caracteristica de autorizare bazată pe roluri a
ASP.NET. Ca și în cazul calității de membru, ASP.NET are grijă să stocheze informațiile despre rol și să le pună la
dispoziția codului dvs. Tot ce trebuie să faceți este să creați rolurile, să atribuiți utilizatori fiecărui rol și apoi să testați
apartenența la rol în codul dvs.
Înainte de a putea utiliza autorizarea bazată pe roluri, trebuie să o activați. Deși puteți efectua acest pas
folosind WAT (trebuie doar să faceți clic pe linkul Activare roluri din fila Securitate), este destul de ușor doar
să adăugați direct linia necesară în fișierul web.config:
<configuration> <system.web>
<roleManager enabled="true" /> ...

</system.web>
...
</configurare>

Ca și în cazul depozitului de date de membru, ASP.NET va crea automat informațiile despre rol în fișierul
aspnetdb.mdf utilizând SQL Server Express. Dacă doriți să utilizați o altă bază de date, trebuie să urmați
pașii discutați anterior în acest capitol pentru a crea baza de date utilizând aspnet_regsql.exe și a modifica
șirul de conexiune.

Crearea și atribuirea rolurilor


După ce activați gestionarea rolurilor, trebuie să creați un set de bază de roluri (de exemplu, Utilizator,
Administrator, Invitat și așa mai departe). Apoi puteți atribui utilizatori unuia sau mai multor grupuri.
Puteți crea roluri în două moduri. Puteți face acest lucru programatic sau puteți face acest lucru manual, folosind
WAT.
Pentru a utiliza WAT, urmați acești pași:
1. Lansați WAT selectând Website ASP.NET Configuration.

2. Faceți clic pe fila Securitate.
3. Faceți clic pe linkul Creare sau gestionare roluri.
4. Pentru a adăuga un rol nou, tastați-l în caseta de text furnizată și faceți clic pe
Adăugare rol (a se vedea Figura 20-13). Sau utilizați linkurile Gestionare și
ștergere din lista de roluri pentru a modifica sau șterge o înregistrare de rol
existentă.

701
CAPITOLUL APARTENENȚĂ
20

Figura 20-13. Crearea rolurilor

Pentru a plasa un utilizator într-un rol, va trebui să reveniți la pagina principală de securitate (faceți clic pe
butonul Înapoi din pagina de gestionare a rolurilor). Apoi urmați acești pași:
1. Selectați Gestionați utilizatorii din fila Securitate. Veți vedea lista completă de
utilizatori pentru site-ul dvs. (împărțită în pagini).
2. Găsiți utilizatorul pe care doriți să îl modificați și faceți clic pe linkul
Editați rolurile de lângă acel utilizator.
3. Completați caseta de selectare pentru fiecare rol pe care doriți să îl atribuiți acelui utilizator.
Figura 20-14 prezintă un exemplu în care utilizatorului i se dă rolul de utilizator.

702
CAPITOLUL APARTENENȚĂ
20

Figura 20-14. Aplicarea rolurilor

Desigur, nu este nevoie să utilizați WAT. De asemenea, puteți utiliza clasa Roluri. Clasa Roluri servește
aceluiași scop pentru gestionarea rolurilor ca și clasa Membru pentru calitatea de membru - oferă o serie
de metode de utilitate statică care vă permit să modificați informațiile despre roluri. Tabelul 20-9 enumeră
metodele pe care le puteți utiliza.

703
CAPITOLUL APARTENENȚĂ
20

Tabelul 20-9. Metodele clasei de roluri

Metodă Descriere

CreateRole() Adaugă un rol nou bazei de date.

DeleteRole() Șterge un rol existent din baza de date. Dacă doriți să ștergeți un rol
care are în prezent membri alocați, fie trebuie să eliminați
utilizatorii din rol mai întâi sau utilizați versiunea supraîncărcată a DeleteRole() care
acceptă parametrul throwOnPopulatedRole (pe care trebuie să îl setați la
fals).

RoleExists() Verifică dacă există un anumit nume de rol în baza de date.

GetAllRoles() Regăsește o listă cu toate rolurile pentru această aplicație.

AddUserToRole(), Atribuie un rol unui utilizator, atribuie mai multe roluri unui utilizator simultan, atribuie un
AddUserToRoles(), rol pentru mai mulți utilizatori sau atribuie mai multe roluri mai multor utilizatori. Dacă vrei
AddUsersToRole() și Pentru a atribui un rol unui număr mare de utilizatori, cea mai rapidă abordare este utilizarea
AddUsersToRoles() clasa Membership pentru a prelua numele de utilizator corespunzătoare (dacă
necesar), apoi utilizați AddUsersToRole() sau AddUsersToRoles()
metoda clasei Roluri pentru a aplica schimbarea tuturor simultan.

RemoveUserFromRole(), Vă permite să eliminați un utilizator dintr-un rol. Puteți efectua această operație
RemoveUserFromRoles(), pe mai mulți utilizatori simultan sau eliminați un utilizator din mai multe roluri simultan,
RemoveUsersFromRole(), în funcție de metoda pe care o utilizați.
și
RemoveUsersFromRoles()

IsUserInRole() Verifică dacă un utilizator face parte dintr-un anumit rol.

GetRolesForUser() Preia toate rolurile pentru un anumit utilizator.

GetUsersInRole() Preia toți utilizatorii care fac parte dintr-un anumit rol.

FindUsersInRole() Preia toți utilizatorii care fac parte dintr-un anumit rol (la fel ca
GetUsersInRole()). Cu toate acestea, vă permite să limitați rezultatele la utilizatorii care
au o anumită bucată de text în numele lor de utilizator.

De exemplu, puteți utiliza următoarea rutină de tratare a evenimentelor cu controlul CreateUserWizard pentru
a atribui un utilizator nou creat într-un anumit rol:

protejat void CreateUserWizard1_CreatedUser(expeditor obiect, EventArgs e)


{
Roles.AddUserToRole (CreateUserWizard1.UserName, "Utilizator");
}

704
CAPITOLUL APARTENENȚĂ
20

Restricționarea accesului pe baza rolurilor


După ce ați creat și atribuit rolurile, trebuie să ajustați aplicația pentru a lua în considerare informațiile
despre roluri. Puteți utiliza mai multe tehnici:
• Puteți scrie reguli de autorizare care să refuze în mod specific anumite roluri din anumite
pagini sau subfoldere. Puteți scrie aceste reguli de mână, adăugând secțiunea
<authorization> la fișierul web.config sau le puteți defini cu ajutorul WAT făcând clic pe
linkul Gestionare reguli de acces.
• Puteți utiliza metoda User.IsInRole() din codul dvs. pentru a testa dacă
utilizatorul aparține unui anumit rol și apoi decideți dacă permiteți o acțiune sau
afișați un anumit conținut în consecință.
• Puteți utiliza controlul LoginView pentru a seta conținut diferit pentru roluri diferite.
Ați învățat deja cum să utilizați primele două tehnici din capitolul anterior. De exemplu, știți deja cum să
scrieți reguli web.config care restricționează un anumit grup, astfel:

<authorization> <deny
users="?" /> <deny
roles="Guest" /> <allow
users="*" /> </authorization>

Aceste reguli sunt interzise tuturor utilizatorilor anonimi și utilizatorilor din rolul de invitat. Rețineți că un utilizator
poate face parte din mai multe roluri, deci ordinea etichetelor <refuzați> contează. Prima regulă care se potrivește
determină dacă utilizatorul este permis sau refuzat.
În mod similar, știți cum să utilizați metoda User.IsInRole() pentru a lua o decizie de
autorizare programatică:

private void Page_Load(Object sender, EventArgs e)


{
lblMessage.Text = "Ați ajuns la pagina securizată, ";
lblMessage.Text += User.Identity.Name + ".";

dacă (User.IsInRole ("Administrator")) { lblMessage.Text +=


"<br /><br />Felicitări:"; lblMessage.Text += "sunteți
administrator."; }

Singura tehnică rămasă de luat în considerare este controlul LoginView.

Controlul LoginView
LoginView este un control de vizualizare precum controlul Panel sau MultiView despre care ați aflat în capitolul 10.
Diferența este că utilizatorul nu alege ce vizualizare să fie utilizată. În schimb, vizualizarea este setată pe baza stării de
autentificare a utilizatorului.
Cel mai simplu mod de a utiliza LoginView este de a afișa conținut separat pentru utilizatorii autentificați și
anonimi. Pentru a utiliza acest design, pur și simplu completați un anumit conținut în secțiunile
<AnonymousTemplate> și <LoggedInTemplate> ale controlului. Iată un exemplu:

705
CAPITOLUL APARTENENȚĂ
20

<asp:LoginView ID="LoginView1" runat="server">


<AnonymousTemplate> <h1>Esti anonim</h1> De ce nu
<a href="Login.aspx">login</a>?

</AnonymousTemplate> <LoggedInTemplate> <h1> Sunteți conectat</h1> <p>Acum sunteți gata să vedeți acest conținut
super-secret.</p>

</LoggedInTemplate>
</asp:LoginView>

Figura 20-15 prezintă cele două moduri în care poate apărea acest control, în funcție de faptul dacă utilizatorul este conectat în
prezent.

Figura 20-15. Afișarea conținutului diferit cu LoginView

LoginView acceptă, de asemenea, o altă etichetă - eticheta Grupuri de roluri. În tagul Grupuri de roluri, adăugați unul sau
mai multe controale Grup de roluri. Fiecare grup de roluri este mapat în mod specific la unul sau mai multe roluri. Cu alte
cuvinte, atunci când utilizați șablonul Grupuri de roluri, puteți afișa conținut diferit pentru utilizatorii autentificați, în funcție
de rolul din care fac parte.
Iată un exemplu:

<asp:LoginView ID="LoginView1" runat="server">


<AnonymousTemplate> <h1>Esti anonim</h1> De ce nu <a
href="Login.aspx">login</a>? </AnonymousTemplate>

<Grupuri de roluri>
<asp:RoleGroup Roles="Utilizator, invitat"> <
ContentTemplate> <p>Dacă vedeți acest lucru, sunteți membru
al rolurilor de utilizator sau invitat.</p>
< /ContentTemplate>
</asp:RoleGroup> <asp:RoleGroup Roles="Administrator"> <ContentTemplate> <p>Felicitări, sunteți
administrator.</p> </ContentTemplate>

706
CAPITOLUL APARTENENȚĂ
20

</asp:RoleGroup> </
RoleGroups> </asp:LoginView>

Nu uitați, un utilizator poate aparține mai multor roluri. Cu toate acestea, se poate afișa un singur șablon la un
moment dat. Când potriviți rolul cu un Grup de roluri, controlul LoginView trece prin tagurile RoleGroup în
ordine și utilizează prima potrivire. Dacă nu poate găsi o potrivire, folosește obișnuitul <LoggedInTemplate>.
LoginView este un control destul de puternic. Vă oferă o modalitate eficientă de a separa conținutul securizat
de conținutul obișnuit în mod declarativ - adică fără a scrie cod personalizat pentru a ascunde și afișa
etichete. Această abordare este mai clară, mai concisă și mai puțin predispusă la erori.

Ultimul cuvânt
Caracteristicile de membru ASP.NET vă oferă mai multe servicii de nivel înalt care funcționează cu sistemele de
autentificare a formularului de bază și sistemele de autentificare Windows despre care ați aflat în capitolul 19.
În acest capitol, ați văzut cum să utilizați calitatea de membru pentru a menține o bază de date de utilizatori,
fie cu SQL Server Express Edition gratuit, fie cu o altă versiune de SQL Server. De asemenea, ați învățat cum
să utilizați controalele de securitate predefinite, care vă oferă o modalitate convenabilă și flexibilă de a adăuga
caracteristici de gestionare a utilizatorilor și de a organiza conținut securizat. În cele din urmă, ați luat în
considerare modul în care puteți utiliza gestionarea rolurilor împreună cu calitatea de membru pentru a
determina exact ce acțiuni ar trebui și nu ar trebui să aibă voie să efectueze un utilizator în aplicațiile dvs.

707
Descărcați de la Wow! eBook <www.wowebook.com>
C A P I T O L U L 21

•■■

Profiluri

Puteți stoca informații pentru utilizatorii site-ului dvs. web într-o varietate de moduri. În capitolul 8, ați învățat cum să
utilizați tehnici precum starea vizualizării, starea sesiunii și cookie-urile pentru a urmări informațiile pentru o perioadă
scurtă de timp. Dar dacă trebuie să stocați informații între vizite, singura opțiune realistă este o bază de date pe
server. Folosind abilitățile ADO.NET pe care le-ați învățat până acum, este destul de ușor să salvați informații precum
adresele clienților și preferințele utilizatorilor într-o bază de date și să le recuperați mai târziu.
Singura problemă cu abordarea bazei de date este că depinde de dvs. să scrieți tot codul pentru preluarea informațiilor și
actualizarea înregistrărilor. Acest cod nu este teribil de complex - capitolul 14 acoperă tot ce trebuie să știți - dar poate fi
obositor. ASP.NET include o caracteristică care vă permite să evitați această plictiseală, dacă puteți lucra în anumite
limitări. Această caracteristică se numește profiluri și este concepută pentru a urmări automat informațiile specifice
utilizatorului.
Când utilizați profiluri, ASP.NET se ocupă de munca lipsită de farmec de a prelua informațiile de care aveți nevoie și de
a actualiza baza de date atunci când se schimbă. Nu trebuie să scrieți niciun cod ADO.NET sau chiar să proiectați
tabelele de baze de date corespunzătoare, deoarece ASP.NET are grijă de toate detaliile. Cel mai bun dintre toate,
funcția de profiluri se integrează cu autentificarea ASP.NET, astfel încât informațiile pentru utilizatorul conectat în
prezent (denumit profilul acelui utilizator) sunt întotdeauna disponibile pentru codul paginii dvs. web.
Singurul dezavantaj al caracteristicii de profiluri este că vă obligă să utilizați o structură de bază de date prestabilită. Acest
lucru vă împiedică să utilizați tabelele pe care le-ați creat deja pentru a stoca detalii specifice utilizatorului și reprezintă o
nouă provocare dacă doriți să utilizați aceleași informații în alte aplicații sau instrumente de raportare. Dacă structura
blocată este prea restrictivă, singura alegere este să creați un furnizor de profiluri personalizate care să extindă funcția de
profiluri (care este o sarcină mai dificilă în afara domeniului de aplicare al acestei cărți) sau să renunțați complet la
profiluri și să scrieți manual propriul cod de ADO.NET.
În acest capitol, veți învăța cum să utilizați profilurile, cum funcționează sistemul de profiluri și când
profilurile au cel mai mult sens.

Înțelegerea profilurilor
Una dintre cele mai semnificative diferențe dintre profiluri și alte tipuri de gestionare a stării este că profilurile sunt concepute
pentru a stoca informații permanent, utilizând o sursă de date back-end, cum ar fi o bază de date. Majoritatea celorlalte tipuri de
gestionare a stărilor sunt concepute pentru a menține informații pentru o serie de solicitări care apar într-un interval relativ scurt
de timp (cum ar fi starea sesiunii) sau în sesiunea curentă a browserului (cum ar fi starea vizualizării și cookie-urile
nepersistente) sau pentru a transfera informații de la o pagină la alta (cum ar fi șirul de interogare și postarea pe mai multe
pagini). Dacă aveți nevoie să stocați informații pe termen lung într-o bază de date, profilurile oferă pur și simplu un model
convenabil care gestionează recuperarea și persistența acestor informații pentru dvs.

Înainte de a începe să utilizați profilurile, trebuie să le evaluați cu atenție. În secțiunile următoare, veți afla
cum se stivuiesc.

709
CAPITOLUL PROFILURI
21

Performanța profilului
Scopul funcției de profiluri ASP.NET este de a oferi o modalitate transparentă de gestionare a informațiilor specifice
utilizatorului, fără a vă forța să scrieți cod de acces personalizat la date utilizând clasele de date ADO.NET. Din păcate,
multe caracteristici care par convenabile suferă de performanțe sau scalabilitate slabă. Aceasta este o preocupare în
special pentru profiluri, deoarece acestea implică accesul la baza de date, iar accesul la baza de date poate deveni cu
ușurință un blocaj de scalabilitate pentru orice aplicație web.
Deci, profilurile suferă de probleme de scalabilitate? Această întrebare nu are un răspuns simplu. Totul depinde de
cantitatea de date pe care trebuie să o stocați și de cât de des intenționați să le accesați. Pentru a lua o decizie în
cunoștință de cauză, trebuie să știți mai multe despre modul în care funcționează profilurile.
Profilurile se conectează la ciclul de viață al paginii în două moduri:
• Prima dată când accesați obiectul Profil din codul dvs., ASP.NET preia datele
complete de profil pentru utilizatorul curent din baza de date. Dacă citiți informațiile
de profil de mai multe ori în aceeași solicitare, ASP.NET le citește o dată și apoi le
reutilizează, salvând baza de date de munca suplimentară inutilă.
• Dacă modificați orice date de profil, actualizarea este amânată până la finalizarea
procesării paginii. În acel moment (după ce evenimentele PreRender,
PreRenderComplete și Unload s-au declanșat pentru pagină), profilul este scris înapoi în
baza de date. În acest fel, mai multe modificări sunt grupate într-o singură operație. Dacă
nu modificați datele de profil, nu se efectuează lucrări suplimentare în baza de date.
În general, caracteristica profiluri ar putea avea ca rezultat două călătorii suplimentare ale bazei de date pentru fiecare solicitare
(într-un scenariu de citire-scriere) sau o călătorie suplimentară a bazei de date (dacă citiți pur și simplu datele de profil). Caracteristica
profiluri nu se integrează cu memorarea în cache, astfel încât fiecare solicitare care utilizează date de profil necesită o conexiune la
baza de date. Din punct de vedere al performanței, profilurile funcționează cel mai bine atunci când următoarele sunt adevărate:

• Aveți un număr relativ mic de pagini care accesează datele profilului.


• Stocați cantități mici de date.
Ele tind să funcționeze mai puțin bine atunci când următoarele sunt adevărate:
• Aveți un număr mare de pagini care trebuie să utilizeze informații de profil.
• Stocați cantități mari de date. Acest lucru este deosebit de ineficient dacă trebuie să
utilizați doar unele dintre aceste date într-o anumită solicitare (deoarece modelul de
profil preia întotdeauna blocul complet de date de profil).
Desigur, puteți combina profilurile cu un alt tip de management de stat. De exemplu, imaginați-vă că site-ul
dvs. web include un expert de comandă care ghidează utilizatorul prin mai mulți pași. La începutul acestui
proces, puteți prelua informațiile de profil și le puteți stoca în starea sesiunii. Apoi puteți utiliza colecția de
sesiuni pentru restul procesului. Presupunând că utilizați serverul de stare în proces sau în afara procesului
pentru a menține datele sesiunii, această abordare este mai eficientă, deoarece vă scutește de necesitatea
de a vă conecta la baza de date în mod repetat.

Cum stochează profilurile datele


Cea mai semnificativă limitare a profilurilor nu are nimic de-a face cu performanța, ci este o limitare a modului
în care profilurile sunt serializate. Furnizorul implicit de profiluri inclus în ASP.NET serializează informațiile de
profil într-un bloc de date care este inserat într-un singur câmp dintr-o înregistrare de bază de date. De
exemplu, dacă serializați informațiile despre adresă, veți obține ceva de genul:
Marty Soren315 Southpart DriveLompocCalifornia93436U.S.A.

710
CAPITOLUL PROFILURI
21

Un alt câmp indică unde începe și unde se oprește fiecare valoare, utilizând un format ca acesta:

Nume:S:0:11:Stradă:S:11:19:Oraș:S:30:6:Stat:S:36:10:Cod poștal:S:46:5:Țară:S:51:6
În esență, acest șir identifică valoarea (Nume, Stradă, Oraș etc.), modul în care este stocată (S pentru șir),
poziția inițială și lungimea. Deci prima parte a acestui șir
Nume:S:0:11
indică faptul că prima proprietate de profil este Nume, care este stocată ca șir, începe de la poziția 0 și are 11
caractere.
Deși această abordare vă oferă flexibilitatea de a stoca aproape orice combinație de date, face mai dificilă utilizarea
acestor date în alte aplicații. Puteți scrie cod personalizat pentru a analiza datele de profil pentru a găsi informațiile
dorite, dar în funcție de cantitatea de date și de tipurile de date pe care le utilizați, acesta poate fi un proces extrem
de obositor. Și chiar dacă faceți acest lucru, sunteți încă limitat în modurile în care puteți reutiliza aceste informații.
De exemplu, imaginați-vă că utilizați profiluri pentru a stoca informații despre adresa clienților. Din cauza formatului
proprietar, nu mai este posibil să generați liste de clienți într-o aplicație precum Microsoft Word sau să efectuați
interogări care filtrează sau sortează înregistrările utilizând aceste date de profil. (De exemplu, nu puteți efectua cu
ușurință o interogare pentru a găsi toți clienții care locuiesc într-un anumit oraș.) Această problemă are două soluții:

• Utilizați propriul cod de ADO.NET personalizat în loc de profiluri.


• Creați un furnizor de profil particularizat care este proiectat să stocheze informații
utilizând schema bazei de date.
Dintre cele două opțiuni, crearea unei componente personalizate de acces la date este mai ușoară și vă oferă mai multă
flexibilitate. Puteți proiecta componenta de date pentru a avea orice interfață doriți și apoi puteți reutiliza acea
componentă cu alte aplicații .NET.
A doua opțiune este interesantă, deoarece permite paginii dvs. să utilizeze în continuare modelul de profil. De
fapt, puteți crea o aplicație care utilizează serializarea profilului standard cu SqlProfileProvider și apoi să o
comutați mai târziu pentru a utiliza un furnizor particularizat. Pentru a face această comutare, nu trebuie să
modificați niciun cod. În schimb, pur și simplu modificați setările profilului în fișierul web.config. Pe măsură ce
devine mai obișnuit ca site-urile web să utilizeze funcțiile de profil, furnizorii de profiluri personalizate vor
deveni mai atractivi.

Notă: De asemenea, este important să luați în considerare tipul de date care funcționează cel mai bine într-un profil. Ca și în cazul multor alte tipuri de

Gestionarea stării, puteți stoca orice tipuri serializabile într-un profil, inclusiv tipuri simple și clase personalizate.

O diferență semnificativă între profiluri și alte tipuri de gestionare a stării este că profilurile sunt stocate ca
înregistrări individuale, fiecare dintre acestea fiind identificată în mod unic prin numele de utilizator. Aceasta
înseamnă că profilurile necesită utilizarea unui fel de sistem de autentificare. Nu contează ce tip de sistem de
autentificare utilizați (Windows, formulare sau un sistem de autentificare particularizat) - singura cerință este
ca utilizatorilor autentificați să li se atribuie un nume de utilizator unic. Acest nume de utilizator este utilizat
pentru a găsi înregistrarea profilului corespunzător în baza de date.

• Notă Mai târziu în acest capitol (în secțiunea "Profiluri anonime"), veți afla cum caracteristica de
identificare anonimă vă permite să stocați temporar informațiile de profil pentru utilizatorii care nu
s-au conectat.

711
CAPITOLUL PROFILURI
21

Atunci când decideți dacă să utilizați profiluri, este firesc să comparați funcția de profiluri cu tipul de cod personalizat de
acces la date pe care l-ați scris în capitolul 14 (și componentele bazei de date pe care veți învăța să le construiți în
capitolul 22). În mod clar, scrierea propriului cod ADO.NET este mult mai flexibilă. Vă permite să stocați alte tipuri de
informații și să efectuați sarcini de afaceri mai complexe. De exemplu, un site de comerț electronic ar putea utiliza în mod
realist profiluri pentru a menține informațiile despre adresa clienților (cu limitările discutate în secțiunea anterioară). Cu
toate acestea, nu veți utiliza un profil pentru a stoca informații despre comenzile anterioare. Nu numai că este mult prea
multă informație pentru a fi stocată eficient, dar este, de asemenea, incomod de manipulat.

Utilizarea SqlProfileProvider
SqlProfileProvider vă permite să stocați informații de profil într-o bază de date SQL Server. Puteți alege să creați
tabelele de profil în orice bază de date. Cu toate acestea, nu puteți modifica niciunul dintre celelalte detalii ale
schemei bazei de date, ceea ce înseamnă că sunteți blocat în anumite nume de tabele, nume de coloane și format
de serializare. De la început până la sfârșit, trebuie să efectuați următorii pași pentru a utiliza profilurile:
1. Activați autentificarea pentru o porțiune a site-ului dvs.
2. Configurați furnizorul de profil. (Acest pas este opțional dacă utilizați SQL Server
Express. Profilurile sunt activate în mod implicit.)
3. Creați tabelele de profil. (Acest pas nu este necesar dacă utilizați SQL Server
Express.)
4. Definiți câteva proprietăți ale profilului.
5. Utilizați proprietățile profilului în codul paginii dvs.
Veți aborda acești pași în secțiunile următoare.

Activarea autentificării
Deoarece profilurile sunt stocate într-o înregistrare specifică utilizatorului, trebuie să autentificați utilizatorul curent înainte de a
putea citi sau scrie informații de profil. Puteți utiliza orice tip de sistem de autentificare, inclusiv autentificarea bazată pe
Windows și autentificarea bazată pe formulare. Sistemului de profiluri nu-i pasă - pur și simplu stochează informațiile specifice
utilizatorului într-o înregistrare identificată pe baza numelui de utilizator. Văzând că fiecare sistem de autentificare identifică
utilizatorii în mod unic după numele de utilizator, orice sistem de autentificare va funcționa.

Următorul fișier web.config utilizează autentificarea Windows:

<configuration> <system.web>
<authentication mode="Windows"/>
<authorization> <deny users="?" />
</autorizație> ...

</system.web> </configuration>

Deoarece acest exemplu utilizează autentificarea Windows, nu este necesar să creați o înregistrare pentru
fiecare utilizator. În schimb, veți utiliza conturile de utilizator Windows existente care sunt definite pe serverul
web. Această abordare vă scutește, de asemenea, de crearea unei pagini de conectare, deoarece browserul
gestionează procesul de conectare. (Pentru mai multe informații despre autentificarea Windows, consultați
capitolul 19.)

712
CAPITOLUL PROFILURI
21

Dacă decideți să utilizați în schimb autentificarea formularelor, va trebui să decideți dacă doriți să efectuați
autentificarea utilizând propria listă de utilizatori particularizată (capitolul 19) sau în combinație cu
caracteristicile de membru (capitolul 20). În majoritatea cazurilor, caracteristicile de membru și profiluri sunt
utilizate împreună - la urma urmei, dacă utilizați caracteristica profiluri pentru a stoca automat informații
specifice utilizatorului, de ce să nu stocați automat și lista acreditărilor de utilizator (nume de utilizator și
parole) în aceeași bază de date?

Sfat: Exemplele descărcabile pentru acest capitol afișează profiluri în acțiune într-un site care utilizează autentificarea formularelor și într-un alt site care utilizează autentificarea Windows.

După ce ați ales sistemul de autentificare (și v-ați ocupat de orice alte treburi care ar putea fi necesare,
cum ar fi crearea unei liste de utilizatori și generarea paginii de conectare), sunteți gata să utilizați
profilurile. Nu uitați, profilurile stochează informații specifice utilizatorului, astfel încât utilizatorul trebuie să
fie autentificat înainte ca profilul său să fie disponibil. În fișierul web.config afișat anterior, o regulă de
autorizare asigură acest lucru prin negarea tuturor utilizatorilor anonimi.

Utilizarea SQL Server Express


În capitolul anterior, ați aflat că nu sunt necesari pași speciali pentru a configura o aplicație web pentru a utiliza calitatea
de membru cu SQL Server Express (versiunea gratuită de SQL Server care este inclus în Visual Studio). Același lucru
este valabil și pentru profiluri.
Când utilizați SQL Server Express, ASP.NET stochează informațiile de profil într-un fișier bază de date
generat automat numit aspnetdb.mdf. Dacă acest fișier nu există, este creat prima dată când utilizați
caracteristici de membru sau profiluri și este plasat în subdirectorul App_Data al aplicației web. Cea mai
bună parte este că nu trebuie să parcurgeți pași suplimentari de configurare, deoarece ASP.NET este
configurat, în mod implicit, pentru a utiliza SQL Server cu profiluri.

Utilizarea versiunii complete de SQL Server


Această caracteristică de creare automată a bazei de date se bazează pe SQL Server Express. Dacă utilizați o
versiune non-Express de SQL Server, trebuie să creați baza de date de care aveți nevoie manual și să configurați în
mod explicit caracteristica profiluri în fișierul web.config.
În mod implicit, șirul de conexiune utilizat cu profiluri se numește LocalSqlServer. Puteți edita acest șir de conexiune
direct în fișierul machine.config. Cu toate acestea, dacă trebuie doar să modificați o singură aplicație, este mai bine
să ajustați fișierul web.config pentru aplicația dvs.
Pentru a face acest lucru, trebuie să eliminați toate șirurile de conexiune existente utilizând elementul
<clear> din fișierul web.config al aplicației web. Apoi, adăugați din nou șirul de conexiune LocalSqlServer -
dar de data aceasta cu valoarea corectă:
<configuration> <connectionStrings> <clear /> <add name="LocalSqlServer"
providerName="System.Data.SqlClient" connectionString="Data Source=localhost;
Securitate integrată=SSPI; Catalog inițial = aspnetdb "/ > < / connectionStrings> ...

713
CAPITOLUL PROFILURI
21

</configurare>
Acesta este același proces pe care l-ați utilizat în capitolul 20, deoarece atât caracteristica de membru, cât și
caracteristica profiluri utilizează șirul de conexiune LocalSqlServer. În acest exemplu, noul șir de conexiune este pentru
versiunea completă de SQL Server. Utilizează o bază de date numită aspnetdb pe computerul local. Apoi, va trebui să
creați baza de date aspnetdb utilizând utilitarul de linie de comandă aspnet_regsql.exe. Acesta este același instrument
care vă permite să generați baze de date pentru alte caracteristici ASP.NET, cum ar fi starea sesiunii bazate pe SQL
Server, calitatea de membru, rolurile, dependențele memoriei cache a bazei de date și personalizarea părților web.
Puteți găsi instrumentul aspnet_regsql.exe în folderul c:\Windows\Microsoft.NET\ Framework\[Version] (unde [Version]
este versiunea de ASP.NET instalată, cum ar fi v4.0.30319). Pentru a crea tabelele, vizualizările și procedurile stocate
necesare pentru profiluri, utilizați opțiunea de linie de comandă -A p. Celelalte detalii pe care poate fi necesar să le
furnizați includ locația serverului (-S), numele bazei de date (-d) și informațiile de autentificare pentru conectarea la baza
de date (utilizați -u și -P pentru a furniza o parolă și un nume de utilizator sau utilizați -E pentru a utiliza contul Windows
curent). Dacă omiteți locația serverului și numele bazei de date, aspnet_regsql.exe utilizează instanța implicită pe
computerul curent și creează o bază de date denumită aspnetdb.

Cel mai simplu mod de a utiliza aspnet_regsql este să deschideți promptul de comandă Visual Studio. Pentru aceasta, deschideți
meniul Start și alegeți Toate programele → Microsoft Visual Studio 2010 → Visual Instrumente Visual Studio
Linia de comandă Studio. Următorul exemplu creează o bază de date numită aspnetdb în serverul→ de baze
de date SQL Server pe computerul curent. Adaugă toate tabelele ASP.NET, inclusiv cele utilizate pentru
calitatea de membru, autentificarea bazată pe roluri și profilurile:
aspnet_regsql.exe -S (locală) -E -A toate
Dacă doriți să utilizați o altă bază de date, trebuie să specificați numele bazei de date utilizând parametrul -d.
În orice caz, ar trebui să utilizați o bază de date nouă, necompletată, care nu include alte tabele
particularizate. Acest lucru se datorează faptului că aspnet_regsql.exe creează mai multe tabele pentru
profiluri (consultați tabelul 21-1 din secțiunea următoare) și nu ar trebui să riscați să le confundați cu datele
de afaceri.

Notă
Această linie de comandă utilizează opțiunea -A all pentru a crea tabele pentru toate caracteristicile bazei de date ASP.NET, inclusiv profiluri și calitatea de membru. De asemenea, puteți alege să adăugați tabele pentru o singură caracteristică la un moment dat. Pentru mai multe informații despre -A și ceilalți parametri de linie de comandă pe care îi puteți utiliza cu aspnet_regsql, consultați tabelul 20-2 din capitolul 20.

Bazele de date de profil


Indiferent dacă utilizați aspnet_regsql pentru a crea bazele de date de profil pe cont propriu sau utilizați SQL Server
Express și lăsați ASP.NET să le creați automat, veți ajunge la aceleași tabele. Tabelul 21-1 le descrie pe scurt.
(Vederile destul de neinteresante nu sunt incluse.)
Dacă doriți să vă uitați la datele din aceste tabele, puteți privi în această bază de date în același mod în care
ați privit în baza de date de membri din capitolul 20. Cu toate acestea, conținutul nu prezintă prea mult
interes, deoarece ASP.NET le gestionează automat. Toate informațiile pe care le stocați într-un profil sunt
combinate într-o singură înregistrare și plasate liniștit într-un câmp numit PropertyValuesString într-un tabel
numit aspnet_Profile.

714
CAPITOLUL PROFILURI
21

Tabelul 21-1. Tabele de baze de date utilizate pentru profiluri

Numele tabelului Descriere

aspnet_Applications Listează toate aplicațiile web care au înregistrări în această bază de date. Este
Este posibil ca mai multe aplicații ASP.NET să utilizeze același ASPNETDB
bază de date. În acest caz, aveți opțiunea de a separa profilul
astfel încât să fie distinct pentru fiecare aplicație (oferind fiecare aplicație
un alt nume de aplicație atunci când înregistrați
furnizorul profilului) sau partajarea acestuia (oferind fiecărei aplicații același lucru
numele aplicației).

aspnet_Profile Stochează informațiile de profil specifice utilizatorului. Fiecare înregistrare conține


Informații complete de profil pentru un singur utilizator. Câmpul NumeProprietăți
listează numele proprietăților și șirul PropertyValuesString și
Câmpurile binare PropertyValuesBinary listează toate datele proprietății, deși veți avea nevoie
pentru a face unele lucrări dacă doriți să analizați aceste informații pentru a fi utilizate în alte
ASP.NET programe. Fiecare înregistrare include, de asemenea, data ultimei actualizări și
timp (LastUpdatedDate).

aspnet_SchemaVersions Listează schemele acceptate pentru stocarea informațiilor de profil. În viitor,


Acest lucru ar putea permite noilor versiuni ale ASP.NET să ofere noi modalități de stocare
informații de profil fără a întrerupe suportul pentru bazele de date de profil vechi care
sunt încă în uz.

aspnet_Users Listează numele de utilizator și le mapează la una dintre aplicațiile din


aspnet_Applications. De asemenea, înregistrează data și ora ultimei solicitări
(LastActivityDate) și dacă înregistrarea a fost generată automat pentru
un utilizator anonim (IsAnonymous). Veți afla mai multe despre anonim
Asistență pentru utilizatori mai jos în acest capitol (în secțiunea
"Profiluri anonime").

Figura 21-1 prezintă relațiile dintre cele mai importante tabele de profil.

715
CAPITOLUL PROFILURI
21

Figura 21-1. Tabelele de profil

Definirea proprietăților profilului


Înainte de a putea stoca orice informații de profil, trebuie să definiți în mod specific ceea ce doriți să stocați.
Faceți acest lucru adăugând elementul <proprietăți> în secțiunea <profil> a fișierului web.config. În interiorul
elementului <proprietăți>, plasați o etichetă <adăugare> pentru fiecare informație specifică utilizatorului pe
care doriți să o stocați. Cel puțin, elementul <adăugați> furnizează numele proprietății, astfel:
<configuration>
<system.web> ...

<profile> <properties> <add


name="FirstName"/> <add
name="LastName"/>
</properties> </profile>
</system.web> ...

</configurare>

De obicei, veți furniza și tipul de date. (Dacă nu, proprietatea este tratată ca un șir.) Puteți specifica orice
tip de date .NET serializabil, așa cum se arată aici:

<add name="FirstName" type="System.String"/> <add


name="LastName" type="System.String"/> <add
name="DateOfBirth" type="System.DateTime"/>
Puteți seta încă câteva atribute de proprietate pentru a crea proprietățile mai avansate prezentate în
tabelul 21-2.

716
CAPITOLUL PROFILURI
21

Tabelul 21-2.
Atributele proprietății profilului

atribut (pentru
elementul <add>) Descriere

nume Numele proprietății.

tip Numele clasei complet calificate care reprezintă tipul de date pentru această
proprietate. În mod implicit, acesta este System.String.

serializare Formatul de utilizat la serializarea acestei valori (String, Binary, XML sau
ProviderSpecific). Veți analiza mai atent modelul de serializare în secțiunea
"Serializarea profilului".

Numai citire O valoare booleană care determină dacă o valoare este modificabilă. Dacă este
adevărată, proprietatea poate fi citită, dar nu modificată. (Încercarea de a modifica
proprietatea va cauza o eroare de compilare.) În mod implicit, acest lucru este fals.

defaultValue O valoare implicită care va fi utilizată dacă profilul nu există sau nu include această
informație. Valoarea implicită nu are niciun efect asupra serializării - dacă setați o
proprietate de profil, ASP.NET va comite valorile curente în baza de date, chiar dacă
acestea se potrivesc cu valorile implicite.

allowAnonymous A
valoare booleană care indică dacă această proprietate poate fi utilizată cu
caracteristica de profiluri anonime discutată mai târziu în acest capitol. În mod
implicit, acest lucru este fals.

furnizor Furnizorul de profiluri care ar trebui utilizat pentru a gestiona doar această
proprietate. În mod implicit, toate proprietățile sunt gestionate utilizând furnizorul
specificat în elementul <profil>, dar puteți atribui proprietăți diferite diferiților furnizori.

Utilizarea proprietăților profilului


Cu aceste detalii instalate, sunteți gata să accesați informațiile de profil utilizând proprietatea Profil a paginii curente. Când
executați aplicația, ASP.NET creează o nouă clasă pentru a reprezenta profilul derivând din System.Web.Profile.ProfileBase,
care împachetează o colecție de setări de profil. ASP.NET adaugă o proprietate puternic tastată la această clasă pentru fiecare
proprietate de profil pe care ați definit-o în fișierul web.config. Aceste proprietăți puternic tastate apelează pur și simplu
metodele GetPropertyValue () și SetPropertyValue () din clasa de bază ProfileBase pentru a prelua și seta valorile de profil
corespunzătoare.
De exemplu, dacă ați definit o proprietate șir numită Prenume, o puteți seta în pagină astfel:

Profile.FirstName = "Henry";
Figura 21-2 prezintă o pagină completă de test care permite utilizatorului să afișeze informațiile de profil
pentru utilizatorul curent sau să seteze noi informații de profil.

717
CAPITOLUL PROFILURI
21
Descărcați de la Wow! eBook <www.wowebook.com>

Figura 21-2. Testarea caracteristicii de profil

Prima dată când rulează această pagină, nu se regăsesc informații de profil și nu se utilizează nicio
conexiune la baza de date. Cu toate acestea, dacă faceți clic pe butonul Afișare date profil, informațiile
de profil sunt preluate și afișate pe pagină:
protejat void cmdShow_Click(expeditor obiect, EventArgs e)
{
Lbl. Text = "Prenume: " + Profile.FirstName + "<br />" + "Nume: " +
Profile.LastName + "<br />" + "Data nașterii: " +
Profile.DateOfBirth.ToString("D");
}

În acest moment, va apărea o eroare dacă baza de date de profil lipsește sau conexiunea nu poate fi
deschisă. În caz contrar, pagina dvs. va rula fără probleme și veți vedea informațiile de profil nou preluate.
Din punct de vedere tehnic, profilul complet este recuperat atunci când codul accesează proprietatea
Profile.FirstName din prima linie și este utilizat pentru instrucțiunile de cod ulterioare.

Notă Proprietățile profilului se comportă ca orice altă variabilă membră a clasei. Aceasta înseamnă că, dacă citiți o valoare de profil care nu a fost setată, veți obține o valoare inițializată implicită (cum ar fi un șir gol sau 0).

718
CAPITOLUL PROFILURI
21

Dacă faceți clic pe butonul Setare date profil, informațiile profilului sunt setate pe baza valorilor
curente ale controlului:

protejat void cmdSet_Click(expeditor obiect, EventArgs e)


{
Profile.FirstName = txtFirst.Text; Profile.LastName =
txtLast.Text; Profile.DateOfBirth =
Calendar1.SelectedDate;
}

Acum, informațiile de profil sunt angajate în baza de date atunci când solicitarea paginii se termină. Dacă
doriți să comiteți unele sau toate informațiile mai devreme (și, eventual, să suportați mai multe călătorii în
baza de date), apelați metoda Profile.Save(). După cum puteți vedea, funcția de profiluri este de neegalat
pentru simplitate.

Sfat: obiectul Profil nu include doar proprietățile pe care le-ați definit. De asemenea, oferă proprietățile LastActivityDate (ultima dată când a fost utilizat acest profil) și LastUpdatedDate (ultima dată când acest profil a fost modificat), utilizând informații extrase din baza de date.

Serializarea profilului
Mai devreme, ați aflat cum proprietățile sunt serializate într-un singur șir. De exemplu, dacă salvați
un prenume al lui Harriet și un nume de familie al lui Smythe, ambele valori sunt aglomerate
împreună în câmpul PropertyValuesString al tabelului aspnet_Profile din baza de date, astfel:
HarrietSmythe
Câmpul NumeProprietăți (și în tabelul aspnet_Profile) oferă informațiile de care aveți nevoie pentru a analiza
fiecare valoare din câmpul PropertyValuesString. Iată ce veți vedea în câmpul NumeProprietăți din acest
exemplu:
Prenume:S:0:7:Nume:S:7:6:
Colonele (:) sunt folosite ca delimitatori. Formatul de bază este următorul:
PropertyName:StringOrBinarySerialization:StartingCharacterIndex:Length:
Ceva interesant se întâmplă dacă creați un profil cu un tip de date DateTime, astfel:

<add name="DateOfBirth" type="System.DateTime" serializeAs="String"/> <add


name="FirstName" type="System.String" serializeAs="Xml"/> <add
name="LastName" type="System.String" serializeAs="Xml"/>
Acum, când vă uitați la câmpul PropertyValuesString, veți vedea ceva de genul:

<?xml version="1.0" encoding="utf-16"?><dateTime>2007-07-12T00:00:00-04:00


</dateTime>HarrietSmythe
Inițial, se pare că datele de profil sunt serializate ca XML, dar PropertyValuesString în mod clar nu conține un document XML
valid (din cauza textului de la sfârșit). Ceea ce s-a întâmplat de fapt este că prima informație, DateTime, este serializată

719
CAPITOLUL PROFILURI
21

(implicit) ca XML. Următoarele două proprietăți de profil sunt serializate ca șiruri obișnuite.

Câmpul NumeProprietăți îl face puțin mai clar:

DataNașterii:S:0:81:Prenume:S:87:7:Nume:S:94:6:
Interesant este că aveți posibilitatea de a schimba formatul de serializare al oricărei proprietăți de profil adăugând
atributul serializeAs la declarația sa din fișierul web.config. Tabelul 21-3 enumeră opțiunile dumneavoastră.

Tabelul 21-3. Opțiuni de serializare

SerializeAs Descriere

Șir Convertește textul într-o reprezentare șir. Necesită un convertor de tip care poate
Ocupați-vă de treabă.

XML Efectuează conversia textului într-o reprezentare XML, care este stocată într-un șir, utilizând pictograma
System.Xml.XmlSerialization.XmlSerializer (aceeași clasă care este utilizat cu web
servicii).

Binar Efectuează conversia textului într-o reprezentare binară proprietară care numai .NET
înțelege folosind System.Runtime.Serialization.Formatters.Binary.
Formatter binar. Aceasta este cea mai compactă opțiune, dar cea mai puțin flexibilă. Binar
datele sunt stocate în câmpul PropertyValuesBinary în loc de PropertyValues.

Specific furnizorului Efectuează serializarea particularizată implementată într-un furnizor particularizat.

De exemplu, iată cum puteți modifica serializarea pentru setările profilului:


<add name="DateOfBirth" type="System.DateTime" serializeAs="String"/> <add name="FirstName"
type="System.String" />
serializeAs="Xml"
<add name="Nume" type="System.String" serializeAs="Xml"/>

Acum, data viitoare când setați profilul, reprezentarea serializată din câmpul PropertyValuesString va stoca
informații pentru Prenume și Nume. Ia această formă:

2007-06-27<?xml version="1.0" encoding="utf-16"?><string>Harriet</string> <?xml


version="1.0" encoding="utf-16"?><string>Smythe</string>

Dacă utilizați modul de serializare binară, valoarea proprietății va fi plasată în câmpul


PropertyValuesBinary în loc de câmpul PropertyValuesString. Iată un exemplu în care proprietatea
FirstName este serializată în câmpul PropertyValuesBinary:
<add name="DateOfBirth" type="System.DateTime" serializeAs="String"/> <add name="FirstName"
type="System.String" />
serializeAs="Binar"
<add name="Nume" type="System.String" serializeAs="String"/>

Singura indicație a acestei schimbări este utilizarea literei B în loc de S în câmpul PropertyNames (și faptul că
sunt necesari mai puțini octeți de):
DataNașterii:S:0:9:Prenume: :Nume:S:9:64:
B:0:31

720
CAPITOLUL PROFILURI
21

Toate aceste detalii de serializare ridică o întrebare importantă - ce se întâmplă atunci când modificați
proprietățile profilului sau modul în care sunt serializate? Proprietățile profilului nu au suport pentru controlul
versiunilor. Cu toate acestea, puteți adăuga sau elimina proprietăți cu consecințe relativ minore. De exemplu,
ASP.NET va ignora proprietățile prezente în tabelul aspnet_Profile, dar care nu sunt definite în fișierul
web.config. Data viitoare când modificați o parte a profilului, aceste proprietăți vor fi înlocuite cu noile
informații de profil. În mod similar, dacă definiți un profil în fișierul web.config care nu există în informațiile de
profil serializate, ASP.NET va utiliza doar valoarea implicită. Cu toate acestea, este posibil ca modificările mai
dramatice, cum ar fi redenumirea unei proprietăți, modificarea tipului de date și așa mai departe, să provoace
o excepție atunci când încercați să citiți informațiile de profil. Chiar mai rău, deoarece formatul serializat al
informațiilor de profil este proprietar, nu aveți o modalitate ușoară de a migra datele de profil existente într-o
nouă structură de profil.

Sfat: Nu toate tipurile sunt serializabile din toate punctele de vedere. De exemplu, clasele care nu oferă un constructor fără parametri nu pot fi serializate în modul Xml. Clasele care nu au atributul serializabil nu pot fi serializate în modul binar. Veți lua în considerare această distincție atunci când vă gândiți cum să utilizați tipuri personalizate cu profiluri (consultați secțiunea "Profiluri și tipuri de date personalizate"), dar deocamdată rețineți că puteți

întâlni tipuri care pot fi serializate numai dacă alegeți un alt mod de serializare.

Grupuri de profil
Dacă aveți un număr mare de setări de profil și unele setări sunt legate logic între ele, se recomandă să utilizați
grupuri de profiluri pentru a obține o organizare mai bună.
De exemplu, este posibil să aveți unele proprietăți care se ocupă de preferințele utilizatorului și altele
care se ocupă de informațiile de expediere. Iată cum puteți organiza aceste proprietăți de profil
utilizând elementul <grup>:
<profile> <properties> <group name="Preferences"> <add
name="LongDisplayMode" defaultValue="true" type="Boolean" /> <add
name="ShowSummary" defaultValue="true" type="Boolean" /> </group> <group
name="Address"> <add name="Name" type="String" /> <add name="Street"
type="String" /> <add name="City" type="String" /> <add name="ZipCode"
type="String" /> <add name="State" type="String" /> <add name="Country"
type="String" /> </group> </properties> </profile>

Acum puteți accesa proprietățile prin numele grupului din codul dvs. De exemplu, iată cum puteți prelua
informațiile despre țară:
lblCountry.Text = Profile.Address.Country;

721
CAPITOLUL PROFILURI
21

Grupurile sunt de fapt doar substitutul unui om sărac pentru o structură sau clasă personalizată cu drepturi
depline. De exemplu, puteți obține același efect ca în exemplul anterior declarând o clasă de adrese
particularizată. De asemenea, veți avea posibilitatea de a adăuga alte caracteristici (cum ar fi validarea în
procedurile proprietății). Următoarea secțiune arată cum.

Profiluri și tipuri de date particularizate


Utilizarea unei clase personalizate cu profiluri este ușoară. Trebuie să începeți prin crearea clasei care
împachetează informațiile de care aveți nevoie. În clasa dvs., puteți utiliza variabile pentru membri publici
sau proceduri de proprietate cu drepturi depline. Ultima alegere, deși mai lungă, este opțiunea preferată,
deoarece vă asigură că clasa dvs. va sprijini legarea datelor și vă oferă flexibilitatea de a adăuga codul de
procedură al proprietății mai târziu. Iată o clasă de adrese care leagă aceleași informații pe care le-ați
văzut în exemplul anterior, utilizând proprietăți automate pentru a reduce cantitatea de cod:
Adresa clasei publice
[Serializabil()]
{
șir public Nume {get; set;} șir public
Street {get; set;} șir public City {get; set;}
șir public ZipCode { get; set; } șir public
State {get; set;} șir public Țară {get; set;}

public Address(nume șir, stradă șir, oraș șir, cod poștal șir,
stare șir, țară șir) { Nume = nume; Stradă = stradă; Oraș = oraș;
Cod poștal = Cod poștal; Stat = stat; } Țara = țara;

adresa publică()
{}
}

Puteți plasa această clasă în directorul App_Code. Ultimul pas este să adăugați o proprietate care o utilizează:

<properties> <add name="Address"


type="Address" /> </properties>

Acum puteți crea o pagină de test care utilizează clasa Adresă. Figura 21-3 prezintă un exemplu care
vă permite pur și simplu să încărcați, să modificați și să salvați informațiile despre adresă într-un profil.

722
CAPITOLUL PROFILURI
21

Figura 21-3. Editarea informațiilor complexe dintr-un profil

Iată clasa de pagini care face posibil acest lucru:

public clasa parțială ComplexTipuri : System.Web.UI.Page


{
protejat void Page_Load(obiect expeditor, EventArgs e) { if (!
Page.IsPostBack) LoadProfile(); }

vid protejat cmdGet_Click(expeditor obiect, EventArgs e) {


LoadProfile(); }

vid privat LoadProfile() { txtName.Text =


Profile.Address.Name; txtStreet.Text =
Profile.Address.Street; txtCity.Text =
Profile.Address.City; txtZip.Text =
Profile.Address.ZipCode; txtState.Text =
Profile.Address.State; } txtCountry.Text =
Profile.Address.Country; protejat void
cmdSave_Click(expeditor obiect, EventArgs e) {

723
CAPITOLUL PROFILURI
21

Profile.Address = new Address(txtName.Text, txtStreet.Text,


txtCity.Text, txtZip.Text, } txtState.Text, txtCountry.Text);

Disecarea codului . . .
• Când pagina se încarcă (și când utilizatorul face clic pe butonul Obțineți), informațiile
de profil sunt copiate din obiectul Profile.Address în diferitele casete de text. O metodă
privată LoadProfile() gestionează această sarcină.
• Utilizatorul poate modifica valorile adreselor din casetele de text. Cu toate acestea,
modificarea nu este comisă până când utilizatorul nu face clic pe butonul Salvare.
• Când se face clic pe butonul Salvare, se creează un nou obiect Adresă utilizând
constructorul care acceptă informații despre nume, stradă, oraș, cod poștal, stat și
țară. Acest obiect este apoi atribuit proprietății Profile.Address. În loc să utilizați
această abordare, puteți modifica fiecare proprietate a obiectului curent
Profile.Address pentru a se potrivi cu valorile text.
• Conținutul obiectului Profil este salvat automat în baza de date la încheierea
solicitării. Nu este necesară muncă suplimentară.

Serializare tip personalizat


Trebuie să țineți cont de câteva puncte, în funcție de modul în care decideți să vă serializați clasa
personalizată. În mod implicit, toate tipurile de date particularizate utilizează serializarea XML cu
XmlSerializer. Această clasă este relativ limitată în capacitatea sa de serializare. Pur și simplu copiază
valoarea din fiecare proprietate publică sau variabilă membră într-un format XML simplu ca acesta:
<Adresa> <Nume>...
</Nume> <Strada>...
</Stradă> <Oraș>...
</Localitate>
<Cod poștal>... </Cod
poștal> <Stat>... </Stat>
<Țară>... </Țară>
</Adresă>
Când deserializați clasa, XmlSerializer trebuie să poată găsi un constructor public fără parametri. În plus, niciuna dintre
proprietățile dvs. nu poate fi doar în citire. Dacă încălcați oricare dintre aceste reguli, procesul de deserializare nu va
reuși.
Dacă decideți să utilizați serializarea binară în loc de XmlSerialization, .NET utilizează o abordare complet
diferită:
<add name="Address" type="Address" serializeAs="Binary"/>
În acest caz, ASP.NET solicită ajutorul BinaryFormatter. BinaryFormatter poate serializa întregul
conținut public și privat al oricărei clase, cu condiția ca clasa să fie decorată cu atributul
<Serializable()>. În plus, orice clasă din care derivă sau referințe trebuie să fie, de asemenea,
serializabilă.

724
CAPITOLUL PROFILURI
21

Salvări automate
Caracteristica profiluri nu poate detecta modificări ale tipurilor complexe de date (altceva decât șiruri de caractere,
tipuri numerice simple, valori booleene etc.). Aceasta înseamnă că, dacă profilul dvs. include tipuri de date
complexe, ASP.NET salvează informațiile complete ale profilului la sfârșitul fiecărei solicitări care accesează
obiectul Profil.
Acest comportament adaugă, evident, cheltuieli generale inutile. Pentru a optimiza performanța atunci când
lucrați cu tipuri complexe, aveți mai multe opțiuni. O opțiune este să setați proprietatea profilului
corespunzător să fie doar în citire în fișierul web.config (dacă știți că proprietatea nu se schimbă niciodată).
O altă abordare este să dezactivați complet comportamentul de salvare automată adăugând atributul
automaticSaveEnabled pe elementul <profil> și setându-l la fals, așa cum se arată aici:
<profile >provider="TrueProvider" immediateSaveEnabled="false">... </profil>
Dacă alegeți această abordare, depinde de dvs. să apelați Profile.Save() pentru a comite în mod explicit
modificări. În general, această abordare este cea mai convenabilă, deoarece este ușor să identificați locurile
din codul dvs. în care modificați profilul. Doar adăugați apelul Profile.Save () la sfârșit:
Profile.Address = noua adresa(txtName.Text, txtStreet.Text, txtCity.Text, txtZip.Text,
txtState.Text, txtCountry.Text);
Profile.Save();
De exemplu, puteți modifica exemplul anterior (prezentat în Figura 21-3) pentru a salva informațiile despre adresă
numai atunci când se modifică. Cel mai simplu mod de a face acest lucru este să dezactivați salvările automate,
dar apelați Profile.Save() atunci când faceți clic pe butonul Salvare. De asemenea, puteți gestiona evenimentul
TextBox.TextChanged pentru a determina când se fac modificări și puteți salva profilul imediat în acest moment.

API-ul Profil
Deși pagina dvs. primește automat informațiile de profil pentru utilizatorul curent, acest lucru nu vă împiedică să
recuperați și să modificați profilurile altor utilizatori. De fapt, aveți două instrumente care să vă ajute - clasa ProfileBase
și clasa ProfileManager.
Obiectul Profil (furnizat de proprietatea Page.Profile) include o metodă utilă GetProfile() care regăsește
informațiile de profil pentru un anumit utilizator după numele de utilizator. Figura 21-4 prezintă un exemplu cu
un utilizator autentificat Windows.

Figura 21-4. Preluarea manuală a unui profil

725
CAPITOLUL PROFILURI
21

Iată codul care primește profilul:

protejat void cmdGet_Click(expeditor obiect, EventArgs e) { ProfileCommon


profile = Profile.GetProfile(txtUserName.Text); Lbl. Text = "Acest utilizator
locuiește în " + profil. Adresa.Țara; }

GetProfile() returnează un obiect ProfileCommon. Cu toate acestea, nu veți găsi ProfileCommon în biblioteca de clasă
.NET. Acest lucru se datorează faptului că ProfileCommon este o clasă generată dinamic pe care ASP.NET creează
pentru a stoca informațiile de profil pentru aplicația dvs. În acest exemplu, profilul definește o proprietate numită Adresă,
astfel încât să puteți prelua aceste informații utilizând proprietatea ProfileCommon.Address. Observați că, odată ce aveți
un obiect ProfileCommon, puteți interacționa cu acesta în același mod în care interacționați cu profilul pentru utilizatorul
curent (la urma urmei, este același tip de obiect). Puteți chiar să faceți modificări. Singura diferență este că modificările
nu sunt salvate automat. Dacă doriți să salvați o modificare, trebuie să apelați metoda Save() a obiectului
ProfileCommon. ProfileCommon adaugă, de asemenea, proprietățile LastActivityDate și LastUpdatedDate, pe care le
puteți utiliza pentru a determina ultima dată când un anumit profil a fost accesat și modificat.

Dacă încercați să recuperați un profil care nu există, nu veți primi o eroare. În schimb, veți ajunge pur și simplu cu
date goale (de exemplu, șiruri goale). Dacă modificați și salvați profilul, va fi creată o nouă înregistrare de profil.

Puteți testa această condiție examinând proprietatea ProfileCommon.LastUpdatedDate . Dacă profilul nu a


fost creat încă, această valoare va fi o valoare dată zero (cu alte cuvinte, ziua 1 din luna 1 din anul 0001).
Iată codul pe care l-ați folosi:
protejat void cmdGet_Click(expeditor obiect, EventArgs e) { ProfileCommon profile = Profile.GetProfile(txtUserName.Text); dacă
(profil. LastUpdatedDate == DateTime.MinValue) { lbl. Text = "Nu s-a găsit nicio potrivire de utilizator."; } altceva { lbl. Text =
"Acest utilizator locuiește în " + profil. Adresa.Țara;

}
}

Dacă trebuie să efectuați alte sarcini cu profiluri, puteți utiliza clasa ProfileManager în spațiul de nume
System.Web.Profile, care expune metodele statice utile descrise în tabelul 21-4. Multe dintre aceste metode
funcționează cu o clasă ProfileInfo, care oferă informații despre un profil. ProfileInfo include numele de
utilizator (UserName), datele ultimei actualizări și ale ultimei activități (LastUpdatedDate și LastActivityDate),
dimensiunea profilului în octeți (Dimensiune) și dacă profilul este pentru un utilizator anonim (IsAnonymous).
Nu oferă valorile reale ale profilului.

726
CAPITOLUL PROFILURI
21

Tabelul 21-4. Metode ProfileManager

Metodă Descriere

DeleteProfile() Șterge profilul utilizatorului specificat.

DeleteProfiles() Șterge mai multe profiluri simultan. Furnizați o colecție de nume de utilizator.

DeleteInactiveProfiles() Șterge profilurile care nu au mai fost utilizate de la o dată specificată de dvs.
De asemenea, trebuie să furnizați o valoare din enumerarea
ProfileAuthenticationOption pentru a indica tipul de profiluri pe care doriți să le
eliminați (Toate, Anonim sau Autentificat).

GetNumberOfProfiles() Returnează numărul de înregistrări de profil din sursa de date. Trebuie să


furnizați o valoare din enumerarea ProfileAuthenticationOption care indică dacă
doriți să vedeți și profiluri autentificate (Autentificate), profiluri anonime
(Anonim) sau ambele (Toate).

Profiluri Returnează numărul de profiluri care nu au mai fost utilizate de la momentul


GetNumberOfInactive() specificat. Trebuie să furnizați o valoare din enumerarea
ProfileAuthenticationOption.

GetAllInactiveProfiles() Regăsește informațiile de profil pentru profilurile care nu au mai fost utilizate
din momentul specificării dvs. Trebuie să furnizați o valoare din enumerarea
ProfileAuthenticationOption. Profilurile sunt returnate ca obiecte ProfileInfo.

GetAllProfiles() Preia toate datele de profil din sursa de date ca o colecție de obiecte ProfileInfo.
Puteți alege ce tip de profiluri doriți să recuperați (Toate, Anonim sau
Autentificat). De asemenea, puteți utiliza o versiune supraîncărcată a acestei
metode care utilizează paginarea și preia doar o parte din setul complet de
înregistrări, pe baza indexului de pornire și a dimensiunii paginii solicitate.
FindProfilesByNume Regăsește o colecție de obiecte ProfileInfo care corespund unui anumit nume de
utilizator() utilizator. SqlProfileProvider utilizează o clauză LIKE atunci când încearcă să se
potrivească cu numele de utilizator, ceea ce înseamnă că puteți utiliza metacaractere,
cum ar fi simbolul %. De exemplu, în cazul în care căutați numele de utilizator
utilizator%, veți returna valori precum user1, user2, user_guest și așa mai departe.
Puteți utiliza o versiune supraîncărcată a acestei metode care utilizează paginarea.

FindInactiveProfilesByU Regăsește informațiile de profil pentru profilurile care nu au mai fost utilizate din
serName() momentul specificării dvs. De asemenea, puteți filtra anumite tipuri de profiluri (Toate,
Anonim sau Autentificat) sau puteți căuta un anumit nume de utilizator (cu potrivire
metacaractere). Valoarea returnată este o colecție de obiecte ProfileInfo.

727
CAPITOLUL PROFILURI
21

De exemplu, dacă doriți să eliminați profilul pentru utilizatorul curent, aveți nevoie doar de o singură
linie de cod:
ProfileManager.DeleteProfile(User.Identity.Name);
Și dacă doriți să afișați lista completă de utilizatori într-o pagină web (fără a include utilizatorii anonimi),
trebuie doar să adăugați un GridView cu AutoGenerateColumns setat la true și să utilizați acest cod:

vid protejat Page_Load(expeditor obiect, EventArgs e)


{
gridProfiles.DataSource = ProfileManager.GetAllProfiles(
ProfileAuthenticationOption.Authenticated);
gridProfiles.DataBind();
}

Figura 21-5 prezintă rezultatul.


Descărcați de la Wow! eBook <www.wowebook.com>

Figura 21-5. Preluarea informațiilor despre toate profilurile din sursa de date

Profiluri anonime
Până în prezent, toate exemplele au presupus că utilizatorul este autentificat înainte ca orice informație de
profil să fie accesată sau stocată. De obicei, acesta este cazul. Cu toate acestea, uneori este util să creați un
profil temporar pentru un utilizator nou, necunoscut. De exemplu, majoritatea site-urilor de comerț electronic
permit utilizatorilor noi să înceapă să adauge articole într-un coș de cumpărături înainte de a se înregistra.
Dacă doriți să oferiți acest tip de comportament și alegeți să stocați articole din coșul de cumpărături într-un
profil, veți avea nevoie de o modalitate unică de a identifica în mod unic utilizatorii anonimi.

Notă: Merită să întrebați dacă are sens să stocați un coș de cumpărături într-un profil. Este un design rezonabil, funcțional, dar mulți dezvoltatori consideră că este mai ușor să controleze în mod explicit modul în care acest tip de informații sunt stocate în baza lor de date folosind codul ADO.NET personalizat în locul funcției de profil.

728
CAPITOLUL PROFILURI
21

ASP.NET oferă o caracteristică de identificare anonimă care umple acest gol. Ideea de bază este că funcția de
identificare anonimă generează automat un identificator aleatoriu pentru orice utilizator anonim. Acest identificator
aleatoriu stochează informațiile de profil în baza de date, chiar dacă nu este disponibil niciun nume de utilizator. Numele
de utilizator este urmărit pe partea clientului folosind un modul cookie (sau în adresa URL, dacă ați activat modul fără
cookie-uri). Odată ce acest cookie dispare (de exemplu, dacă utilizatorul anonim închide și redeschide browserul),
sesiunea anonimă se pierde și se creează o nouă sesiune anonimă.
Identificarea anonimă are potențialul de a lăsa o mulțime de profiluri abandonate, ceea ce irosește spațiu în
baza de date. Din acest motiv, identificarea anonimă este dezactivată în mod implicit. Cu toate acestea, îl
puteți activa utilizând elementul <anonymousIdentification> din fișierul web.config, așa cum se arată aici:
<configurare> ...

<system.web> <anonymousIdentification
enabled="true" /> ...

</system.web> </configuration>

De asemenea, trebuie să semnalizați fiecare proprietate de profil care va fi păstrată pentru utilizatorii
anonimi, adăugând atributul allowAnonymous și setându-l la true. Acest lucru vă permite să stocați doar
câteva informații de bază și să restricționați obiectele mai mari la utilizatorii autentificați.
<properties> <add name="Address" type="Address" />
allowAnonymous="true" ...
</proprietăți>
Dacă utilizați un tip complex, atributul allowAnonymous este o setare totul sau nimic. Configurați întregul
obiect pentru a accepta stocare anonimă sau nu acceptați.
Elementul <anonymousIdentification> acceptă, de asemenea, numeroase atribute opționale care vă permit
să setați numele și expirarea cookie-ului, să specificați dacă cookie-ul va fi emis numai printr-o conexiune
SSL, să controlați dacă protecția cookie-urilor (validare și criptare) este utilizată pentru a preveni manipularea
și interceptarea și să configurați suportul pentru urmărirea ID-ului fără cookie-uri. Iată un exemplu:
<anonymousIdentification enabled="true" cookieName=".
ASPXANONYMOUS" cookieTimeout="43200" cookiePath="/"
cookieRequireSSL="false" cookieSlidingExpiration="true"
cookieProtection="All" cookieless="UseCookies"/>
Pentru mai multe informații, consultați Ajutorul Visual Studio.

Sfat
Dacă utilizați identificarea anonimă, este o idee bună să ștergeți sesiunile anonime vechi în mod regulat utilizând procedura aspnet_Profile_DeleteInactiveProfiles stocată, pe care o puteți executa la intervale programate utilizând SQL Server Agent. De asemenea, puteți șterge profilurile vechi utilizând clasa ProfileManager, așa cum este descris în secțiunea anterioară.

729
CAPITOLUL PROFILURI
21

Migrarea profilurilor anonime


O provocare care apare cu profilurile anonime este ce să faci cu informațiile de profil atunci când un utilizator anonim
anterior se conectează. De exemplu, într-un site web de comerț electronic, un utilizator poate selecta mai multe elemente
și apoi se poate înregistra sau se poate conecta pentru a finaliza tranzacția. În acest moment, trebuie să vă asigurați că
informațiile coșului de cumpărături sunt copiate din profilul utilizatorului anonim în profilul autentificat (utilizator)
corespunzător.
Din fericire, ASP.NET oferă o soluție prin intermediul evenimentului ProfileModule.MigrateAnonymous. Acest eveniment
se declanșează ori de câte ori este disponibil un identificator anonim (fie ca cookie, fie în URL, dacă utilizați modul fără
cookie) și utilizatorul curent este autentificat. Pentru a gestiona evenimentul MigrateAnonymous, trebuie să adăugați o
rutină de tratare a evenimentelor la fișierul care gestionează toate evenimentele aplicației - fișierul Global.asax, despre
care ați aflat în capitolul 5.
Tehnica de bază atunci când se gestionează evenimentul MigrateAnonymous este încărcarea profilului pentru
utilizatorul anonim apelând Profile.GetProfile() și transmițând ID-ul anonim, care este furnizat rutinei de tratare a
evenimentelor prin ProfileMigrateEventArgs.
După ce ați încărcat aceste date, puteți transfera manual setările în noul profil. Puteți alege să
transferați câte setări doriți și puteți efectua orice altă procesare necesară. În cele din urmă, codul dvs.
ar trebui să elimine datele de profil anonime din baza de date și să șteargă identificatorul anonim,
astfel încât evenimentul MigrateAnonymous să nu se declanșeze din nou. De exemplu:

void Profile_MigrateAnonymous(Object sender, ProfileMigrateEventArgs pe)


{
Obțineți profilul anonim.
ProfileCommon anonProfile = Profile.GetProfile(pe. ID anonim);

Copiați informațiile în profilul autentificat // (dar numai


dacă există informații acolo). dacă
(!anonProfile.IsNullOrEmpty()) { Profile.Address =
anonProfile.Address; }

Ștergeți profilul anonim din baza de date.


(Puteți decide să omiteți acest pas pentru a crește performanța // dacă aveți o
lucrare dedicată programată pe serverul bazei de date // pentru a elimina vechile
profiluri anonime.)
System.Web.Profile.ProfileManager.DeleteProfile(sau. ID anonim);

Eliminați identificatorul anonim.


AnonymousIdentificationModule.ClearAnonymousIdentifier();
}

Trebuie să vă ocupați de această sarcină cu o anumită prudență. Dacă ați activat identificarea anonimă,
evenimentul MigrateAnonymous se declanșează de fiecare dată când un utilizator se conectează, chiar dacă
utilizatorul nu a introdus nicio informație în profilul anonim. Aceasta este o problemă - dacă nu sunteți atent,
puteți suprascrie cu ușurință profilul real (salvat) pentru utilizator cu profilul anonim gol. Problema este
complicată și mai mult de faptul că tipurile complexe (cum ar fi obiectul Adresă) sunt create automat de
ASP.NET, deci nu puteți verifica doar o referință nulă pentru a determina dacă utilizatorul are informații
anonime despre adresă. În exemplul anterior, codul testează pentru o proprietate Nume lipsă în obiectul
Adresă. Dacă aceste informații nu fac parte din profilul anonim, nu se migrează nicio informație. Un exemplu
mai sofisticat ar putea testa separat proprietățile individuale sau ar putea migra un profil anonim numai dacă
informațiile din profilul de utilizator lipsesc sau sunt depășite.

730
CAPITOLUL PROFILURI
21

Ultimul cuvânt
În acest capitol, ați învățat cum să utilizați profilurile și cum stochează informațiile în baza de date. Mulți
dezvoltatori ASP.NET vor prefera să-și scrie propriul cod ADO.NET pentru recuperarea și stocarea
informațiilor specifice utilizatorului. Acest lucru nu numai că vă permite să utilizați propria structură a bazei de
date, ci vă permite să adăugați propriile caracteristici, cum ar fi memorarea în cache, înregistrarea în jurnal,
validarea și criptarea. Dar profilurile sunt utile pentru construirea rapidă a aplicațiilor modeste care nu
stochează o mulțime de informații specifice utilizatorului și nu au cerințe speciale pentru modul în care sunt
stocate aceste informații.

731
PART6

• ■■

ASP.NET avansate
C A P I T O L U L 22

• ■■

Programare bazată pe componente

Programarea bazată pe componente este o idee simplă și elegantă. Atunci când este utilizat corect, permite codului
să fie mai organizat, mai consecvent și mai reutilizabil. De asemenea, este incredibil de ușor de implementat într-o
aplicație .NET, deoarece nu trebuie să utilizați niciodată registrul Windows sau să efectuați o configurație specială.
O componentă, la modul cel mai simplu, este una sau mai multe clase care sunt compilate într-un fișier de asamblare
DLL separat. Aceste clase oferă o unitate de funcționalitate legată logic. Puteți accesa o componentă dintr-o singură
aplicație sau puteți partaja componenta între mai multe aplicații. Paginile dvs. web (sau orice altă aplicație .NET) pot
utiliza clasele din componentele dvs. în același mod în care utilizează orice altă clasă .NET. Cel mai bun dintre toate,
componenta dvs. este încapsulată, ceea ce înseamnă că oferă exact caracteristicile de care are nevoie codul dvs. și
ascunde toate celelalte detalii dezordonate.
Atunci când este combinată cu o organizare atentă, programarea bazată pe componente este baza unui design bun al
aplicațiilor ASP.NET. În acest capitol, veți examina modul în care puteți crea componente (și de ce ar trebui) și veți lua în
considerare exemple care vă arată cum să încapsulați funcționalitatea bazei de date cu un obiect de afaceri bine scris.
De asemenea, veți afla cum să legați componenta bazei de date la controalele web de pe o pagină utilizând
ObjectDataSource.

De ce să folosiți componente?
Pentru a stăpâni dezvoltarea ASP.NET, trebuie să deveniți un utilizator calificat al bibliotecii de clasă .NET. Până în prezent,
ați învățat cum să utilizați componentele .NET concepute pentru citirea fișierelor, comunicarea cu bazele de date, apelarea
serviciilor web și stocarea informațiilor despre utilizator. Deși aceste ingrediente ale bibliotecii de clasă sunt puternice, ele nu
sunt personalizabile, ceea ce este atât un avantaj, cât și un punct slab.
De exemplu, dacă doriți să regăsiți date dintr-o bază de date SQL Server, trebuie să țeseți detaliile bazei de date (cum
ar fi interogările SQL) direct în clasa din spatele codului sau (dacă utilizați SqlDataSource) în porțiunea de marcaj .aspx
a fișierului paginii web. În orice caz, dacă structura bazei de date se schimbă chiar și ușor, ați putea rămâne cu zeci de
pagini de actualizat și retestat. Pentru a rezolva aceste probleme, trebuie să creați un strat suplimentar între codul
paginii web și baza de date. Acest strat suplimentar ia forma unei componente personalizate.

Acest scenariu de bază de date este doar unul dintre motivele pentru care poate doriți să creați propriile componente.
Programarea bazată pe componente este într-adevăr doar o extensie logică a principiilor bune de organizare a codului
și oferă o listă lungă de avantaje:

Siguranță: Deoarece codul sursă nu este conținut în pagina dvs. web, nu îl puteți modifica. În schimb, sunteți limitat
la funcționalitatea oferită de componenta dvs. De exemplu, puteți configura o componentă a bazei de date pentru a
permite numai anumite operațiuni cu anumite tabele, câmpuri sau rânduri. Acest lucru este adesea mai ușor decât
configurarea permisiunilor complexe în baza de date. Deoarece aplicația trebuie să treacă prin componentă, trebuie
să joace după regulile sale.

735
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

O mai bună organizare: componentele elimină dezordinea din codul paginii web. De asemenea, devine
mai ușor pentru alți programatori să înțeleagă logica aplicației dvs. atunci când este împărțită în
componente separate. Fără componente, codul utilizat în mod obișnuit trebuie copiat și lipit într-o
aplicație, ceea ce face extrem de dificilă schimbarea și sincronizarea.
Depanare mai ușoară: Este imposibil să supraestimați avantajul componentelor atunci când testați și
depanați o aplicație. Programele bazate pe componente sunt împărțite în blocuri de cod mai mici și mai
strânse, facilitând izolarea exactă a locului în care apare o problemă. De asemenea, este mai ușor să
testați componentele individuale separat de restul aplicației web.
Mai multă gestionare: Programele bazate pe componente sunt mult mai ușor de îmbunătățit și modificat,
deoarece componenta și codul aplicației web pot fi modificate separat. Dusă la extrem, această
abordare vă permite să aveți o echipă de dezvoltare care lucrează la componentă și o altă echipă care
codifică site-ul web care utilizează componenta.
Reutilizarea codului: Componentele pot fi partajate cu orice aplicație ASP.NET care are nevoie de
funcționalitatea componentei. Chiar mai bine, orice aplicație .NET poate utiliza o componentă, ceea ce
înseamnă că puteți crea o "coloană vertebrală" comună a logicii care este utilizată de o aplicație web și
de o aplicație Windows obișnuită.
Simplitate: Componentele pot furniza mai multe sarcini conexe pentru o singură solicitare a clientului
(scrierea mai multor înregistrări într-o bază de date, deschiderea și citirea unui fișier într-un singur pas
sau chiar pornirea și gestionarea unei tranzacții cu baza de date). În mod similar, componentele ascund
detalii - un programator de aplicații poate utiliza o componentă a bazei de date fără a-și face griji cu
privire la numele bazei de date, locația serverului sau contul de utilizator necesar pentru conectare.
Chiar mai bine, puteți efectua o căutare utilizând anumite criterii, iar componenta însăși poate decide
dacă să utilizeze o instrucțiune SQL generată dinamic sau o procedură stocată.

Jargonul component
Programarea bazată pe componente este uneori învăluită într-o ceață de jargon specializat. Înțelegerea
acestor termeni vă ajută să sortați exact ce ar trebui să facă o componentă și, de asemenea, vă permite să
înțelegeți articolele MSDN despre proiectarea aplicațiilor. Dacă sunteți deja familiarizat cu elementele
fundamentale ale componentelor, nu ezitați să săriți mai departe.

Design pe trei niveluri


Ideea proiectării pe trei niveluri este că funcționalitatea majorității aplicațiilor complete poate fi împărțită în
trei niveluri principale (a se vedea figura 22-1). Primul nivel este interfața cu utilizatorul (sau nivelul de
prezentare), care afișează controale și primește și validează datele introduse de utilizator. Toți gestionarii de
evenimente din pagina dvs. web se află în acest prim nivel. Al doilea nivel este nivelul de afaceri, unde are
loc logica specifică aplicației. Pentru un site de comerț electronic, logica specifică aplicației include reguli
precum modul în care se aplică taxele de expediere unei comenzi, când sunt valabile anumite promoții și ce
acțiuni ale clienților ar trebui înregistrate. Nu implică detalii generice .NET, cum ar fi cum să deschideți un
fișier sau să vă conectați la o bază de date. Al treilea nivel este nivelul de date, unde plasați logica care
stochează informațiile dvs. în fișiere, într-o bază de date sau într-un alt depozit de date. Al treilea nivel
conține logică despre cum să regăsiți și să actualizați datele, cum ar fi interogările SQL sau procedurile
stocate.

736
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Figura 22-1. Design pe trei niveluri

Detaliul important despre designul pe trei niveluri este că informațiile călătoresc de la un singur nivel la un nivel adiacent.
Cu alte cuvinte, codul paginii dvs. web nu ar trebui să se conecteze direct la baza de date pentru a prelua informații. În
schimb, ar trebui să treacă printr-o componentă din nivelul de afaceri care se conectează la baza de date și returnează
datele.
Acest principiu de bază al organizării nu poate fi întotdeauna respectat, dar este un model bun de urmat.
Când creați o componentă, aceasta este aproape întotdeauna utilizată în al doilea nivel pentru a reduce
decalajul dintre date și interfața cu utilizatorul. Cu alte cuvinte, dacă doriți să completați o listă de categorii de
produse într-o casetă listă, codul interfeței cu utilizatorul apelează o componentă, care preia lista din baza de
date și apoi o returnează în codul dvs. Codul paginii web este izolat de baza de date, iar dacă structura bazei
de date se modifică, trebuie să modificați o componentă concisă în loc de fiecare pagină de pe site.

737
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Încapsulare
Dacă proiectarea pe trei niveluri este obiectivul general al programării bazate pe componente, încapsularea este cea mai
bună regulă generală. Încapsularea este principiul că ar trebui să vă creați aplicația din "cutii negre" care ascund
informații. Deci, dacă aveți o componentă care înregistrează o achiziție pe un site de comerț electronic, acea componentă
gestionează toate detaliile și permite specificarea doar a variabilelor esențiale.
De exemplu, această componentă poate accepta un ID de utilizator și un ID de element de comandă, apoi
poate gestiona toate celelalte detalii. Codul de apelare nu trebuie să-și facă griji cu privire la modul în care
funcționează componenta sau de unde provin datele, ci trebuie doar să înțeleagă cum să utilizeze
componenta. (Acest principiu este descris într-o mulțime de moduri pitorești. De exemplu, știi cum să conduci
o mașină pentru că înțelegi interfața componentelor sale – volanul și pedalele – nu pentru că înțelegi
detaliile de nivel scăzut despre arderea internă și motor. Ca rezultat, puteți să vă transferați cunoștințele la
multe tipuri diferite de automobile, chiar dacă acestea au lucrări interne dramatic diferite.)

Obiecte de afaceri
Termenul obiect de afaceri înseamnă adesea lucruri diferite pentru oameni diferiți. În general, obiectele de
afaceri sunt componentele din al doilea nivel al aplicației dvs. care oferă stratul suplimentar între codul dvs. și
Descărcați de la Wow! eBook <www.wowebook.com>

sursa de date. Ele sunt numite obiecte de afaceri deoarece aplică reguli de afaceri. De exemplu, dacă
încercați să trimiteți o comandă de cumpărare fără articole, obiectul comercial corespunzător va arunca o
excepție și va refuza să continue. În acest caz, nu a apărut nicio eroare .NET - în schimb, ați detectat
prezența unei condiții care nu ar trebui să fie permisă în conformitate cu logica aplicației dvs. În exemplele
acestui capitol, obiectele de afaceri vor conține, de asemenea, cod de acces la date. Într-un sistem extrem de
complicat, mare și schimbabil, este posibil să doriți să subdivizați în continuare componentele și să aveți codul
interfeței cu utilizatorul vorbind cu un obiect de afaceri, care, la rândul său, vorbește cu un alt set de obiecte
care interacționează cu sursa de date. Cu toate acestea, pentru majoritatea programatorilor, acest pas
suplimentar este exagerat, mai ales cu nivelul crescut de sofisticare pe ADO.NET îl oferă.

Obiecte de date
Termenul obiect de date este, de asemenea, utilizat într-o varietate de moduri. În această carte, obiectele de
date sunt pur și simplu pachete de date pe care le utilizați pentru a trimite informații între pagina dvs. web și
obiectele dvs. de afaceri. De exemplu, puteți crea o clasă de date numită Angajat care reprezintă informațiile
dintr-o înregistrare dintr-un tabel Angajați, completată cu proprietăți precum Prenume, Nume de familie și
DataNașterii. Un obiect de date tipic este umplut cu proprietăți, dar nu oferă metode.

Componente și clase
Din punct de vedere tehnic, o componentă este doar o colecție de una sau mai multe clase (și, eventual, alte tipuri .NET,
cum ar fi structuri și enumerări) care sunt compilate împreună ca o unitate. De exemplu, System.Web.dll Microsoft este o
singură componentă (dar foarte mare) care oferă tipurile găsite în multe dintre spațiile de nume care încep cu
System.Web.
Până în prezent, exemplele de cod din această carte au folosit doar câteva tipuri de clase - în principal clase
de pagini web personalizate care moștenesc de la System.Web.UI.Page și conțin în mare parte proceduri de
gestionare a evenimentelor. Clasele componente, pe de altă parte, de obicei nu vor include nicio logică a
interfeței cu utilizatorul (ceea ce ar limita inutil utilizarea lor) și nu trebuie să moștenească de la o clasă
existentă.

738
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Crearea unei componente


Pentru a crea o componentă, creați un nou proiect bibliotecă clasă în Visual Studio. Mai întâi, selectați Fișier
Nou Proiect pentru a afișa caseta de dialog Proiect nou. Apoi, alegeți grupul Visual C# din stânga (nu
grupul Web) și alegeți șablonul de proiect Biblioteca clasei (a se vedea Figura 22-2). De asemenea, va trebui
să specificați o locație de fișier și un nume de proiect.

Figura 22-2. Crearea unei componente în Visual Studio

În loc să alegeți doar Fișier → Proiect nou nou pentru a crea biblioteca clasei, o puteți adăuga la
→ aceeași soluție ca și site-ul dvs. Acest lucru facilitează depanarea codului din componentă în timp ce îl testați cu o pagină web.
(Pe cont propriu, nu există nicio modalitate de a rula o componentă, deci nu există nicio modalitate de a o testa.) Pentru a crea o
bibliotecă de clase nouă într-o soluție web existentă, începeți prin a deschide site-ul web, apoi alegeți Adăugare proiect nou.
Specificați directorul și numele proiectului în caseta de dialog Adăugare proiect nou.
Fișier
→ Figura 22-3 prezintă o soluție atât cu un site web, cât și cu o bibliotecă de clase numită
Componente. Site-ul web este scris cu caractere aldine în Exploratorul de soluții pentru a indica faptul
că se execută la pornire (când faceți clic pe butonul Start).

739
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Figura 22-3. O soluție cu un site web și un proiect de bibliotecă de clasă

Pentru a facilita deschiderea acestei soluții, poate doriți să luați un moment pentru a o salva. Faceți clic pe numele soluției (care este
"Componente" în Figura 22-3) în Exploratorul de soluții. Apoi alegeți Salvare fișier
→ [SolutionName] ca. Puteți deschide acest fișier .sln mai târziu pentru a încărca atât site-ul web, cât și
proiectul bibliotecii de clasă. Puteți compila biblioteca de clase în orice moment, făcând clic dreapta pe
proiect în Exploratorul de soluții și alegând Construire. Acest lucru creează un fișier de asamblare DLL
(Components.dll). Nu puteți rula acest fișier direct, deoarece nu este o aplicație și nu oferă nicio interfață cu
utilizatorul.

Clase și spații de nume


După ce ați creat proiectul bibliotecii de clase, sunteți gata să adăugați clase într-un fișier .cs. Proiectele bibliotecii clasei încep cu un fișier numit Class1.cs, pe care îl
puteți utiliza, șterge sau redenumi. De asemenea, puteți adăuga mai multe fișiere de clasă pur și simplu făcând clic dreapta pe proiect în Exploratorul de soluții și
alegând Adăugare
→ Clasa. Singura diferență între proiectele bibliotecii de clasă și aplicațiile web este că fișierele de clasă
nu vor fi plasate într-un subdirector App_Code.

Iată un exemplu care creează o clasă numită SimpleTest:

clasa publică SimpleTest { // (Codul merge aici, în interiorul


uneia sau mai multor metode.) }

Amintiți-vă, o componentă poate conține mai multe clase. Aveți posibilitatea să creați aceste alte clase în
același fișier sau să utilizați fișiere separate pentru o mai bună organizare. În ambele cazuri, toate clasele și
fișierele codului sursă sunt compilate împreună într-un singur ansamblu:
clasa publică SimpleTest { //
Codul clasei omis. } clasa
publică SimpleTest2 { // Codul

740
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

clasei omis. }

De obicei, clasele sunt plasate în interiorul unui bloc de spațiu de nume. Asta înseamnă că codul tău
va arăta astfel:

namespace Componente { public class SimpleTest { // (Codul clasei a fost omis.) }

clasa publică SimpleTest2 { // (Codul


clasei a fost omis.) } }

Când adăugați un nou fișier de clasă la o bibliotecă de clasă, C# adaugă automat un bloc de spațiu de nume
utilizând spațiul de nume implicit pentru proiectul dvs. Clasele din componenta dvs. sunt organizate automat
într-un spațiu de nume denumit după proiectul dvs. De exemplu, dacă ați creat un proiect numit Componente,
clasele SimpleTest și SimpleTest2 vor fi în spațiul de nume Componente (afișat aici), iar numele lor complet
calificate vor fi Components.SimpleTest și Components.SimpleTest2. Trebuie să cunoașteți numele complet
calificat pentru a vă utiliza clasele într-o altă aplicație, deoarece alte aplicații nu vor partaja același spațiu de nume.
Dacă nu vă place spațiul de nume implicit, puteți edita numele spațiului de nume în toate fișierele de cod. Cu toate
acestea, dacă abia începeți într-un proiect nou, există o modalitate mai ușoară - puteți solicita pur și simplu Visual Studio
să modifice spațiul de nume implicit pentru proiectul dvs. În acest fel, ori de câte ori adăugați un nou fișier de clasă la
proiect, Visual Studio va insera un bloc de spațiu de nume care utilizează numele spațiului de nume dorit. (Spațiul de
nume al fișierelor existente nu se modifică.) Pentru a modifica spațiul de nume implicit, începeți prin a face clic dreapta pe
proiect în Exploratorul de soluții și a alege Proprietăți. Veți vedea un afișaj cu mai multe file al setărilor aplicației. Alegeți
fila Aplicație și apoi editați spațiul de nume în caseta text Spațiu de nume implicit. De asemenea, puteți utiliza caseta text
Nume ansamblu din această fereastră pentru a configura numele dat fișierului de asamblare compilat.

Dacă aveți o componentă complexă, puteți alege să o subdivizați în spații de nume imbricate. De exemplu,
este posibil să aveți un spațiu de nume numit Components.Database și altul numit Components.Validation.
Pentru a crea un spațiu de nume imbricat în spațiul de nume implicit al proiectului, utilizați un bloc de spațiu
de nume ca acesta:
namespace Componente { namespace Database { public class SimpleDatabaseTest { // (Codul clasei a fost omis.) } } }

741
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Acum, această clasă are numele complet calificat Components.Database.SimpleDatabaseTest.

Sfat
Regula generală pentru denumirea spațiilor de nume este de a utiliza numele companiei urmat de numele tehnologiei și, opțional, urmat de o diviziune specifică caracteristicii, ca în CompanyName.TechnologyName.Feature. Exemple de spații de nume care urmează după această sintaxă includ Microsoft.Media și Microsoft.Media.Audio. Aceste convenții de spațiu de nume reduc dramatic posibilitatea ca mai multe companii să lanseze componente în aceleași spații de nume, ceea ce ar duce la conflicte de denumire. Singura excepție de la instrucțiunile de denumire este în

ansamblurile de bază care fac parte din .NET. Ei folosesc spații de nume care încep cu Sistem.

Membrii clasei
Pentru a adăuga funcționalitate clasei, adăugați metode sau proprietăți publice. Codul paginii web poate apela
apoi acești membri pentru a prelua informații sau pentru a efectua o sarcină.
Următorul exemplu arată una dintre cele mai simple componente posibile, care nu face altceva decât să
returneze un șir la codul de apelare:

clasa publică SimpleTest { șir public GetInfo(string param) { return "Ați invocat SimpleTest.GetInfo() cu '" + param + "'";

}}

clasa publică SimpleTest2


{
șir public GetInfo(string param) { return "Ați invocat
SimpleTest2.GetInfo() cu '" + param + "'"; }

În secțiunile următoare, veți afla cum să utilizați această componentă într-o aplicație web. Puțin mai târziu,
veți trece la o componentă mai complexă și practică.

Adăugarea unei referințe la componentă


Utilizarea componentei într-o pagină de ASP.NET reală este ușoară. În esență, site-ul dvs. web are nevoie de o copie a
componentei dvs. în directorul Coșului de gunoi. ASP.NET monitorizează automat acest director și pune toate clasele
sale la dispoziția oricărei pagini web din aplicație. Pentru a crea această copie, utilizați o caracteristică Visual Studio
numită referințe.
→ Iată
Adăugați
cum referință
funcționează:
din meniu.
Mai întâi,
Acestselectați
lucru vă site-ul
aduce laweb
caseta
în Exploratorul
de dialog Adăugare
de soluții.
referință.
Apoi, selectați
(Nu alegeți
Site web

742
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Adăugare referință web sau Adăugare referință serviciu, care sunt utilizate pentru a conecta o aplicație la un
serviciu web și au puține în comun cu comanda denumită în mod similar Adăugare referință.)
Puteți lua una dintre cele două abordări în caseta de dialog Adăugare referință:

Adăugați o referință de proiect: Dacă proiectul bibliotecii de clase se află în aceeași soluție, utilizați fila
Proiecte. Aceasta vă arată o listă cu toate proiectele bibliotecii de clasă din soluția curentă (a se vedea
Figura 22-4). Selectați biblioteca clasei și faceți clic pe OK.
Adăugați o referință de asamblare: Dacă biblioteca dvs. de clasă se află într-o altă soluție sau aveți
doar fișierul DLL compilat (poate componenta a fost creată de un alt dezvoltator), utilizați fila Răsfoire
(consultați Figura 22-5). Navigați prin directoare până găsiți fișierul DLL, selectați-l și faceți clic pe OK.

• Notă
Construiți
Dacă utilizați
soluțiaodin
referință
meniulde
Visual
asamblare,
Studio)trebuie
înainte mai
de aîntâi
puteasă adăuga
compilați
referința.
componenta (alegeți Compilare

Figura 22-4. Adăugarea unei referințe de proiect

743
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Figura 22-5. Adăugarea unei referințe de asamblare

În orice caz, .NET copiază fișierul DLL compilat în subdirectorul Bin al aplicației web (a se vedea Figura 22-6).
Veți vedea, de asemenea, un fișier . PDB fișier care conține informații de depanare pentru Visual Studio.

Figura 22-6. O componentă din directorul Bin

744
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Visual Studio are, de asemenea, grijă deosebită pentru a vă asigura că utilizați în continuare cea mai recentă
versiune a componentei. Dacă modificați componenta și o recompilați, Visual Studio va observa modificarea. Data
viitoare când executați aplicația web, Visual Studio va copia automat componenta nouă în subdirectorul Bin.

Dacă utilizați o referință de proiect, Visual Studio face un pas mai departe. De fiecare dată când executați proiectul
site-ului web, Visual Studio verifică dacă există modificări în fișierele de cod sursă ale componentei. Dacă oricare
dintre aceste fișiere a fost modificat, Visual Studio recompilează automat componenta și copiază noua versiune în
subdirectorul Bin din aplicația web.
Când adăugați o referință la o componentă, Visual Studio vă permite, de asemenea, să utilizați clasele sale în
codul dvs. cu verificarea sintaxei obișnuite și IntelliSense. Dacă nu adăugați referința, nu veți putea utiliza
clasele componente (și dacă încercați, Visual Studio interpretează încercările dvs. de a utiliza clasa ca greșeli
și refuză să compileze codul).

Notă Eliminarea unei referințe este puțin mai dificilă. Cel mai simplu mod este să faceți clic dreapta pe proiectul web și să alegeți Pagini de proprietate. Apoi, alegeți Referințe din listă. Veți vedea o listă cu toate referințele dvs. (inclusiv referințele de asamblare și proiect). Pentru a elimina unul, selectați-l și faceți clic pe Eliminare.

Utilizarea componentei
După ce ați adăugat referința, puteți utiliza componenta creând instanțe ale clasei SimpleTest sau
SimpleTest2, așa cum se arată aici:

folosind Componente;

TestPage public de clasă parțială : System.Web.UI.Page


{
protejat void Page_Load(Object sender, EventArgs e) { SimpleTest testComponent = new SimpleTest(); SimpleTest2
testComponent2 = noul SimpleTest2(); lblResult.Text = testComponent.GetInfo("Bună ziua") + "<br><br>"; } lblResult.Text +=
testComponent2.GetInfo("Pa");

Rezultatul pentru această pagină, prezentat în Figura 22-7, combină valoarea returnată din
ambele metode GetInfo().

745
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Figura 22-7. Ieșirea componentei SimpleTest

Pentru a simplifica puțin acest cod, puteți alege să utilizați metode statice în clasa de componente, astfel
încât să nu fie nevoie să creați o instanță înainte de a utiliza metodele. O metodă statică GetInfo() arată
astfel:
clasa publică SimpleTest { public șir static GetInfo(string param) { return "Ați invocat SimpleTest.GetInfo() cu '" + param + "'"; } }

În acest caz, pagina web accesează metoda statică GetInfo() prin numele clasei și nu trebuie să creeze un
obiect:

vid protejat Page_Load(Object sender, EventArgs e) {


lblResult.Text = SimpleTest.GetInfo("Hello"); }

Sfat că, dacă utilizați referințe de asamblare și componenta și aplicația web nu se află în aceeași soluție, nu veți
• Rețineți
vedea imediat efectul modificărilor. În schimb, trebuie să recompilați ansamblul componentelor (alegeți Construire

→ Build Solution) și apoi reconstruiți aplicația web. Dacă utilizați referințe de proiect, acest
lucru nu este necesar - Visual Studio observă fiecare modificare efectuată și recompilează
automat componenta.

746
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Decizia când să utilizați metode de instanță și când să utilizați metode statice face parte din arta designului orientat pe
obiecte și necesită experiență. Metodele statice impun considerații suplimentare - și anume, clasa dvs. trebuie să fie
apatridă (un concept descris în secțiunea următoare), ceea ce înseamnă că nu poate reține informații suplimentare în
variabilele membre. Dacă da, riscă un potențial conflict dacă mai multe bucăți de cod utilizează componenta în același
timp.
Ca regulă generală, utilizați metode de instanță dacă trebuie să puteți crea mai multe instanțe ale clasei dvs. în același
timp. De exemplu, metodele de instanță au sens pentru clasa SqlConnection, deoarece puteți alege să deschideți o
conexiune la mai multe baze de date diferite pentru o singură operație. Metodele de instanță sunt, de asemenea, cea mai
bună alegere dacă doriți să configurați un obiect o dată și să îl utilizați de mai multe ori. De exemplu, clasa SqlConnection
vă permite să setați șirul de conexiune și apoi să deschideți și să închideți conexiunea cât de mult este necesar. Pe de
altă parte, luați în considerare metodele statice dacă metodele dvs. efectuează o singură sarcină discretă care nu
necesită nicio inițializare. Exemplele includ calculele din clasa de matematică și activitățile de afaceri (cum ar fi
înregistrarea unui client nou) într-o componentă de afaceri la nivel înalt.

Proprietăți și stare
Clasele SimpleTest oferă funcționalitate prin metode publice. Dacă sunteți familiarizat cu programarea bazată pe clase
(așa cum este descris în capitolul 3), vă veți aminti că clasele pot, de asemenea, să stocheze informații în variabile private
pentru membri și să ofere proceduri de proprietate care permit codului de apelare să modifice aceste informații. De
exemplu, o clasă Persoană poate avea o proprietate Prenume.
Când utilizați proprietăți și stocați informații în variabilele membrilor, utilizați designul stării. În proiectarea
statală, clasa are responsabilitatea de a menține anumite informații. În proiectarea apatridă, nu se păstrează
informații între apelurile metodei. Comparați clasa anterioară SimpleTest, care utilizează designul apatrid, cu
clasa statală SimpleTest prezentată aici:
clasa publică SimpleTest
{
date șir private; șir
public de date { get {
return data; } set { data
= value; } }

șir public GetInfo() { return "You invoked


SimpleTest.GetInfo()," + "and data is '" + data + "'"; }

Programatorii care proiectează aplicații la scară largă (cum ar fi aplicațiile web) dezbat uneori dacă
programarea statală sau apatridă este cea mai bună. Programarea statală este abordarea cea mai naturală,
orientată spre obiecte, dar are și câteva dezavantaje. Pentru a efectua o activitate obișnuită, poate fi
necesar să setați mai multe proprietăți înainte de a apela o metodă. Fiecare dintre acești pași adaugă un pic
de cheltuieli inutile. Un design apatrid, pe de altă parte, își desfășoară adesea toată activitatea într-un singur
apel de metodă. Cu toate acestea, deoarece nicio informație nu este păstrată în stare, poate fi necesar să
specificați mai mulți parametri, ceea ce poate face programarea obositoare. Un bun exemplu de obiecte
statale versus apatride este prezentat de clasele FileInfo și File, care sunt descrise în capitolul 17.

747
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Nu există un răspuns scurt cu privire la faptul dacă designul statal sau apatrid este cel mai bun și depinde adesea
de sarcina la îndemână. Componentele care sunt de înaltă performanță, componentele care utilizează tranzacții,
componentele care utilizează resurse limitate, cum ar fi o conexiune la baza de date sau componentele care
trebuie invocate de la distanță (cum ar fi serviciile web) utilizează de obicei designul fără stare, care este cea mai
simplă și mai fiabilă abordare. Deoarece nicio informație nu este păstrată în memorie, sunt utilizate mai puține
resurse de server și nu există pericolul de a pierde date valoroase dacă apare o defecțiune software sau
hardware. Următorul exemplu ilustrează diferența cu două moduri de a proiecta o clasă de cont.

O clasă de conturi statale


Luați în considerare o clasă de conturi statale care reprezintă un singur cont de client. Informațiile sunt citite
din baza de date atunci când sunt create pentru prima dată în metoda constructorului, iar aceste informații pot
fi actualizate utilizând metoda Update().
clasă publică CustomerAccount
{
private int accountNumber;
soldul zecimal privat;
Descărcați de la Wow! eBook <www.wowebook.com>

public zecimal Sold { get { return balance; } set } { balance = value; }

public CustomerAccount (int accountNumber) { // (Codul pentru citirea


înregistrării contului din baza de date merge aici.) }

public void Update() { // (Codul pentru actualizarea


înregistrării bazei de date merge aici.) }

Dacă aveți două obiecte CustomerAccount care expun o proprietate Sold, trebuie să efectuați doi pași
separați pentru a transfera bani dintr-un cont în altul. Conceptual, procesul funcționează astfel:

Creați un obiect de cont pentru fiecare cont, // folosind


numărul contului.
CustomerAccount accountOne = nou CustomerAccount(122415);
CustomerAccount accountTwo = nou CustomerAccount(123447);
suma zecimală = 1000;
Retrageți bani dintr-un singur cont.
accountOne.Balance -= suma;
Depuneți bani în celălalt cont.

748
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

accountTwo.Balance += suma; Actualizați înregistrările bazei de date


subiacente utilizând o metodă de actualizare. accountOne.Update();
accountTwo.Update();

Problema aici este că, dacă această sarcină este întreruptă la jumătatea drumului de o eroare, veți ajunge la
cel puțin un client nemulțumit.

A clasă Stateless AccountUtility


Un obiect apatrid ar putea expune doar o metodă statică numită FundTransfer(), care își desfășoară toată
activitatea într-o singură metodă:

clasă publică AccountUtility


{
public static void FundTransfer(int accountOne, int accountTwo,
sumă zecimală) { // (Codul de aici preia cele două înregistrări ale
bazei de date, // le modifică și le actualizează.) }

}
Codul de apelare nu poate utiliza aceleași obiecte elegante CustomerAccount, dar poate fi sigur că transferurile
de cont sunt protejate împotriva erorilor. Deoarece toate operațiunile bazei de date sunt efectuate simultan,
acestea pot utiliza o procedură stocată în baza de date pentru o performanță mai mare și pot utiliza o tranzacție
pentru a se asigura că retragerea și depunerea fie reușesc, fie eșuează în ansamblu.

Setați detaliile contului și transferului. suma


zecimală = 1000; int accountIDOne = 122415;
int accountIDTwo = 123447;

AccountUtility.FundTransfer(accountIDne, accountIDTwo,
suma);
Într-un sistem critic pentru misiune, tranzacțiile sunt adesea necesare. Din acest motiv, clasele care
păstrează puține informații de stare sunt adesea cea mai bună abordare de proiectare, chiar dacă nu sunt la
fel de satisfăcătoare dintr-o perspectivă orientată pe obiecte.

Sfat: Există un potențial compromis. Puteți crea clase statale pentru a reprezenta elemente comune, cum ar fi conturi, clienți și așa mai departe, fără a adăuga nicio funcționalitate. Apoi, puteți utiliza aceste clase ca pachete de date pentru a trimite informații către și de la o clasă de utilitate fără stat. (Acestea sunt obiectele de date care au fost descrise la începutul acestui capitol.)

749
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Componente de acces la date


În mod clar, componentele sunt extrem de utile. Dar dacă începeți un proiect mare de programare, este posibil să nu fiți
sigur care caracteristici sunt cei mai buni candidați pentru a fi transformate în componente separate. Învățarea modului
de a împărți o aplicație în componente și clase este una dintre marile arte ale programării și necesită multă practică și
reglaj fin.
Unul dintre cele mai comune tipuri de componente este o componentă de acces la date.
Componentele de acces la date sunt o aplicație ideală de programare bazată pe componente din mai
multe motive:
Bazele de date necesită detalii străine: Aceste detalii includ șiruri de conexiune, nume de câmpuri și
așa mai departe, toate acestea putând distrage atenția de la logica aplicației și pot fi ușor încapsulate
de o componentă bine scrisă.
Bazele de date evoluează în timp: Chiar dacă structura tabelului de bază rămâne constantă și nu sunt
necesare niciodată informații suplimentare (ceea ce este departe de a fi sigur), interogările pot fi
înlocuite cu proceduri stocate, iar procedurile stocate pot fi reproiectate.
Bazele de date au cerințe speciale de conexiune: Poate fi necesar chiar să modificați codul de acces la
baza de date din motive care nu au legătură cu aplicația. De exemplu, după crearea de profiluri și
testarea unei baze de date, este posibil să descoperiți că puteți înlocui o singură interogare cu două
interogări sau cu o procedură stocată mai eficientă. În ambele cazuri, datele returnate rămân constante,
dar codul de acces la date este dramatic diferit.
Bazele de date sunt utilizate în mod repetat într-un set finit de moduri: Cu alte cuvinte, o rutină
comună a bazei de date ar trebui să fie scrisă o singură dată și este sigur că va fi utilizată de mai
multe ori.

O componentă simplă de acces la date


Pentru a examina cel mai bun mod de a crea o componentă de acces la date, veți lua în considerare o
aplicație simplă care oferă o pagină de anunțuri care listează articole pe care diverse persoane le au de
vânzare. Baza de date utilizează două tabele: unul este un tabel Elemente care listează descrierea și prețul
unui anumit articol de vânzare, iar celălalt este un tabel Categorii care listează diferitele grupuri pe care le
puteți utiliza pentru a clasifica un element. Figura 22-8 prezintă relația.

Figura 22-8. Relațiile din baza de date AdBoard

În acest exemplu, vă conectați la o bază de date SQL Server utilizând ADO.NET. Puteți crea singur această bază de
date sau puteți consulta eșantioanele online, care includ un script SQL care o generează automat. Pentru început,
tabelul Categorii este preîncărcat cu un set standard de categorii permise. Componenta de acces la date este simplă.
Este o singură clasă (numită DBUtil), care este plasată într-un spațiu de nume numit DatabaseComponent (care este
spațiul de nume implicit pentru proiect). Clasa DBUtil utilizează metode de instanță și păstrează unele informații de
bază (cum ar fi șirul de conexiune de utilizat), dar nu permite clientului să modifice aceste informații. Prin urmare, nu
are nevoie de proceduri de proprietate. În schimb, își desfășoară cea mai mare parte a activității în metode precum

750
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

GetCategories() și GetItems(). Aceste metode returnează seturile de date cu înregistrările corespunzătoare ale bazei de
date. Acest tip de design creează un strat destul de subțire peste baza de date - gestionează unele detalii, dar clientul
este încă responsabil pentru lucrul cu clase de ADO.NET familiare, cum ar fi DataSet.

Notă Pentru a utiliza acest exemplu așa cum este scris, trebuie să adăugați o referință la ansamblurile System.Configuration.dll și System.Web.dll din biblioteca clasei. În caz contrar, nu puteți utiliza WebConfigurationManager pentru a dezgropa șirul de conexiune de care aveți nevoie. Pentru a adăuga aceste referințe, selectați Proiect


→ Adăugați referință și căutați în fila .NET.

folosind Sistem; utilizarea


System.Data; folosind
System.Data.SqlClient; folosind
System.Web.Configuration;
namespace DatabaseComponent
{
clasa publică DBUtil { private string
connectionString;

public DBUtil() { connectionString = WebConfigurationManager.ConnectionStrings[ } "AdBoard"]. ConnectionString;

set public de date GetCategories() { string query = "SELECT * FROM Categories"; SqlCommand cmd = noul
SqlCommand(interogare); } returnați FillDataSet (cmd, "Categorii");

set de date publice GetItems() { string query = "SELECT * FROM Items"; SqlCommand cmd = noul
SqlCommand(interogare); } returnați FillDataSet (cmd, "Items");

set de date public GetItems(int categoryID) { // Creați comanda.

interogare șir = "SELECT * FROM Items WHERE Category_ID=@CategoryID";


SqlCommand cmd = noul SqlCommand(interogare);
cmd.Parameters.AddWithValue("@CategoryID", categoryID); } // Completați setul de date.

751
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

returnați FillDataSet (cmd, "Items");

public void AddCategory(string name) { SqlConnection con = new


SqlConnection(connectionString);

} // Creați comanda.
string insertSQL = "INSERAȚI ÎN Categorii "; insertSQL
+= "(Nume) VALORI @Name"; SqlCommand cmd =
nou SqlCommand(insertSQL, con);
cmd.Parameters.AddWithValue("@Name", nume);
Încercați { con. Deschis();
cmd.ExecuteNonQuery(); } în
cele din urmă { con.
Aproape(); }

public void AddItem(titlul șirului, descrierea șirului, prețul zecimal, int


categoryID) { SqlConnection con = noul
SqlConnection(connectionString);

} // Creați comanda.
string insertSQL = "INSERAȚI ÎN ELEMENTE "; insertSQL += "(titlu,
descriere, preț, Category_ID)"; insertSQL += "VALORI (@Title,
@Description, @Price, @CategoryID)"; SqlCommand cmd = nou
SqlCommand(insertSQL, con); cmd.Parameters.AddWithValue("@Title",
titlu); cmd.Parameters.AddWithValue("@Description", descriere);
cmd.Parameters.AddWithValue("@Price", preț);
cmd.Parameters.AddWithValue("@CategoryID", categoryID);

Încercați { con. Deschis();


cmd.ExecuteNonQuery(); } în
cele din urmă { con.
Aproape(); }

private DataSet FillDataSet(SqlCommand cmd, string tableName) {


SqlConnection con = new SqlConnection(connectionString);

752
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

cmd.Connection = con; Adaptor SqlDataAdapter = noul


SqlDataAdapter(cmd);

DataSet ds = set de date nou();


Încercați { con. Deschis(); adaptor.
Fill(ds, tableName); } în cele din
urmă { con. Aproape(); } returnează
DS;

}
}
}

Disecarea codului . . .
• Când este creat un obiect DBUtil, constructorul preia automat șirul de conexiune din
fișierul web.config, folosind tehnica descrisă în capitolul 5. Cu toate acestea, este
important să rețineți că acesta este fișierul web.config al aplicației web (deoarece
componenta nu are un fișier de configurare). Acesta este un design bun, deoarece
permite unui site web să utilizeze componenta bazei de date cu orice server de baze de
date. Cu toate acestea, dacă aplicația web client nu are setarea de configurare
corespunzătoare, componenta bazei de date nu va funcționa.
• Codul include metode de preluare a datelor (acele metode care încep cu Get) și
metode de actualizare a datelor (acele metode care încep cu Add).
• Această clasă include o metodă supraîncărcată numită GetItems(). Aceasta
înseamnă că clientul poate apela GetItems() fără parametri pentru a returna lista
completă sau cu un parametru care indică categoria corespunzătoare. (Capitolul
2 oferă o introducere în metodele supraîncărcate.)
• Fiecare metodă care accesează baza de date deschide și închide conexiunea.
Aceasta este o abordare mult mai bună decât încercarea de a menține o conexiune
deschisă pe toată durata de viață a clasei, ceea ce va duce cu siguranță la degradarea
performanței în scenarii multiutilizator.

Sfat
Serverul dvs. web poate deschide și închide conexiunile frecvent fără a provoca nicio încetinire. Acest lucru se datorează faptului că ADO.NET utilizează gruparea conexiunilor pentru a menține un set mic de conexiuni deschise gata de utilizare. Atâta timp cât nu modificați șirul de conexiune și atâta timp cât există conexiuni disponibile în pool, atunci când apelați SqlConnection.Open(), primiți una dintre aceste conexiuni, evitând astfel cheltuielile generale de

configurare a unei noi conexiuni.

• Codul utilizează propria funcție privată FillDataSet() pentru a face codul mai
concis. Acest lucru nu este pus la dispoziția clienților. În schimb, metodele
GetItems() și GetCategories() utilizează funcția FillDataSet().

753
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Utilizarea componentei de acces la date


Pentru a utiliza această componentă într-o aplicație web, trebuie mai întâi să vă asigurați că șirul de
conexiune corespunzător este configurat în fișierul web.config, așa cum se arată aici:

<configurare>

<connectionStrings> <add name="AdBoard" connectionString= "Data


Source=localhost\SQLEXPRESS; catalog inițial = AdBoard; Securitate integrată = SSPI" / > < /
connectionStrings> ...

</configurare>

Apoi, compilați și copiați fișierul DLL component sau adăugați o referință la acesta dacă utilizați Visual Studio.
Singura sarcină rămasă este să adăugați interfața cu utilizatorul pentru pagina web care utilizează
componenta. Pentru a testa această componentă, puteți crea o pagină de test simplă. În exemplul prezentat
în Figura 22-9, această pagină permite utilizatorilor să răsfoiască lista curentă pe categorii și să adauge
elemente noi. Când utilizatorul vizitează prima dată pagina, îi solicită utilizatorului să selecteze o categorie.

Figura 22-9. Listarea AdBoard

Odată ce o categorie este aleasă, se afișează elementele corespunzătoare și apare un panou de comenzi,
care permite utilizatorului să adauge o nouă intrare în AdBoard în categoria curentă, așa cum se arată în
Figura 22-10.

754
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Figura 22-10. Listarea AdBoard

Pentru a accesa componenta mai ușor, pagina web importă spațiul său de nume:
folosind DatabaseComponent;
Codul paginii creează componenta pentru a prelua informații din baza de date și o afișează legând setul de
date la lista verticală sau la controlul GridView:

public clasa parțială AdBoard : System.Web.UI.Page


{
protejat void Page_Load(Object sender, EventArgs e) { if
(!this. IsPostBack) { DBUtil DB = new DBUtil();

lstCategories.DataSource = DB. GetCategories();


lstCategories.DataTextField = "Nume";
lstCategories.DataValueField = "ID";
lstCategories.DataBind(); pnlNew.Visible = fals; } }

755
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

vid protejat cmdDisplay_Click(Object sender, EventArgs e) { DBUtil


DB = new DBUtil();

gridItems.DataSource = DB. GetItems(


Int32.Parse(lstCategories.SelectedItem.Value));
gridItems.DataBind(); pnlNew.Visible = adevărat;
}

vid protejat cmdAdd_Click(Object sender, EventArgs e) { DBUtil


DB = new DBUtil();

încercați { DB. AddItem(txtTitle.Text, txtDescription.Text,


Decimal.Parse(txtPrice.Text),
Int32.Parse(lstCategories.SelectedItem.Value));

gridItems.DataSource = DB. GetItems(


Int32.Parse(lstCategories.SelectedItem.Value));
gridItems.DataBind();
}
catch (FormatException err) { // Apare o eroare dacă utilizatorul a introdus un preț nevalid // (caractere non-numerice). //
În acest caz, nu luați nicio măsură.

O altă opțiune este să adăugați un control validator } // pentru

caseta de text preț pentru a preveni introducerea nevalidă.


}
}

Disecarea codului . . .
• Nu toate funcționalitățile componentei sunt utilizate în această pagină. De
exemplu, pagina nu utilizează metoda AddCategory() sau versiunea de GetItems()
care nu necesită un număr de categorie. Acest lucru este complet normal. Alte
pagini pot utiliza caracteristici diferite de componentă.
• Codul paginii web nu conține cod de acces la date. Cu toate acestea, trebuie să
înțeleagă cum să utilizeze un set de date și trebuie să cunoască nume specifice de
câmpuri pentru a crea un GridView mai atractiv, cu șabloane personalizate pentru
aspect (în loc de coloane generate automat).
• Pagina poate fi îmbunătățită cu ajutorul codului de tratare a erorilor sau al
controalelor de validare. În prezent, nu se efectuează nicio validare pentru a se
asigura că prețul este numeric sau chiar pentru a se asigura că valorile necesare sunt
furnizate.

756
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Sfat
codificați direct în codul componentei, chiar dacă nu face parte din aceeași soluție. Fișierul de cod sursă corespunzător este încărcat automat în editor, atâta timp cât este disponibil (și ați compilat componenta în modul de depanare).
Dacă depanați codul în Visual Studio, veți descoperi că puteți face un singur pas din pagina web

Îmbunătățirea componentei cu gestionarea erorilor


O modalitate prin care puteți îmbunătăți componenta este cu un suport mai bun pentru raportarea erorilor. Așa cum
este, orice erori ale bazei de date care apar sunt imediat returnate la codul de apelare. În unele cazuri (de exemplu,
dacă există o problemă legitimă a bazei de date), aceasta este o abordare rezonabilă, deoarece componenta nu poate
rezolva problema.
Cu toate acestea, componenta nu reușește să rezolve corect o problemă comună. Această problemă apare dacă șirul de
conexiune nu este găsit în fișierul web.config. Deși componenta încearcă să citească șirul de conexiune imediat ce este
creat, codul de apelare nu își dă seama că există o problemă până când nu încearcă să utilizeze o metodă de bază de
date.
O abordare mai bună este să notificați clientul imediat ce problema este detectată, așa cum se
arată în următorul exemplu de cod:

clasa publică DBUtil


{
conexiune privată la șirȘir;

public DBUtil() { if (WebConfigurationManager.ConnectionStrings["AdBoard"] == null) { throw new ApplicationException(


"Missing ConnectionString variable in web.config."); } else { connectionString =
WebConfigurationManager.ConnectionStrings[ "AdBoard"]. ConnectionString; } }

(Alt cod de clasă a fost omis.)


}

Acest cod lansează un ApplicationException cu un mesaj de eroare particularizat care indică problema.
Pentru a oferi o raportare și mai bună, puteți crea propria clasă de excepții care moștenește de la
ApplicationException, așa cum este descris în capitolul 7.

757
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Sfat: Componentele captează adesea excepțiile care apar în timpul activităților de nivel scăzut (cum ar fi citirea unui fișier sau interacțiunea cu o bază de date) și apoi aruncă excepții mai puțin detaliate, cum ar fi ApplicationException, pentru a notifica pagina web. În acest fel, nu există nicio șansă ca utilizatorul să vadă informațiile despre eroarea tehnică. Acest lucru este important, deoarece mesajele de eroare detaliate pot oferi hackerilor indicii despre modul în care funcționează codul

dvs. și cum să îl submineze.

Îmbunătățirea componentei cu informații agregate


Componenta nu trebuie să limiteze tipul de informații pe care le furnizează seturilor de date. Alte informații
sunt, de asemenea, utile. De exemplu, puteți furniza o proprietate doar în citire numită ItemFields care
returnează o matrice de șiruri reprezentând numele câmpurilor din tabelul Items. Sau puteți adăuga o altă
metodă care regăsește informații agregate despre întregul tabel, cum ar fi costul mediu al elementelor din
baza de date sau numărul total de elemente listate în prezent, așa cum se arată aici:
clasa publică DBUtil
{
Descărcați de la Wow! eBook <www.wowebook.com>

(Alt cod de clasă a fost omis.)

zecimal public GetAveragePrice() { string query =


"SELECT AVG(Price) FROM Items";

SqlConnection con = nou SqlConnection(connectionString);


SqlCommand cmd = nou SqlCommand(interogare, con);

înșela. Deschis(); medie zecimală =


(zecimal)cmd.ExecuteScalar(); înșela. Aproape();

media de returnare;
}

public int GetTotalItems() { string query = "SELECT


Count(*) FROM Items";

SqlConnection con = nou SqlConnection(connectionString);


SqlCommand cmd = nou SqlCommand(interogare, con);

înșela. Deschis(); int count =


(int)cmd.ExecuteScalar(); înșela.
Aproape();
numărul de returnări;
}
}

758
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Aceste interogări utilizează unele SQL care pot fi noi pentru dvs. (și anume, funcțiile agregate COUNT și
AVG). Cu toate acestea, aceste metode sunt la fel de ușor de utilizat din perspectiva clientului ca
GetItems() și GetCategories():
DBUtil DB = new DBUtil(); medie zecimalăPreț =
DB. GetAveragePrice(); int totalItems = DB.
GetTotalItems();
Este posibil să vă fi gândit că puteți returna informații, cum ar fi numărul total de elemente, printr-o procedură
de proprietate doar în citire (cum ar fi TotalItems) în loc de o metodă (în acest caz, GetTotalItems). Deși acest
lucru funcționează, procedurile de proprietate sunt mai bine lăsate la informații care sunt menținute cu clasa
(într-o variabilă privată) sau sunt ușor de reconstruit. În acest caz, este nevoie de o operațiune de bază de
date pentru a număra numărul de rânduri, iar această operațiune a bazei de date poate provoca o problemă
neobișnuită sau poate încetini performanța dacă este utilizată frecvent. Pentru a ajuta la întărirea acestui fapt,
se folosește o metodă în locul unei proprietăți.

The ObjectDataSource
Utilizarea unei componente dedicate bazei de date este o modalitate excelentă de a vă menține codul eficient și bine
organizat. De asemenea, vă este ușor să aplicați modificările mai târziu. Cu toate acestea, acest lucru are un dezavantaj - și
anume, trebuie să scrieți destul de puțin cod pentru a crea o pagină web și o componentă separată de acces la date. În
capitolul 15, ați văzut că vă puteți simplifica viața folosind componente precum SqlDataSource pentru a încapsula toate
detaliile de acces la date. Din păcate, această abordare fără cod nu va funcționa dacă utilizați o componentă separată - sau o
va face?
Se pare că există o modalitate de a obține cele mai bune din ambele lumi și de a utiliza o componentă
separată de acces la date și o legare mai ușoară a datelor paginilor web. În loc să utilizați SqlDataSource,
utilizați ObjectDataSource, care definește o legătură între pagina web și componenta. Acest lucru nu vă va
salva de la scrierea codului real de acces la date în componenta dvs., dar vă va salva de la scrierea codului
obositor în pagina dvs. web pentru a apela metodele din componenta dvs., a extrage datele, a le formata și a
le afișa în pagină.

Notă ObjectDataSource vă permite să creați pagini web fără cod, dar tot trebuie să scrieți codul în componentă. Nu ar trebui să vedeți acest lucru ca pe un dezavantaj - la urma urmei, trebuie să scrieți acest cod pentru a obține un control fin asupra a ceea ce se întâmplă și, prin urmare, pentru a optimiza performanța strategiei dvs. de acces la date.

În secțiunile următoare, veți învăța cum să luați clasa DBUtil existentă prezentată mai devreme și să o utilizați
într-o pagină web legată de date. Veți învăța cum să reproduceți exemplul prezentat în Figura 22-9 și Figura
22-10 fără a scrie niciun cod de pagină web.

Făcând clase pe care ObjectDataSource le poate înțelege


În esență, ObjectDataSource vă permite să creați o legătură declarativă între controalele paginii dvs. web și o
componentă de acces la date care interoghează și actualizează datele. Deși ObjectDataSource este
remarcabil de flexibil, nu poate suporta toate componentele imaginabile pe care le-ați putea crea. De fapt,
pentru ca componenta de date să poată fi utilizată cu ObjectDataSource, trebuie să respectați câteva reguli:

759
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

• Clasa dumneavoastră trebuie să fie apatridă. Acest lucru se datorează faptului că ObjectDataSource
va crea o instanță numai atunci când este necesar și o va distruge la sfârșitul fiecărei solicitări.

• Clasa dvs. trebuie să aibă un constructor implicit, fără argumente.


• Toată logica trebuie să fie cuprinsă într-o singură clasă. (Dacă doriți să utilizați
clase diferite pentru selectarea și actualizarea datelor, va trebui să le încadrați într-o
altă clasă de nivel superior.)
• Rezultatele interogării trebuie furnizate sub formă de DataSet, DataTable sau un fel
de colecție de obiecte. (Dacă decideți să utilizați o colecție de obiecte, fiecare obiect
de date trebuie să expună toate câmpurile de date ca proprietăți publice.)
Din fericire, multe dintre aceste reguli sunt cele mai bune practici pe care ar trebui să le urmați deja. Chiar
dacă clasa DBUtil nu a fost proiectată în mod expres pentru ObjectDataSource, îndeplinește toate aceste
criterii.

Selectarea înregistrărilor
Puteți afla multe despre ObjectDataSource construind pagina prezentată în Figura 22-10. În secțiunile
următoare, veți aborda această provocare.
Primul pas este să creați caseta listă cu lista categoriilor. Pentru această listă, aveți nevoie de un ObjectDataSource
care se leagă la clasa DBUtil și apelează metoda GetCategories() pentru a regăsi lista completă de înregistrări de
categorii.
Pentru a furniza date în caseta listă, trebuie să definiți un ObjectDataSource și să indicați numele clasei care
conține metodele de acces la date. Faceți acest lucru specificând numele clasei complet calificat cu
proprietatea TypeName, așa cum se arată aici:
<asp:ObjectDataSource ID="sourceCategories" runat="server" ... />

TypeName = "DatabaseComponent.DBUto"
După ce ați atașat ObjectDataSource la o clasă, următorul pas este să o indicați către metodele pe care le poate
utiliza pentru a selecta și actualiza înregistrări.
ObjectDataSource definește proprietățile SelectMethod, DeleteMethod, UpdateMethod și InsertMethod pe
care le utilizați pentru a lega clasa de acces la date la diverse activități. Fiecare proprietate ia numele
metodei în clasa de acces la date. În acest exemplu, trebuie pur și simplu să activați interogarea, deci trebuie
să setați proprietatea SelectMethod, astfel încât să apeleze metoda GetCategories():
<asp:ObjectDataSource ID="sourceCategories" runat="server" TypeName="DatabaseComponent.DBUtil"

SelectMethod="GetCategories" />

După ce ați configurat ObjectDataSource, puteți lega controalele paginii web în același mod în care faceți
cu SqlDataSource. Iată eticheta de care aveți nevoie pentru caseta listă:

<asp:DropDownList ID="lstCategories" runat="server"


DataSourceID="sourceCategories" DataTextField="Name" DataValueField="ID">
</asp:DropDownList>
Această etichetă afișează o listă de nume de categorii (datorită proprietății DataTextField) și, de asemenea, ține
evidența ID-ului categoriei (utilizând proprietatea DataValueField).
Acest exemplu funcționează bine până acum. Puteți rula pagina web de testare și puteți vedea lista
categoriilor din listă (așa cum se arată în Figura 22-9).

760
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Utilizarea parametrilor metodei


Următorul pas este să afișați lista de elemente din categoria curentă în GridView de dedesubt. Ca și în cazul SqlDataSource,
ObjectDataSource poate fi utilizat numai pentru o singură interogare. Asta înseamnă că va trebui să creați un al doilea
ObjectDataSource care să poată prelua lista de elemente apelând GetItems(). Trucul aici este că metoda GetItems() necesită
un singur parametru (numit categoryID). Asta înseamnă că trebuie să creați un ObjectDataSource care include un singur
parametru. Puteți utiliza toate tipurile de parametri utilizați cu SqlDataSource pentru a obține valori din șirul de interogare, alte
controale și așa mai departe. În acest caz, ID-ul categoriei este furnizat de proprietatea SelectedValue din caseta listă, astfel
încât să puteți utiliza un parametru de control care indică spre această proprietate.

Iată definiția ObjectDataSource de care aveți nevoie:

<asp:ObjectDataSource ID="sourceItems" runat="server" SelectMethod="GetItems"


TypeName="DatabaseComponent.DBUtil" > <SelectParameters> <asp:ControlParameter
ControlID="lstCategories" name="categoryID" PropertyName="SelectedValue"
type="Int32" /> </SelectParameters> </asp:ObjectDataSource>

Din nou, utilizați clasa DBUtil, dar de data aceasta este metoda GetItems() de care aveți nevoie. Chiar dacă există două
versiuni supraîncărcate ale metodei GetItems() (una care ia un parametru categoryID și una care nu), nu trebuie să vă
faceți griji. ObjectDataSource utilizează automat supraîncărcarea corectă uitându-se la parametrii pe care i-ați definit.

În acest caz, utilizați un singur parametru care extrage ID-ul categoriei selectate din caseta listă și îl
transmite la metoda GetItems(). Observați că numele definit în eticheta ControlParameter se potrivește cu
numele parametrului metodei GetItems(). Aceasta este o cerință absolută. ObjectDataSource caută metoda
GetItems() utilizând reflexia și verifică dacă orice potrivire potențială are numărul de parametri, nume de
parametri și tipuri de date pe care le-ați indicat. Acest proces de căutare permite ObjectDataSource să
distingă între diferite versiuni supraîncărcate ale aceleiași metode. Dacă ObjectDataSource nu poate găsi
metoda specificată, se ridică o excepție în acest moment.

Sfat Dacă aveți vreodată îndoieli cu privire la metoda apelată în componenta de acces la date, plasați un punct de întrerupere pe metodele posibile și utilizați caracteristicile de depanare ale Visual Studio (așa cum este descris în capitolul 4).

Ultimul pas este să conectați GridView la noul ObjectDataSource utilizând DataSourceID. Iată eticheta care
o face:
<asp:GridView ID="gridItems" runat="server" DataSourceID="sourceItems"/>
Aceasta este tot ce aveți nevoie. Ar trebui să păstrați butonul Afișare, deoarece declanșează o postback a
paginii și permite ObjectDataSource să înceapă să funcționeze. (Dacă nu doriți să utilizați acest buton,
setați proprietatea AutoPostback din caseta listă la Adevărat, astfel încât să se afișeze înapoi ori de câte ori
modificați selecția.) Nu trebuie să scrieți niciun cod de gestionare a evenimentelor pentru a reacționa atunci
când se face clic pe buton. Interogările sunt executate automat, iar controalele sunt legate automat.

761
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Actualizarea înregistrărilor
Ultimul pas este de a oferi utilizatorului o modalitate de a adăuga elemente noi. Cel mai simplu mod de a face acest
lucru posibil este să utilizați un control de date îmbogățit care se ocupă de înregistrări individuale, fie DetailsView, fie
FormsView. DetailsView este cel mai simplu dintre cele două, deoarece nu necesită un șablon. Este cel folosit în
exemplul următor.
În mod ideal, ați defini DetailsView folosind o etichetă ca aceasta și lăsați-o să genereze toate câmpurile
de care are nevoie pe baza sursei de date legate:
<asp:DetailsView ID="detailsAddItem" runat="server" DataSourceID="sourceItems"/>
Din păcate, acest lucru nu va funcționa în acest exemplu. Problema este că această abordare creează prea
multe domenii. În acest exemplu, nu doriți ca utilizatorul să specifice ID-ul elementului (setat automat de baza
de date) sau ID-ul categoriei (care se bazează pe categoria selectată în prezent). Deci, niciunul dintre aceste
detalii nu ar trebui să apară. Singura modalitate de a vă asigura că acest lucru este cazul este să dezactivați
generarea automată a câmpului și să definiți în mod explicit fiecare câmp dorit, așa cum se arată aici:
<asp:DetailsView ID="detailsAddItem" runat="server"
DataSourceID="sourceItems" AutoGenerateRows="False"> <Fields>
<asp:BoundField DataField="Title" HeaderText="Title" /> <asp:BoundField
DataField="Price" HeaderText="Price"/> <asp:BoundField DataField="Description"
HeaderText="Description" /> </Fields> </asp:DetailsView>

Trebuie să faceți alte câteva modificări. Pentru a permite inserarea, trebuie să setați
AutoGenerateInsertButton la True. În acest fel, DetailsView creează linkurile care vă permit să începeți să
introduceți o înregistrare nouă și apoi să o inserați. În același timp, puteți seta proprietatea DefaultMode la
Insert. În acest fel, DetailsView este întotdeauna în modul inserare și este utilizat exclusiv pentru adăugarea
de înregistrări (nu afișarea acestora), la fel ca pagina non-legată de date prezentată anterior.
<asp:DetailsView ID="detailsAddItem" runat="server"
DefaultMode = "Inser" AutoGenerateInsertButton = "True"
DataSourceID = "sourceItems" AutoGenerateRows = "False"> ...
</asp:DetailsView>

ObjectDataSource oferă același tip de suport pentru legarea datelor actualizabile ca SqlDataSource.
Primul pas este să specificați InsertMethod, care trebuie să fie o metodă publică din aceeași clasă:

<asp:ObjectDataSource ID="sourceItems" runat="server"


TypeName="DatabaseComponent.DBUtil" SelectMethod="GetItems"

InsertMethod="AddItem" >
</asp:ObjectDataSource>

Provocarea constă în asigurarea faptului că InsertMethod are semnătura corectă. Ca și în cazul


SqlDataSource, actualizările, inserările și ștergerile primesc automat o colecție de parametri de la controlul
de date legat. Acești parametri au aceleași nume ca numele câmpurilor corespunzătoare. Deci, în acest caz,
câmpurile sunt Titlu, Preț și Descriere, care se potrivesc exact cu numele parametrilor din metoda AddItem().
(Scrierea cu majuscule nu este aceeași, dar ObjectDataSource nu este sensibil la litere mari și mici, deci
aceasta nu este o problemă.)

762
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Cu toate acestea, acest lucru are încă o problemă. Când utilizatorul efectuează o editare, DetailsView trimite cei trei
parametri pe care îi așteptați (Titlu, Preț și Descriere). Cu toate acestea, metoda AddItem() are nevoie de un al
patrulea parametru—CategoryID. Am lăsat parametrul respectiv în afara câmpurilor DetailsView, deoarece nu
doriți ca utilizatorul să poată seta ID-ul categoriei. Cu toate acestea, trebuie să o furnizați metodei. Deci, de unde
puteți obține ID-ul categoriei actuale? Cea mai ușoară alegere este să o extrageți din caseta listă, la fel cum ați
făcut pentru metoda GetItems(). Tot ce trebuie să faceți este să adăugați o etichetă ControlParameter care
definește un parametru numit CategoryID și îl leagă de proprietatea SelectedValue a casetei listă. Iată eticheta
revizuită pentru ObjectDataSource:
<asp:ObjectDataSource ID="sourceItems" runat="server" SelectMethod="GetItems"
TypeName="DatabaseComponent.DBUtil" InsertMethod="AddItem" >
<SelectParameters> ...
</SelectParameters>
<InsertParameters> <asp:ControlParameter ControlID="lstCategories" name="categoryID"
PropertyName="SelectedValue" type="Int32" /> </InsertParameters>

</asp:ObjectDataSource>
Acum aveți toți parametrii de care aveți nevoie - cei trei din DetailsView și cel suplimentar din caseta listă. Când
utilizatorul încearcă să insereze o înregistrare nouă, ObjectDataSource colectează acești patru parametri, se
asigură că se potrivesc cu semnătura pentru metoda AddItem(), le pune în ordine și apoi apelează metoda.

Figura 22-11 prezintă o inserție în curs.

Figura 22-11. Inserarea cu DetailsView

763
CAPITOLUL PROGRAMAREA BAZATĂ PE COMPONENTE
22

Când faceți clic pe butonul Inserare, se întâmplă destul de mult în culise. Iată o defalcare a ceea ce se
întâmplă de fapt:
1. DetailsView adună toate valorile noi și le transmite către
ObjectDataSource.
2. ObjectDataSource apelează metoda DBUtil.AddItem(), trecând toate valorile
primite de la DetailsView în pozițiile corecte (prin potrivirea numelor câmpurilor cu
numele parametrilor) și valoarea selectată din caseta listă lstCategories.

3.
Metoda DBUtil.AddItem() construiește o comandă SQL parametrizată. Apoi
deschide o conexiune la baza de date și execută comanda pentru a insera noua
înregistrare. (În acest moment, sistemul ASP.NET de legare a datelor ia o pauză
și permite apariția altor evenimente, cum ar fi Page.Load.)
4. Chiar înainte ca pagina să fie redată, începe procesul de legare a datelor. Lista
verticală solicită primul ObjectDataSource pentru lista de categorii (care
declanșează un apel la metoda DBUtil.GetCategories(), iar GridView solicită lista
de elemente din al doilea ObjectDataSource (care declanșează metoda
DBUtil.GetItems().
Deoarece pagina este întotdeauna refăcută după terminarea tuturor operațiunilor de inserare și
actualizare, veți vedea întotdeauna cele mai recente informații în controalele web. De exemplu, dacă
adăugați un element nou, îl veți vedea apărând imediat, completat cu valoarea ID unic pe care serverul
bazei de date o generează automat.

Notă În unele cazuri, poate fi necesar să furnizați un parametru suplimentar care trebuie setat programatic. În acest caz, trebuie să definiți o etichetă de parametru simplă (în loc de o etichetă ControlParameter ), cu un nume și un tip de date, dar fără valoare. Apoi, puteți răspunde la evenimentul ObjectDataSource corespunzător (cum ar fi Inserarea, actualizarea sau ștergerea) pentru a completa valoarea de care aveți nevoie la timp. Este puțin dezordonat (și te obligă să scrii cod în pagina ta web), dar uneori este o

necesitate. Capitolul 15 demonstrează această tehnică cu controlul SqlDataSource.

Ultimul cuvânt
Exemplele din acest capitol demonstrează modalități sigure și solide de a crea componente și de a le integra
în site-ul dvs. După cum puteți vedea, aceste obiecte respectă regulile de încapsulare, ceea ce înseamnă că
fac o sarcină specifică de afaceri, dar nu se implică în generarea interfeței cu utilizatorul pentru aplicație. De
exemplu, clasa DBUtil utilizează ADO.NET cod pentru a regăsi înregistrări sau pentru a actualiza o bază de
date. Depinde de alte controale, cum ar fi GridView și DetailsView, pentru a furniza prezentarea.

764
C A P I T O L U L 23

•■■

Cache

ASP.NET aplicații sunt un pic contradictorii. Pe de o parte, deoarece sunt găzduite pe Internet, au cerințe
unice - și anume, trebuie să poată servi sute de clienți la fel de ușor și rapid ca și cum ar avea de-a face cu
un singur utilizator. Pe de altă parte, ASP.NET include câteva trucuri remarcabile care vă permit să proiectați
și să codificați o aplicație web în același mod în care programați o aplicație desktop. Aceste trucuri sunt utile,
dar pot duce dezvoltatorii la probleme. Problema este că ASP.NET face ușor să uitați că creați o aplicație
web - atât de ușor, încât ați putea introduce practici de programare care vă vor încetini sau paraliza aplicația
atunci când este utilizată de un număr mare de utilizatori din lumea reală.
Din fericire, există o cale de mijloc. Puteți utiliza funcțiile incredibile de economisire a timpului, cum ar fi starea de
vizualizare, controalele web și starea sesiunii despre care ați petrecut ultimele 20 de capitole ciudate învățând și puteți
crea în continuare o aplicație web robustă. Dar pentru a termina treaba în mod corespunzător, va trebui să investiți puțin
timp suplimentar pentru a profila și optimiza performanța site-ului dvs. web. Una dintre cele mai ușoare modalități de
îmbunătățire a performanței este utilizarea cache-ului, o tehnică care stochează temporar informații valoroase în
memoria serverului, astfel încât să poată fi reutilizate. Spre deosebire de celelalte tipuri de management de stat despre
care ați aflat în capitolul 8, cache-ul include câteva caracteristici încorporate care asigură performanțe bune.

Înțelegerea memorării în cache


ASP.NET a făcut câțiva pași dramatici înainte cu cache-ul. Mulți dezvoltatori care învață mai întâi despre cache îl văd ca
pe un pic de bibelouri, dar nimic nu ar putea fi mai departe de adevăr. Utilizat inteligent, cache-ul poate oferi o
îmbunătățire a performanței de două ori, de trei ori sau chiar de zece ori prin păstrarea datelor importante doar pentru o
perioadă scurtă de timp.
Memorarea în cache este adesea utilizată pentru a stoca informații preluate dintr-o bază de date. Acest lucru are sens -
la urma urmei, recuperarea informațiilor dintr-o bază de date necesită timp. Cu o optimizare atentă, puteți reduce timpul
și puteți diminua povara impusă bazei de date într-o anumită măsură, dar nu o puteți elimina niciodată. Dar, cu un sistem
care utilizează memorarea în cache, unele solicitări de date nu vor necesita o conexiune la baza de date și o interogare.
În schimb, vor prelua informațiile direct din memoria serverului, ceea ce este o propunere mult mai rapidă.

Desigur, stocarea informațiilor în memorie nu este întotdeauna o idee bună. Memoria serverului este o
resursă limitată; Dacă încercați să stocați prea mult, unele dintre aceste informații vor fi paginate pe disc,
ceea ce poate încetini întregul sistem. De aceea, cache-ul ASP.NET este autolimitant. Când stocați informații
într-o memorie cache, vă puteți aștepta să le găsiți acolo la o cerere viitoare, de cele mai multe ori. Cu toate
acestea, durata de viață a acestor informații este la discreția serverului. Dacă memoria cache devine plină
sau alte aplicații consumă o cantitate mare de memorie, datele vor fi evacuate selectiv din memoria cache,
asigurându-se că aplicația continuă să funcționeze bine. Această autosuficiență face cache-ul atât de
puternic (și l-ar face extrem de complicat de implementat pe cont propriu).

765
CAPITOLUL CACHE
23

Când se utilizează memorarea în cache


Secretul pentru a profita la maximum de cache este alegerea momentului potrivit pentru a-l utiliza. O strategie bună de
cache identifică cele mai frecvent utilizate bucăți de date care necesită cel mai mult timp pentru a le crea și stoca. Dacă
stocați prea multe informații, riscați să umpleți memoria cache cu date relativ neimportante și să forțați conținutul pe care
doriți cu adevărat să îl păstrați.
Iată două linii directoare de memorare în cache pentru a vă menține pe drumul cel bun:

Cache date (sau pagini web) care sunt scumpe: Cu alte cuvinte, cache informații care necesită mult timp
pentru a crea. Rezultatele unei interogări a bazei de date sau conținutul unui fișier sunt exemple bune. Nu
numai că este nevoie de timp pentru a deschide o conexiune la baza de date sau un fișier, dar poate, de
asemenea, să întârzie sau să blocheze alți utilizatori care încearcă să facă același lucru în același timp.
Memorați în cache date (sau pagini web) care sunt utilizate frecvent: Nu are rost să puneți deoparte
memoria pentru informații care nu vor mai fi necesare niciodată. De exemplu, puteți alege să nu
memorați în cache paginile cu detalii despre produse, deoarece există sute de produse diferite, fiecare
cu propria pagină. Dar are mai mult sens să memorați în cache lista categoriilor de produse, deoarece
aceste informații vor fi reutilizate pentru a servi multe solicitări diferite.
Dacă țineți cont de aceste două reguli, puteți obține două avantaje din memorarea în cache simultan:
puteți îmbunătăți atât performanța, cât și scalabilitatea.
Performanța este o măsură a cât de repede funcționează o pagină web pentru un singur utilizator. Cache-ul
îmbunătățește performanța, deoarece ocolește blocajele, cum ar fi baza de date. Ca urmare, paginile web sunt procesate
și trimise înapoi clientului mai rapid.
Scalabilitatea măsoară modul în care performanța aplicației dvs. web se degradează pe măsură ce tot mai
mulți oameni o folosesc în același timp. Memorarea în cache îmbunătățește scalabilitatea, deoarece vă
permite să reutilizați aceleași informații pentru solicitările care au loc în succesiune rapidă. Cu cache-ul, tot
mai mulți oameni pot utiliza site-ul dvs. web, dar numărul de călătorii la baza de date nu se va schimba
foarte mult. Prin urmare, sarcina globală asupra sistemului va rămâne relativ constantă, astfel cum se arată
în figura 23-1.

Figura 23-1. Efectul unei memorări în cache bune

766

S-ar putea să vă placă și