André Krämers Blog

Lösungen für Ihre Probleme

vielen Missverständnissen. Eines dieser Missverständnisse ist die häufig anzutreffende Meinung, dass der ASP.NET ViewState für DropDownList Controls nicht abgeschaltet werden darf. Andernfalls würde die DropDownList den vom Anwender gewählten Wert beim Postback “verlieren”.

Ein kleines Beispiel

Sehen wir uns zur Verdeutlichung ein kleines Beispiel an. Ein Entwickler - nennen wir ihn Herrn Brause -  hat die Aufgabe eine Webseite zu schreiben, auf der ein Anwender in einer DropDownListe auswählen kann, wie viele Zeilen Quellcode er heute schreiben kann. Nach der Bestätigung der Auswahl soll der entsprechende Wert auf der Webseite angezeigt werden.

Herr Brause macht sich schnell ans Werk und erstellt folgende Seite:

Screenshot der erstellen Webseite, bestehend aus Text und DropDownList

Die DropDownListe füllt er mit folgendem Code.

  using System;
  using System.Web.UI.WebControls;
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      if (!IsPostBack)
      {
        linesOfCodeDropDown.DataSource = GetLinesOfCodeListItems();
        linesOfCodeDropDown.DataBind();
      }
    }
    static ListItemCollection GetLinesOfCodeListItems()
    {
      var items = new ListItemCollection();
      for (int i = 0; i < 1000; i++)
      {
        items.Add(new ListItem(string.Format("{0} Codezeilen", i), i.ToString()));
      }
      return items;
    }
    protected void sendButton_Click(object sender, EventArgs e)
    {
      linesOfCodeLiteral.Text = linesOfCodeDropDown.SelectedItem.Text;
    }
  }

Ein erster kleiner Test auf Herrn Brauses Rechner ergibt, dass die Webseite zufriedenstellend läuft. Stolz übergibt er sein Arbeitsergebnis an seinen Chef.

Nicht so schnell!

Kurz nach der Veröffentlichung der Seite mehren sich die Beschwerden von Anwendern, die an Standorten mit schlechten Internetverbindungen arbeiten. Sie klagen über extrem lange Ladezeiten.

Herr Brause nimmt sich dem Problem an und findet schnell heraus, dass sich mehr als ein Drittel der übertragenen Daten aus dem ViewState der Seite zusammen setzt. Von den insgesamt knapp 107 kB der Seite verteilen sich 40 kB auf den ViewState.

Bild der Seiten- und Viewstategröße.

Diese 40 kB werden übrigens nicht nur zum Client heruntergeladen, sondern bei jedem PostBack auch wieder zurück zum Server geschickt. Sie belasten die Seite also doppelt!

Was war noch mal der ViewState?

Ehe wir den ViewState gleich eliminieren werden, möchte ich zuvor schnell erklären, was genau wir überhaupt versuchen los zu werden.

Der ViewState speichert Werte von ASP.NET Controls anhand ihres Namens. Er ist vergleichbar mit einer Hashtable. Diese Name/Wert Paare serialisiert er in ein verstecktes Formularfeld mit dem Namen __VIEWSTATE. Nach einem Postback wird das __VIEWSTATE Feld deserialisiert und der Viewstate somit wieder hergestellt.

In unserem Beispiel sieht das __VIEWSTATE Feld so aus:

Bild des Seitenquelltexts mit Viewstate.

Der tatsächliche Viewstate der Seite ist um einiges größer! Die Abbildung zeigt nur einen Ausschnitt.

Der ViewState dient übrigens als Datenspeicher für die meisten Controleigenschaften. Die Eigenschaft “DataSource” der DropDownList könnte daher zum Beispiel wie folgt implementiert sein:


  public IEnumerable DataSource
  {
    get
    {
      return ViewState["DataSource"] as IEnumerable
    }
    set
    {
      ViewState["DataSource"] = value;
    }
  }

Also weg damit!

Um die übertragene Datenmenge seiner Seite zu reduzieren, schaltet Herr Brause also den ViewState der DropDownList ab. Da die DataSource der Liste nach einem PostBack nun nicht mehr automatisch gefüllt wird, entfernt er außerdem die Prüfung auf einen PostBack ehe er die DataSource der Liste füllt:

protected void Page_Load(object sender, EventArgs e)
{
  linesOfCodeDropDown.DataSource = GetLinesOfCodeListItems();
  linesOfCodeDropDown.DataBind();
}

Die anschließende Prüfung ergibt, dass der ViewState nun nur noch 0,05 kB beträgt und die komplette Seite nun über 40 kB kleiner geworden ist.

Der ViewState beträgt nun nur noch 0.05 kB

Leider ergibt der zweite Test, dass die Seite nun nicht mehr funktioniert. Egal was der Anwender auswählt, die Seite gibt immer “0 Codezeilen” aus. Dies liegt daran, dass der “SelectedIndex” der Liste innerhalb des Button Click Events immer 0 ist.

Im folgenden Screenshot wurde zum Beispiel “5 Codezeilen” ausgewählt und anschließend auf Absenden geklickt.

Obwohl der Anwender den Wert 5 auswählte, wird nur eine 0 dargestellt.

Ist der ViewState also dafür zuständig, die Auswahl des Anwenders in das Servercontrol zu schreiben?

Nein!

