Enunţ aici resursele asociate acestui articol:
În acest articol, care reprezintă numărul al treilea al tutorialului nostru de ASP.NET (partea I aici: http://hpc-consulting.ro/csb/blogs/mihai/archive/2008/09/17/tutorial-asp-net-episodul-1.aspx, partea a doua aici: http://hpc-consulting.ro/csb/blogs/mihai/archive/2008/10/02/tutorial-asp-net-partea-a-ii-a.aspx ), vom continua discuția despre interfața cu utilizatorul din aplicațiile web ASP.NET, concentrându-ne mai mult pe aspecte legate de controalele ASP.NET și pe păstrarea stării obiectelor în aplicațiile web.
Unelte / tehnologii: Visual Web Developer 2008 Express, ASP.NET 3.5.
Controale dezvoltate de programator
Trebuie să facem distincția între două tipuri de controale care pot fi dezvoltate de către programatori: ”user controls” respectiv ”custom controls”.
User controls
Acestea reprezintă maniera de a combina o serie de controale ASP.NET existente într-un singur control. De multe ori, paginile unei aplicații ASP.NET conțin controale similare, cum ar fi de exemplu un formular care cere informațiile legate de adresa unui utilizator – aici am avea câte o pereche de Label și TextBox pentru nume, prenume, adresă, oraș, cod poștal, etc. Putem crea un ”user control” care să conțină toate aceste controale grupate la un loc, și care poate fi apoi folosit unitar de oricâte ori este nevoie.
Crearea de User controls
Aceste tipuri de controale se realizează într-o manieră asemănătoare paginilor ASP.NET, doar că extensia rezervată fișierelor care le conțin este .ascx, iar în codul ASP.NET observăm directiva Control în loc de directiva Page specifică paginilor. Pentru crearea unui User control: Website – Add New Item – Web User Control. Toate controalele și textul care sunt create într-un astfel de control vor fi apoi renderizate pe paginile în care acesta este inclus.
Crearea unui User control dintr-o pagină ASP.NET
O pagină gata creată poate fi convertită la User control. Această practică poate avea avantajul că mai întâi testăm controlul având direct o pagină, iar după ce îl finisăm putem să realizăm conversia astfel:
· Scoatem tag-urile: html, body, form.
· Modificăm directiva Page în Control.
· Modificăm extensia fișierului din .aspx în .ascx.
· În directiva Control, modificăm valoarea atributului Inherits din ”System.Web.UI.Page” în ”System.Web.UI.UserControl”.
Adăugarea unui User control pe o pagină
Din Solution Explorer, realizăm operațiunea de drag-and-drop a unui User control existent pe o pagină ASP.NET. Codul ASP.NET va conține o directivă de tipul acesta:
<%@ Register Src="MyControl.ascx" TagName="MyControl" TagPrefix="uc1" %>
Respectiv acolo unde a fost așezat controlul în pagină, vom găsi un cod asemănător cu acesta:
<uc1:MyControl ID="MyControl1" runat="server" />
Directiva Register este necesară pentru a avea User controls în paginile ASP.NET. Atributul TagPrefix este cel care specifică namespace-ul din care face parte controlul dorit – prin urmare, controalele noastre pot fi așezate în spații de nume pentru a le putea grupa funcțional, respectiv pentru a asigura unicitatea numelor acestora. Atributul TagName reprezintă numele controlului nostru, iar atributul Src conține calea către fișierul .ascx unde a fost definit. Instanța propriuzisă a controlului se află undeva în form, acolo unde a fost așezat la drag-and-drop.
Custom controls
Acestea sunt controale dezvoltate de programator, prin specializarea unor controale existente în ASP.NET. Prin specializare înțelegem moștenirea dintr-o clasă ASP.NET care reprezintă un control sau direct din clasa WebControl, și specializarea comportamentului acestuia. Sigur, se poate moșteni direct din clasa Control dar aceasta se află mai sus în ierarhie, ceea ce înseamnă că nu oferă la fel de multe facilități implicite precum WebControl.
În ambele situații (moștenire din WebControl sau specializarea unui control existent), trebuie să avem în vedere reutilizarea controlului dezvoltat de noi, de aceea este recomandat să fie creat într-o bibliotecă dinamică (.dll), adică un Class Library din Visual Studio.
Moștenirea dintr-un control existent
O situație destul de întâlnită atunci când dorim specializarea unui control existent, este să suprascriem metoda Render a controlului pentru a modifica modul în care acesta este renderizat de către ASP.NET.
Moștenirea din clasa WebControl
Alegem aceată opțiune când niciun control ASP.NET existent nu ne oferă funcționalitate similară cu ceea ce dorim să obținem. În acest caz este obligatoriu să suprascriem metoda Render pentru a specifica modul în care va fi renderizat noul control.
Controale dimanice
Până acum am discutat despre controale ASP.NET respectiv dezvoltate de programator care sunt așezate în pagină în timpul dezvoltării site-ului, numite controale statice. Există posibilitatea de a crea controale dinamic, la rularea aplicației, situație care apare atunci când în funcție de o anumită decizie sau de alta site-ul nostru trebuie să se comporte diferit.
Crearea controalelor dinamice
Controalele ASP.NET
Pentru a crea dinamic un control ASP.NET, tot ce trebuie făcut este să scriem acest cod în metoda unde dorim să fie creat:
TextBox txt = new TextBox();
User controls
Presupunând că există un User control numit MyUserControl care se află în fişierul .ascx cu acelaşi nume, atunci crearea dinamică a sa se realizează astfel:
Control c = this.LoadControl("~/MyUserControl.ascx");
De ce nu putem să creem dinamic User controls în aceeași manieră în care o facem cu controalele server obișnuite? Ei bine, atunci când avem un User control într-o pagină, conținutul său trebuie renderizat. Asta înseamnă că dacă noi am crea pur și simplu un obiect în maniera aceasta MyUserControl ctrl = new MyUserControl(); pur și simplu el nu poate fi renderizat respectiv proprietățile sale – cum ar fi ID-ul - nu pot fi setate.
Metoda LoadControl are nevoie de calea către fișierul care conține User control-ul, iar acea cale trebuie să fie virtuală și relativă. Câteva exemple:
· Control c = this.LoadControl("~/MyUserControl.ascx");. Calea este relativă la alicație.
· Control c = this.LoadControl("MyUserControl.ascx");. Calea este relativă la pagina curentă.
· Control c = this.LoadControl("./UserControls/MyUserControl.ascx");. Calea este relativă la pagina curentă.
Păstrarea stării în aplicaţiile ASP.NET
Aplicaţiile web diferă fundamental faţă de cele desktop în ceea ce priveşte păstrarea stării obiectelor create. Dacă într-o aplicaţie desktop un obiect odată creat rămâne în memorie şi poate fi accesat respectiv putem să îi vedem starea până când el este distrus explicit (sau de către Garbage Collector după o perioadă lungă de nefolosire); într-o aplicaţie web implicit nu se reţine starea obiectelor create. Motivul e simplu: fiecare cerere (Request) către un server web presupune “distrugerea” paginii curente şi crearea uneia noi, iar obiectele care existau înainte de Request sunt distruse.
Există mai multe posibilităţi de a păstra starea unor obiecte, unele oferite implicit de ASP.NET altele implicând muncă din partea programatorului.
Starea controalelor ASP.NET
Controalele ASP.NET îşi păstrează starea între două postback-uri, lucru care poate fi uşor observat dacă creem un formular cu câteva Labels şi TextBoxes, apoi scriem ceva în ele și facem un postback. Responsabile pentru acest lucru sunt două etape din suita de evenimente care au loc la încărcarea unei pagini ASP.NET cerută de pe server, anume in imaginea alăturată ”LoadViewState” și ”LoadPostbackData”.
După cum spuneam și în partea a II-a a acestui tutorial, nu ViewState-ul este responsabil pentru încărcarea stării controalelor, decât parţial. Toate controalele care implementează interfaţa IPostBackDataHandler vor beneficia de încărcarea stării lor de dinainte de postback indiferent dacă ViewState-ul este activat sau nu. Prin urmare, este foarte important să analizăm dacă ViewState-ul trebuie lăsat activ (aşa este implicit) – ceea ce înseamnă o încărcare suplimentară a paginii cu date care poate nu trebuie reţinute. Un exemplu edificator în acest sens apare în screencast-ul ataşat (partea a doua).
Păstrarea stării controalelor dinamice
Am văzut cum se crează controalele dinamice, dar trebuie acum să vedem și cum se comportă ele la mai multe postback-uri ale aceleiași pagini.
Evident că în paginile ASP.NET avem de-a face cu postback-uri. Dacă avem controale dinamice, este important ca ele să fie create şi adăugate la form, sau într-un alt control container de fiecare dată când pagina se reîncarcă. Să spunem că dorim să avem un Label dinamic; următorul cod nu este corect:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
Label lbl = new Label();
lbl.Text = "text initial";
lbl.ID = "lbl";
form1.Controls.Add(lbl);
}
}
protected void btnChangeText_Click(object sender, EventArgs e)
{
Label lbl = (Label)form1.FindControl("lbl");
lbl.Text = "text modificat";
}
Asta deoarece controlul se crează doar la prima încărcare a paginii, iar la postback-uri nu va mai fi găsit. De exemplu, dacă avem un eveniment unde dorim modificarea unei proprietăți a acestui Label, aplicația va arunca o excepție System.NullReferenceException. Controlul trebuie adăugat la fiecare postback:
protected void Page_Load(object sender, EventArgs e)
{
Label lbl = new Label();
lbl.Text = "text initial";
lbl.ID = "lbl";
form1.Controls.Add(lbl);
}
O situație mult mai uzuală în care lucrăm dinamic cu controalele ASP.NET este cea în care folosim un control avansat, eventual care este legat la date (data bound). Un exemplu ar fi un GridView care să primească dinamic, la execuție, coloane noi în funcție de anumite condiții legate de starea aplicației. Varianta de mai jos presupune adăugarea dinamică a unui BoundField la un GridView existent declarat static in pagină, dar prezintă o problemă:
protected void Page_Load(object sender, EventArgs e)
{
BoundField field = new BoundField();
field.HeaderText = "Coloana dinamica";
field.DataField = "Data";
grid.Columns.Add(field);
List<GridData> list = new List<GridData>();
list.Add(new GridData("item 1"));
list.Add(new GridData("item 2"));
list.Add(new GridData("item 3"));
grid.DataSource = list;
grid.DataBind();
}
Problema este că la fiecare postback noi vom adăuga la GridView o coloană dinamică şi probabil că nu dorim acest lucru. Codul care adaugă respectiva coloană trebuie pus ori într-o secţiune de cod if (!IsPostBack), ori pus în OnInit.
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
BoundField field = new BoundField();
field.HeaderText = "Coloana dinamica";
field.DataField = "Data";
grid.Columns.Add(field);
}
Starea obiectelor
ASP.NET se ocupă de păstrarea stării controalelor, dar de multe ori este necesar să reţinem starea unor altfel de obiecte. De exemplu, să ne imaginăm că pe prima pagină a unei aplicaţii ASP.NET se efectuează autentificarea utilizatorului, iar în toate paginile ulterioare acesta trebuie reţinut. În acest sens, avem la dispoziţie câteva opţiuni.
Query String
Sau parametri de get, anume care apar în hyperlink-ul de navigare către o pagină. De exemplu, dacă dorim ca din Pagina1.aspx să transmitem un parametru în Pagina2.aspx, atunci putem avea o ancoră care în atributul href să aibă valoarea Pagina2.aspx?parametru=valoare_parametru. Prin Query String putem trimite parametri de tip șir de caractere, ceea ce poate nu este suficient întotdeauna, dar pentru a-i transmite dintr-o pagină în cealaltă nu avem nevoie de postback pe pagina curentă, ci pur și simplu de o navigare html.
Session
Acesta este un container specific ASP.NET în care se pot salva obiecte de orice tip relativ la sesiunea curentă. O sesiune este dată de o instanță de browser în care este deschisă aplicația ASP.NET – deci de pe același calculator dacă deschidem în 2 instanțe diferite de browser aceeași aplicație, atunci avem două sesiuni diferite.
Un obiect odată introdus în Session, rămâne acolo și poate fi accesat de oriunde din aplicație pe toată durata sesiunii. Sesiunea se poate termina astfel:
- La un timp de la închiderea browser-ului. Tipic, sesiunea mai este reținută timp de 20 de minute, dar această valoare este configurabilă.
- La resetarea procesului de IIS în care rulează aplicaţia pe server. Procesul de IIS se poate restarta dacă un anumit prag de memorie alocată a fost depăşit sau un anumit timp de rulare continuă a fost depăşit. Acestea sunt setări care ţin de administrarea serverului de IIS, dar ce trebuie să reţinem este că acest proces se poate restarta în timp ce aplicaţia rulează (este cel puţin o sesiune activă) şi atunci acea sesiune se pierde.
- Pentru o mai mare robusteţe, sesiunea poate fi stocată într-un server Microsoft SQL Server. Atunci avem avantajul robusteţii faţă de stocarea ei în IIS, dar avem dezavantajul performanţei pentru că la fiecare operaţie de scriere / citire cu sesiunea are loc şi o serializare / deserializare.
Făcând abstracţie de setările de pe server şi modurile diferite de stocare a sesiunii, putem presupune că aceasta este disponibilă până când utilizatorul închide browser-ul.
Obiectele care se introduc în Session sunt specifice sesiunii (utilizatorului) curente, adică ele nu sunt “vizibile” de către alţi utilizatori.
Application
Dacă dorim să păstrăm starea unor obiecte dar acestea să fie partajate între toţi utilizatorii aplicaţiei, avem la dispoziţie obiectul Application. Maniera de lucru cu acesta este identică cu cea a Session-ului, doar că zona de memorie este partajată între utilizatori.
Cookie
Cookie-urile sunt o altă modalitate de a păstra starea unor obiecte. Acestea nu sunt altceva decât fișiere care se stochează pe calculatorul utilizatorului aplicației, deci avem avantajul că nu utilizăm memorie de pe server. Totuși, există dezavantajul că depindem de setările browser-ului utilizatorului care poate sau nu să permită salvarea de cookie-uri, respectiv acesta oricând poate să și le șteargă.