Motivace
ViewState je technika, pomocí které ASP.NET přenáší informace o stavu webového formuláře (komponenty na formuláři a hodnoty jejích vlastností) mezi
postbacky a přibližuje programování pro web stylu programování tlustých okenních klientů. Ve výchozím nastavení si hodnotu ViewState jednotlivé požadavky mezi sebou předávají pomocí skrytého pole v HTML s názvem
__VIEWSTATE. Absence důsledného pochopení techniky ViewState dříve či později vede k neefektivitě, velkému přenosu dat a citlivosti výsledné aplikace na změny (i na první pohled přímo nesouvisející). Velikost ViewState u větších projektů, zejména se spoustou vlastních komponent, může dosáhnout i několika megabytů, což už pocítí i uživatelé rychlejších linek (o starších počítačích raději ani nemluvě).
Velikost ViewState lze pak efektivně stáhnout přepsáním projektu tak, aby splňoval pravidla pro psaní ASP.NET kódu a způsob práce s ViewState (ve smyslu hesla „Čím dřív, tím lépe." – hlavně při vytváření objektů a nastavování jejích vlastností). Zejména u rozsáhlých projektů toto přepsání znamená značné úsilí a alokaci velkého množství času, navíc se jedná o rizikovou změnu, která může zavléct mnoho chyb a vyžaduje opětovné důkladné otestování celé aplikace. Jako poměrně rychlé řešení může posloužit ukládání hodnoty ViewState do databáze a místo ní zasílat klientovi pouze příslušný identifikátor do databáze.
Článek se bude držet následujícího postupu. Nejdříve si připravíme projekt, následně vytvoříme potřebné třídy, databáze a tabulky. V dalším kroku naprogramujeme samotnou logiku pro ukládání a načítání ViewState. Na závěr zajistíme, aby se náš nový kód při ošetřování ViewState opravdu používal.
Kód prezentovaný v tomto článku je kompatibilní s Microsoft .NET Framework 2.0, Microsoft Visual Studio 2005 a Microsoft SQL Server 2005 Express.
Příprava projektu
Ve Visual Studiu 2005 si vytvoříme prázdný projekt „Web Site“ (například pomocí File | New | Web Site). V projektu vytvoříme adresáře App_Code, App_Data a App_Browsers, které budeme potřebovat později. Výsledný strom by měl přiměřeně odpovídat následujícímu obrázku.
Tvorba vlastního adaptéru
Ukládání a načítání ViewState zajišťuje v ASP.NET třída dědící System.Web.UI.Adapters.PageStatePersister. Ve zděděné třídě přetížíme její metody Save() a Load(). Instanci této třídy si ASP.NET vyžádá zavoláním metody GetStatePersister() z objektu dědícího z System.Web.UI.Adapters.PageAdapter. Vytvoříme tedy obě třídy, jak bylo nastíněno, metody Save() a Load() prozatím ponecháme prázdné. Na závěr také ponecháme zaregistrování našeho nového adaptéru ke stránce. Dosavadní výsledek zobrazuje následující kód.
using System;
using System.Web.UI;
namespace ViewStateExample
{
public class DBPageStatePersister : PageStatePersister
{
public DBPageStatePersister(Page page)
: base(page)
{
}
public override void Save()
{
}
public override void Load()
{
}
}
public class PageAdapter : System.Web.UI.Adapters.PageAdapter
{
public override PageStatePersister GetStatePersister()
{
return new DBPageStatePersister(Page);
}
}
}
Vytvoření databáze a tabulky
Naším cílem je ukládat ViewState v databázi, proto si ji nyní vytvoříme. V adresáři App_Data založíme novou databázi (Add New Item v kontextové nabídce, možnost SQL Database). V kontextové nabídce nově vytvořené databáze zvolíme Open a otevřeme tak databázi v panelu Server Explorer. Podobně vytvoříme novou tabulku (pravým tlačítkem na Tables, Add New Table) a v ní dva sloupce – ViewStateId (uniqueidentifier, odškrtnuté Allow Nulls) a ViewStateValue (text, odškrtnuté Allow Nulls). Tabulku uložíme pod názvem MyViewState. Výsledek ukazuje následující obrázek.

Nyní si vytvoříme funkce pro práci s tabulkou. Známým způsobem vytvoříme nový soubor typu DataSet v adresáři App_Code a nazveme ho MyViewState.xsd. V průvodci nastavíme dotaz „SELECT ViewStateId, ViewStateValue FROM MyViewState WHERE (ViewStateId = @ViewStateId)“ a ujistíme se, že v kroku Choose Methods to Generates máme zaškrtnuté všechny tři boxíky. Blíže vše lépe popíší následující obrázky. Na závěr vše potvrdíme a počkáme na dokončení průvodce.


Samozřejmě můžete pro přístup k databázi využít i jiné způsoby, například kombinaci SqlConnection a SqlCommand. Tento článek používá DataSet a DataAdapter z důvodu snadnějšího výkladu.
Uložení ViewState do databáze
Uložení ViewState do databáze má na starosti funkce metoda Save() naší třídy DBPageStatePersister, kterou ASP.NET volá v závěrečných fázích zpracování stránky. Naším úkolem je uložit hodnoty vlastností ViewState a ControlState. Nejdříve tedy obě hodnoty spojíme v jeden objekt a výsledek serializujeme do řetězce. Poté si vymyslíme nové id a serializovanou hodnotu pod tímto id uložíme do databáze. Na závěr nastavíme id jako hodnotu ViewState, vynulujeme ControlState a požádáme třídu dodávanou s ASP.NET, aby nové hodnoty umístila do stránky ve formě známého skrytého pole __VIEWSTATE.
Poznámka: Nastíněné řešení není optimální. Bohužel se mi ale nepodařilo najít způsob, jak se obejít bez dodávané třídy – například HiddenFieldPageStatePersister. Po ručním vložení skrytého pole do stránky, přidal následně ASP.NET do stránky další instanci tohoto pole, které tím pádem přepsalo ručně vloženou hodnotu. Navíc nedocházelo u AJAXových skriptů ke správné aktualizaci ViewState. Podle mého pozorování se zdá, že ASP.NET – na rozdíl od implementace ASP.NET v projektu Mono – neexportuje všechny vlastnosti a funkce, které jsou potřebné k vytvoření plnohodnotné třídy pro ukládání ViewState. Samozřejmě, pokud přijdete na řešení, budu rád, pokud mi o něm dáte vědět.
public override void Save()
{
if ((ViewState == null) && (ControlState == null))
return;
// Serializujeme originální hodnotu ViewState
Pair states = new Pair(ViewState, ControlState);
String viewState = StateFormatter.Serialize(states);
// Vygenerujeme si nové id pro ViewState
Guid newGuid = Guid.NewGuid();
// Uložíme ViewState do databáze pod vygenerovaným id
MyViewStateTableAdapters.MyViewStateTableAdapter adapter = new MyViewStateTableAdapters.MyViewStateTableAdapter();
adapter.Insert(newGuid, viewState);
// Zajistíme uložení našeho id do skrytého pole ve stránce místo originální hodnoty ViewState.
HiddenFieldPageStatePersister hidden = new HiddenFieldPageStatePersister(Page);
hidden.ViewState = newGuid.ToString();
hidden.ControlState = null;
hidden.Save();
}
Načtení ViewState z databáze
Načtení ViewState se v metodě Load() logicky provádí v opačném pořadí, než jeho ukládání. Nejdříve tedy požádáme dodanou třídu o vydolování id ze stránky a načteme si serializovaný objekt s ViewState z databáze. Pokud takový nenalezneme, můžeme skončit. V opačném případě deserializujeme hodnotu načtenou z databáze a rozdistribuujeme ji do vlastností ViewState a ControlState, ze kterých původně vznikla.
public override void Load()
{
// Načteme id ViewState ze stránky
HiddenFieldPageStatePersister hidden = new HiddenFieldPageStatePersister(Page);
hidden.Load();
Guid guid = new Guid(hidden.ViewState.ToString());
// Načteme odpovídající originální hodnotu ViewState z databáze
MyViewStateTableAdapters.MyViewStateTableAdapter adapter = new MyViewStateTableAdapters.MyViewStateTableAdapter();
MyViewState.MyViewStateDataTable viewStateObject = new MyViewState.MyViewStateDataTable();
adapter.Fill(viewStateObject, guid);
if (viewStateObject.Rows.Count <= 0)
return;
String viewState = viewStateObject[0]["ViewStateValue"].ToString();
// Obnovíme hodnotu ViewState v místech, kde ji ASP.NET očekává
if (String.IsNullOrEmpty(viewState))
return;
Pair state = (Pair)StateFormatter.Deserialize(viewState);
ViewState = state.First;
ControlState = state.Second;
}
Registrace adaptéru
Zbývá nám ještě sdělit ASP.NET, aby používal náš nový adaptér s přetíženou metodou GetStatePersister(). Vytvoříme proto soubor MyViewState.browser v adresáři App_Browsers, který jsme si připravili na začátku. V souboru uvedeme, že adaptérem k objektu System.Web.UI.Page (třída reprezentující stránku v ASP.NET) je právě naše nová třída ViewStateExample.PageAdapter. Kompletní text souboru zobrazuje následující kód.
<browsers>
<browser refID="Default" >
<controlAdapters>
<adapter
controlType="System.Web.UI.Page"
adapterType="ViewStateExample.PageAdapter" />
</controlAdapters>
</browser>
</browsers>
Shrnutí
V článku jsme si ukázali, jak naprogramovat vlastní způsob ukládání ViewState a nastavit jeho používání. Stačí nám tedy zdědit metodu System.Web.UI.Adapters.PageAdapter.GetStatePersister(), která vrátí instanci třídy dědící z System.Web.UI.Adapters.PageStatePersister. V této třídě implementujeme metody Save() a Load(), které provedou samotné ukládání a načítání ViewState.
Příloha