Daniel Kolman

Zobrazují se příspěvky se štítkemViewState. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemViewState. Zobrazit všechny příspěvky

Jak může GridView přijít o ViewState

| 1 comments |

Aneb o side-effectech, CompositeDataBoundControl-ech a Watch okně.

Minulý týden jsem řešil zapeklitý problém: GridView občas při postbacku ztratilo řádky, tj. nenačetlo je správně z ViewState. Ovládací prvek, ve kterém bylo GridView použito, přitom na jiných webových stránkách fungoval bez problémů.

Nejdřív jsem pátral po vytváření ViewState a jeho ukládání do stránky a načítání při postbacku. Máme totiž poměrně složitou infrastrukturu odvozenou od třídy Page, která se nechová úplně standardně a občas vyvádí psí kusy. Jenže se to ukázalo jako špatná stopa: ViewState byl normálně uložen a přítomen ve skrytém poli v postbacku. Jeho struktura vypadala dobře, zdálo se, že jej prostě GridView ignoruje.

Trochu jsem se porejpal ve zdrojových kódech .NETu (God Bless The Reflector) a přišel jsem na to, že GridView má dceřinný ovládací prvek typu Table, který při postbacku dostane svůj ViewState a stará se o správnou rekonstrukci řádek tabulky. To znamená, že tento dceřinný prvek musí být vytvořen nejpozději v OnLoad. Jenže na mé stránce měl v události OnLoad můj GridView prázdnou kolekci Controls, ale přitom jeho ChildControlsCreated bylo true. Když neexistuje dceřinný prvek Table, nemá ani co nahrát ViewState.

Zajímavé přitom bylo, že pokud jsem ve Watch okně Visual Studia změnil ChildControlsCreated na false, okamžitě se přepsalo zpět na true a do kolekce Controls se přidal prvek typu Table! Chvíli jsem přemýšlel o woodoo a černé magii, pak jsem si ale prošel seznam ostatních položek ve Watch okně a začal jsem je zkoumat. Na první pohled nic podezdřelého neobsahovalo. Když jsem z něj ale odebral položku Controls.Count, toto podivné chování ustalo a ChildControlsCreated se klidně nechalo nastavit na false.

Vrátil jsem se proto s Reflectorem do kódu a našel jsem zajímavý side effect při přistupování na kolekci Controls, zavedený třídou CompositeDataBoundControl: Před vrácením kolekce se volá EnsureChildControls()! To vysvětluje ono podivné chování ve Watch okně, kde jsem měl kromě jiného i Controls.Count. Když jsem přepsal ChildControlsCreated na false, Visual Studio se pokusilo o refresh všech položek Watch okna, takže přístupem na kolekci Controls spustilo i metodu EnsureChildControls(), která nastavila ChildControlsCreated zpět na true.

A bylo jasno! Pokud přistoupím na kolekci Controls před natažením ViewState (tedy před OnLoad), způsobím volání metody EnsureChildControls(). Ta zavolá CreateChildControls(), ale pouze při prvním spuštění. No a CreateChildControls() nevytvoří žádný ovládací prvek, pokud nemá k disposici ViewState! Takže GridView nemá dceřinný prvek Table který načítá ViewState a ani jej nevytvoří, protože už je označená jako ChildControlsCreated=true.

Z toho, děti, plynou následující poučení:
  1. Používáte-li ViewState, nepřistupujte na kolekci Controls před událostí OnLoad. Platí to pro všechny potomky CompositeDataBoundControl, tedy GridView, FormView a DetailsView.

  2. Dávejte si pozor na to, co máte ve Watch okně, neboť to může mít (blahý i neblahý) efekt na běh kódu.


PS: Poslal jsem to jako bug na Microsoft, jsem zvědavej jak se s tím hoši poperou.
PPS: Nemusej to bejt jen hoši, koukal jsem nedávno na jeden rozhovor na Channel9 s Politou Paulus a má na stole (teda na okně:-) ocenění za návrh architektury DataBoundControls, takže možná je to její práce.

Jak zjistit celkovou velikost ViewState?

| 0 comments |

Když si zapnete podrobné trasování běhu stránky pomocí elementu <trace> v souboru web.config, ASP.NET vám do stránky vypíše strom ovládacích prvků a informaci o velikosti jejich ViewState a ControlState. Jaká je ale celková velikost dat odesílanýchve skrytém HTML poli __VIEWSTATE?

Na internetu lze nalézt několik návodů, jak přidat informaci o velikosti ViewState do trace logu. Jenže všechny co jsem našel zjišťují velikost podle Request["__VIEWSTATE"], ale to je navrácený ViewState, odeslaný z předchozí stránky! Pokud nás zajímá, kolik dat odesíláme ve ViewState aktuálně zpracovávané stránky, musíme použít daleko špinavější trik.

Výchozí PageStatePersister stránky pro ViewState je HiddenFieldPageStatePersister, který v metodě Save() zkomprimuje ViewState a ControlState a vloží jej do interního textového pole ClientState stránky. Stránka jej pak později narenderuje do jednoho nebo více skrytých polí __VIEWSTATE (záleží na nastavení vlastnosti stránky MaxPageStateFieldLength). No a tak si zjistíme jak je toto pole dlouhé a přidáme to do trace logu:

protected override void Render(HtmlTextWriter writer)
{
#if DEBUG
System.Reflection.PropertyInfo secretState =
this.GetType().GetProperty(
"ClientState",
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic
);
string viewState = (string)secretState.GetValue(this, null);
if (viewState != null)
Trace.Warn("ViewState Size", viewState.Length.ToString());
else
Trace.Warn("ViewState Size", "0");
#endif
base.Render(writer);
}

No a je to. Informace o velikosti ViewState je vidět přímo v trace logu stránky! Dobrý, ne? Reflexe interního pole je ovšem špinavá technika a jsou k ní potřeba určitá práva. Pro ladění stránek a velikosti ViewState je to však príma.