ASP.NET features – ViewState

Solīts ir un paliek solīts – rakstām par ASP.Net atribūtiku. Šoreiz – par ViewState.
Jau iepriekš pieņemu, ka lasītājs ir iepazinies ar rakstu par ASP.Net pamatiem un par ASP.Net koda izpildes modeli

Īsumā, kas ir ViewState:

  • veids, kā ASP.Net uzglabā informāciju par WebFormas stāvokli, informāciju nesaglabājot sesijas mainīgajos
  • izstrādātājam – datu masīvs, kurā var ievietot datus un tos nolasīt neatkarīgi no WebFormas dzīves cikla. Tāpat arī – iespēja vienu reizi dinamiski uzstādīt kādas kontroles kādu atribūtu un pēc tam ļaut par atribūta vērtību parūpēties pašam ASP.Net
  • pārlūkprogrammai – burtu virknīte, hidden lauks HTML dokumentā, kas jāpārsūta atpakaļ serverim.

Vēlreiz parunāsim par ASP.Net WebFormu dzīves ciklu. Piemēram, esam atvēruši lapu http://localhost/default.aspx Šajā lapā ir viens teksta ievades lauks txtName un viena poga. Nospiežot pogu, tiek izpildīta HTTP POST operācija, datus atkal pārsūtot uz http://localhost/default.aspx, tas ir, lapa tos pārsūta “pati uz sevi”. Saņemtos datus apstrādā ASP.Net, ģenerē atjaunotu WebFormas izskatu un to nosūta uz pārlūku.

Saņemtajos formas POST datos parādās lauka txtName vērtība, kādu to ir ievadījis vai izmainījis lietotājs. Taču, lai varētu korekti apstrādāt lauka vērtības izmaiņas, nepieciešams salīdzināt šī lauka vērtību pirms to ļāva lietotājam mainīt un tad, kad saņemtas lietotāja veiktās izmaiņas, tas ir, nepieciešams zināt, kāda bija lauka vērtība, kad lapa iepriekšējo reizi tika sūtīta lietotājam. Tieši šādam mērķim arī kalpo ViewState – tajā ir atrodamas dažādu informācijas lauku “vecās versijas”. Tādējādi ASP.Net, procesējot lapu http://localhost/default.aspx, var salīdzināt, ka šobrīd aktuālā txtName vērtība ir “ArčibaldsKronins”, taču pēc ViewState datiem laukā txtName bijis ierakstīts “VitālijsMētra” un, pamatojoties uz to, apstrādāt txtName_TextChanged notikumu.

Jāatzīmē, ka katra lapas kontrole var ViewState saglabāt dažādus savus atribūtus. Piemēram, visas klases, kas mantotas no Web.UI.WebControls.WebControl klases, ViewState informācijā saglabā Enabled, AccessKey, TabIndex, ToolTip, Visible un papildus tam vēl specifiski kontrolei piemītošas īpašības, piemēram, Web.UI.WebControls.Label papildus tam saglabā arī Text atribūtu.

To, vai kāda ekrāna kontrole vispār saglabā savus datus ViewState vai nē, nosaka no System.Web.UI.Control klases mantotā atribūta EnableViewState vērtība. Ja EnableViewState=False, kontrole savu informāciju ViewState datos nesaglabā. Ja True, saglabā visu šim tipam atbilstošo informāciju (bieži vien pat par daudz). Par to, kā samazināt ViewState apjomu, raksta daudzi, ir vērts iepazīties ar Microsoft ieteikumiem aplikācijas stāvokļa saglabāšanai.

Lai saprastu tālāk, jāpaskatās, kā izskatās ViewState dati no HTML viedokļa. Tas ir slēpts datu lauks, kura vērtība apstrādāta ar Base-64 algoritmu

< input type="hidden" 
name="__VIEWSTATE" 
value="dDw5NDE3OTg0NzE7Oz6gEYYV9S+G2OiSz/XslX9Ha+7t5Q==" />

Atkodējot datus, var iegūt pa pusei lasāmu datu virkni, kas satur informāciju par visām kontrolēm, kas izmanto ViewState. Piemēram, manis demonstrētais ViewState ir atkodējams uz šādu t<941798471;;> †õ/†Ųč’Ļõģ•Gkīķå. Nav sevišķi izteiksmīgs teksts, taču dators saprot. Pēdējie baiti ir informācija pašam ASP.Net, lai varētu pārbaudīt, vai dati nav modificēti pēc nosūtīšanas klientam, pirmie – satur svarīgo informāciju par ekrāna kontroļu atribūtiem.

Kā redzams no atkodētajiem datiem, ViewState saglabātā informācija nav piekārtota ekrāna kontrolēm pēc to identifikatoriem vai nosaukumiem, bet gan pēc secības, kādā kontroles novietotas lapā. Tieši šī iemesla dēļ ir svarīgi, ka ekrāna kontroļu kolekcija nav tikusi mainīta kopš iepriekšējās lapas ielādes.
Ja atšķiras kontroļu skaits vai tips, ASP.Net ģenerēs kļūdas paziņojumu par nekorektiem ViewState datiem. Šī īpatnība nerada problēmas gadījumā, kad visas ekrāna kontroles ir sagatavotas .aspx lapā un kontroļu kolekcija ir nemainīga visā WebFormas dzīves laikā, bet problēma aktualizējas brīdī, kad kontroles tiek pievienotas lapai dinamiski.