Auch wenn diese Vermutung zunächst nahe liegt, ist sie vollkommen falsch. Um das Rätsel zu lösen, muss man sich kurz den ASP.NET Page Life Cycle vor Augen halten.

Dieser teilt sich in folgende Phasen auf:

  1. Page Request
  2. Start
  3. Page Initialization
  4. Load
  5. Validation
  6. Postback event handling
  7. Rendering
  8. Unload

Interessant für unsere Situation sind die Phasen PageInitialization und Load.

In der Phase PageInitialization werden die folgenden Methoden durchlaufen:

OnInit

Alle Controls der Seite sind bereits erstellt worden. Deklarativ in der ASPX Seite festgelegte Werte wurden zugewiesen. ViewState und durch den Anwender gepostete Werte wurden noch nicht in die Controleigenschaften geschrieben

OnInitComplete

Wird aufgerufen, nachdem das die Methode OnInit durchgelaufen ist.

OnPreLoad

Wird aufgerufen, ehe die Phase Load begonnen wird.

In der Phase Load werden** unter anderem** die folgenden Methoden durchlaufen

LoadViewState

In dieser Methode wird der ViewState zurück in die Seite und die Controls geschrieben. In unserem ursprünglichen Beispiel würde hier also die DataSource Eigenschaft der DropDownList gefüllt werden.

ProcessPostData

Diese Methode schreibt die durch den Benutzer geposteten Werte in die Controls. In unserem Beispiel wird hier also der selektierte Wert der DropDownList gesetzt.

PageLoad

Wenn diese Methode ausgeführt wird, ist die Seite vollständig initialisiert. In unserem Ursprünglichen Beispiel haben wir hier die DataSource der DropDownList gesetzt.

Die Events der einzelnen Controls, also zum Beispiel das Event Click unseres Buttons werden übrigens in einer späteren Phase, nämlich der Phase PostBack Event Handling ausgelöst.

Interessant, aber was hat es mit unserem Problem zu tun?

Auch wenn der kleine Exkurs in Richtung Page Life Cycle sicherlich (vielleicht ;-)) sehr interessant war, stellt sich die Frage, wo der Zusammenhang zu unserem Problem der fehlenden geposteten Daten besteht.

Nun, dies ist eigentlich ganz einfach. Ursprünglich lief unsere Seite wie folgt:

Erster Request:

Setzen der DataSource Eigenschaft in der Methode Page_Load.

PostBack

Automatisches Wiederherstellen der DataSource Eigenschaft in der Methode LoadViewState, anschließend automatisches Zuweisen des durch den Anwender ausgewählten Werts in ProcessPostData.

Durch unsere Änderungen, nämlich das Deaktivieren des ViewStates und der Entfernung der Prüfung auf einen PostBack in Page_Load haben wir nun folgendes Bewirkt:

Erster Request:

Setzen der DataSource Eigenschaft in der Methode Page_Load.

PostBack

Automatisches Zuweisen des durch den Anwender ausgewählten Werts in ProcessPostData. Da die DropDownListe jedoch noch keine DataSource hat, kann dies hier noch nicht funktionieren. Die DataSource wird erst anschließend in Page_Load gesetzt.

Und das bedeutet?

Das bedeutet, dass die “fehlenden” geposteten Werte eigentlich gar nichts mit dem deaktivierten ViewState zu tun hatten. Die Ursache ist einfach die Tatsache, dass wir die DataSource setzen, nachdem die Daten des Anwenders in das Control geschrieben wurden.

Und die Lösung?

Die Lösung ist recht einfach. Der Code, der die DataSource der DropDownList setzt muss einfach ausgeführt werden, ehe ProcessPostData durchlaufen wird. Typischerweise nimmt man dazu die Methode OnInit.

  protected override void OnInit(EventArgs e)
  {
    linesOfCodeDropDown.DataSource = GetLinesOfCodeListItems();
    linesOfCodeDropDown.DataBind();
  }
  protected void Page_Load(object sender, EventArgs e)
  {

  }

Fazit

Der ViewState einer DropDownList darf sehr wohl deaktiviert werden. Um das Problem der “fehlenden” geposteten Werte in den Griff zu bekommen, muss lediglich die Zuweisung der DataSource an einen früheren Zeitpunkt innerhalb des Page Life Cycle verschoben werden.

Wer mehr zu dem Thema wissen möchte, sollte sich übrigens in jedem Fall Dave Reeds Artikel Truly Understanding ViewState durchlesen. Eine umfangreichere und vor allem bessere Behandlung des Themas habe ich bisher an keiner anderen Stelle im Web gefunden.

Wer mehr über den ASP.NET Page Life Cycle wissen möchte, dem kann ich den Artikel Der Lebenszyklus einer ASP.NET 2.0 Seite empfehlen.

Tja und wer gar nichts mehr mit ViewState oder Page Life Cycle zu tun haben möchte, der sollte zum ASP.NET MVC Framework greifen;-)

Es gibt 3 Kommentare

Comment by http://peitor.blogspot.com/
Von | 29.10.2011 21:11
Nice post! Exactly this happened to me starting with ASP.NET ;-)Good explanationCheers!
Comment by http://peitor.blogspot.com/
Von | 29.10.2011 21:11
Nice post! Exactly this happened to me starting with ASP.NET ;-)Good explanationCheers!
Comment by André Krämer
Von | 29.10.2011 21:11
Thanks! Feedback is always welcome!