Piemēram, ja veidosim WebFormu, kas parāda uz ekrāna kāda faila EXIF atribūtus un gribēsim ļaut tos visus vienlaicīgi mainīt uz ekrāna – katram EXIF laukam būs jāveido atsevišķa TextBox kontrole, kurā jāparāda esošā vērtība un jāļauj ievadīt citus datus. Tādējādi šo formu nevarēs sagatavot jau iepriekš un kontroļu skaits būs atšķirīgs.

Līdz ar to, lai pirmo reizi parādītu visus EXIF laukus, veidosim jaunu TextBox objektu:

    Dim oTxt As New Web.UI.WebControls.TextBox
    oTxt.ID = "txtResolution"
    oTxt.Text = oSomeObj.Resolution 'šis ir objekts, no kura "lasām" EXIF datus

un pievienosim šo objektu ekrāna kontroļu kolekcijai ar kodu Page.Controls.Add(oTxt).
Taču nākamajā lapas ielādes reizē, kad gribēsim izmantot ViewState datus, mums jāizveido attiecīgais TextBox objekts un jāļauj tam patstāvīgi nolasīt savu vērtību no ViewState. Objekta veidošanu ierakstām Page_Load metodē, kas izpildās pirms ViewState datu lasīšanas:

Private Sub Page_Load _
            (ByVal s As System.Object, ByVal e As System.EventArgs) _
             Handles MyBase.Load
    'izņemot gadījumu, ja lapa ielādējas pirmo reizi - tad jāizpilda augstāk aprakstītā procedūra
    If Not Page.IsPostBack() Then
        Dim oTxt As New Web.UI.WebControls.TextBox
        oTxt.ID = "txtResolution"
    Else
        'izsaukums uz augstāk aprakstīto procedūru, kas nolasa datus no paša faila
    End If
End Sub

Jādomā, ka problēma šeit jau ir jūtama – kā zināt, kā saucas ekrāna kontroles, kas jāveido, lai tās pašas no sevis nolasītu datus. Piemērā esmu izmantojis nosaukumu “txtResolution”, jo pieņemu, ka gandrīz katrā EXIF galvenē būs dati par attēla izšķirtspēju, taču ko darīt, ja datus ielādējam patiešām dinamiski?

Viens no risinājumiem (apkārtceļiem, protams), kādu esmu lietojis, ir – savākt visu pirmajā reizē izveidoto kontroļu ID-us vienā garā String tipa mainīgajā, atdalītā, piemēram ar “#” simboliem. Un, kad dinamiskā ģenerēšana beigusies, šo String noglabāt turpat ViewState datos:

Viewstate.Add("ControlNames", sControlNames)

Pēc tam, nākamo reizi ielādējot lapu, pārbaudām, vai ViewState satur mainīgo ControlNames. Ja satur – sadalām šo String pa atsevišķiem nosaukumiem, analizējam nosaukumus, pēc analīzes rezultātiem veidojam kontroles. Piemēram, no šāda te ControlNames satura – “txtResolution#chkMakePublic” var secināt, ka jāveido teksta ievades lauks ar nosaukumu txtResolution un ķekškaste ar nosaukumu chkMakePublic.

Kāpēc jālieto šāds apkārtceļš? Lai atkārtoti nelasītu datus no paša faila (pieņemsim, ka fiziskā faila atribūtu lasīšana ir resursietilpīga procedūra). Ja datus varam atkārtoti lasīt no paša faila, nav nepieciešams izmantot ViewState, jo var vienkārši salīdzināt lietotāja piesūtītos datus ar oriģinālajiem.

Ja pieļaujams darbam izmantot SessionState datus jeb “sesijas mainīgos”, lielu daļu no ViewState var pārcelt uz SessionState, tādējādi atslogojot klienta-servera saziņas datu kanālu, bet vairāk noslogojot serveri.

Jāatceras, ka daudzas no Microsoft piedāvātajām datu ievades kontrolēm strādās jocīgi vai nestrādās nemaz, ja ViewState tām būs izslēgts. Piemēram, ļoti ērtā DataGrid kontrole intensīvi izmanto ViewState un pie izslēgta ViewState darbojas slikti. Taču tieši šī kontrole saglabā ļoti daudz datu ViewState masīvā. No tā varam viegli secināt, ka ViewState būs labs risinājums gadījumos, kad klienta-servera komunikācija ir viegla un saziņas kanāls ir plats (piemēram, iekšējā tīkla risinājumos), bet ar to jāuzmanās tad, kad datu plūsmas apjoms ir svarīgs.

Starp citu – ViewState datos var saglabāt, šķiet, jebkuru objektu, kas atbalsta ISerializable interfeisu, piemēram, DataSet, taču ļoti jāuzmanās, vai dotais datu tips netiek serializēts nejēdzīgi lielā bināru datu virknē.