poniedziałek, 7 czerwca 2010

Cykl życia strony w ASP.NET [PL]

Promuj

Dla kogoś, kto do tej pory tworzył oprogramowanie bazujące na WinForms, a przechodzi na WebForm, ważne jest zrozumienie cyklu życia strony w ASP.NET. Dlaczego? Proces, w którym strona ASP.NET jest dla przeglądarki generowana, składa się z wielu etapów, z których każdy pełni odmienną funkcję w tworzeniu i generowaniu strony. Jeśli umieścimy kod w nieodpowiednim miejscu, wówczas może braknąć kontrolek, które wg nas powinny się pojawić, lub też, jeśli już się pojawiają, to ich stan może być nieokreślony, czy też nieprzewidywalny.

Etapy i zdarzenia w życiu strony w ASP.NET

Poniższa tabela pokazuje kolejność etapów i zdarzeń dla strony ASP.NET.

Etap
Zdarzenie/funkcja
Uwagi
Żądanie strony


  • Serwer sprawdza, czy strona jest dostępna w cache.


Konstruktor
  • Request, Response nie są dostępne.
  • Kontrolki na stronie nie są jeszcze wytworzone (odwołania do nich mają wartość null).
Rozpoczęcie strony


  • Ustawiane jest Request, Response, IsPostBack, UICulture.


PreInit
  • Tu można dynamicznie tworzyć kontrolki.
  • Obiekty kontrolek są już powołane do życia, więc można podłączać się do zdarzeń, które występują w kontrolkach.
  • Ustawianie stron wzorcowych, skórek (Theme).
  • Odczyt danych profilu użytkownika.
  • W przypadku korzystania z Templated User Control należy w tym zdarzeniu utworzyć instancję kontenera nazw i połączyć go z layoutem.
Inicjalizacja
Init
  • Tworzone są puste kontrolki i ich hierarchia.
Ładowanie
Load
  • Kontrolki przywracane są z zapisanego stanu (ViewState), jeżeli jest to wywołanie zwrotne.
  • ClientScriptManager i rejestracja nowych skryptów.
  • Można wykonywać dodatkową. inicjalizację kontrolek
  • Uwaga: kontrolki, powiązane ze źródłem danych nie są jeszcze wypełnione treścią.
Walidacja


  • Wywoływane jest Validate() dla kontrolek i ustawione zostaje IsValid.
Obsługa zdarzeń wywołania zwrotnego


  • Obsługiwane są tutaj zdarzenia kontrolek, np. kliknięcia przycisków.


LoadComplete
  • Zakończony jest etap ładowania.


PreRender
  • Można nanieść ostateczne poprawki do wyglądu kontrolek.
Generacja


  • Kontrolki wpisują swoje dane do odpowiedzi.
  • Ustawianie zaznaczonego elementu.
Wyładowanie
Unload
  • Zwolnienie zasobów.
  • Można zapisywać do logów i wykonywać tracing.
  • Response/Request nie są dostępne.

Przykład sprawdzający

Informacje zaprezentowane w powyższej tabeli, można sprawdzić również samodzielnie. Przeanalizujmy następujący przykład:
Default.aspx:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="asp_net_experiments._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server"><title></title></head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Button ID="btnSubmit" runat="server" Text="Button" UseSubmitBehavior="true" OnClick="btnSubmit_Click" />
    </div>
    </form>
</body>
</html>
Default.aspx.cs:
using System;
using System.Web.UI;

namespace asp_net_experiments
{
  public partial class _Default: System.Web.UI.Page
  {
    protected override void OnPreInit( EventArgs e )
    {
      Response.Write( "Wywołano OnPreInit().<br/>" );
      base.OnPreInit( e );
    }
    protected override void OnInit( EventArgs e )
    {
      Response.Write( "Wywołano OnInit().<br/>" );
      base.OnInit( e );
    }
    protected override void OnInitComplete( EventArgs e )
    {
      Response.Write( "Inicjalizacja zakończona (OnInitComplete()).<br/>" );
      base.OnInitComplete( e );
    }
    protected override void OnPreLoad( EventArgs e )
    {
      Response.Write( "Wywołano OnPreLoad().<br/>" );
      base.OnPreLoad( e );
    }
    protected override void OnLoad( EventArgs e )
    {
      Response.Write( "Wywołano OnLoad().<br/>" );
      base.OnLoad( e );
    }
    protected void Page_Load( object sender, EventArgs e )
    {
      if ( Page.IsPostBack )
      {
        Response.Write( "To jest wywołanie typu postback." );
      }
      else
      {
        Response.Write( "To jest nowe wywołanie." );
      }
      Response.Write( "<br/>Wywołano Page_Load().<br/>" );
    }
    protected override void OnLoadComplete( EventArgs e )
    {
      Response.Write( "Ładowanie zakończone (OnLoadComplete()).<br/>" );
      base.OnLoadComplete( e );
    }
    protected override void OnUnload( EventArgs e )
    {
      // nie można w tym miejscu niczego wyprowadzić ponieważ
      // Response/Request są niedostępne podczas tego etapu.
      base.OnUnload( e );
    }
    protected override void OnPreRender( EventArgs e )
    {
      Response.Write( "Wywołano OnPreRender().<br/>" );
      base.OnPreRender( e );
    }
    protected override void OnPreRenderComplete( EventArgs e )
    {
      Response.Write( "Wywołano OnPreRenderComplete().<br/>" );
      base.OnPreRenderComplete( e );
    }
    protected void btnSubmit_Click( object sender, EventArgs e )
    {
      Response.Write( "Kliknięto przycisk Button.<br/>" );
    }
  }
}
W wyniku wykonania powyższego kodu powinniśmy otrzymać następujące informacje w przeglądarce:
Pierwsze wywołanie
Po kliknięciu przycisku
Wywołano OnPreInit().
Wywołano OnInit().
Inicjalizacja zakończona (OnInitComplete()).
Wywołano OnPreLoad().
Wywołano OnLoad().
To jest nowe wywołanie.
Wywołano Page_Load().
Ładowanie zakończone (OnLoadComplete()).
Wywołano OnPreRender().
Wywołano OnPreRenderComplete().
Wywołano OnPreInit().
Wywołano OnInit().
Inicjalizacja zakończona (OnInitComplete()).
Wywołano OnPreLoad().
Wywołano OnLoad().
To jest wywołanie typu postback.
Wywołano Page_Load().
Kliknięto przycisk Button.
Ładowanie zakończone (OnLoadComplete()).
Wywołano OnPreRender().
Wywołano OnPreRenderComplete().

Strona z kontrolką

Teraz przeanalizujmy, jak są wywoływane zdarzenia w przypadku osadzenia na stronie kontrolki:
Wywołano OnPreInit().
- GridView1.Rows.Count=0.
- Wywołano GridView1_Load().
Wywołano OnInit().
- GridView1.Rows.Count=0.
Inicjalizacja zakończona (OnInitComplete()).
- GridView1.Rows.Count=0.
Wywołano OnPreLoad().
- GridView1.Rows.Count=0.
Wywołano OnLoad().
- GridView1.Rows.Count=0.
To jest nowe wywołanie.
Wywołano Page_Load().
- GridView1.Rows.Count=0.
- Wywołano GridView1_Load().
Ładowanie zakończone (OnLoadComplete()).
- GridView1.Rows.Count=0.
Wywołano OnPreRender().
- GridView1.Rows.Count=0.
- Wywołano GridView1_DataBinding().
- Wywołano GridView1_DataBound().
Wywołano OnPreRenderComplete().
- GridView1.Rows.Count=91.
Aby otrzymać powyższy wynik, do zdarzeń kontrolki podpięcie zostało wykonane w PreInit (nie można tego zrobić w konstruktorze strony, gdyż kontrolki nie zostały jeszcze powołane do życia, a odwołania mają wartość null). Dodatkowo osadzona kontrolka miała dowiązanie do źródła danych (to, czy dowiązanie miało miejsce sprawdzane, jest przy pomocy wyświetlania ilości wierszy (GridView1.Rows.Count), które zawiera kontrolka GridView), dlatego do listy śledzonych zdarzeń kontrolki zostały dodane DataBinding i DataBound. Została również dodana obsług zdarzenia DataBinding strony.
Co należy tutaj zauważyć:
  • Nie ma informacji na temat wystąpienia zdarzenia PreInit dla osadzonej kontrolki, oznacza to że ma ono miejsce przed zdarzeniem PreInit strony (a tam dopiero została dodana obsługa zdarzeń.
  • Osadzona kontrolka jest już ładowana w zdarzeniu PreInit strony.
  • Bindowanie do danych jest wykonywane dopiero podczas etapu PreRender, więc dane w kontrolce są dopiero dostępne w zdarzeniu PreRenderComplete.
  • UWAGA: Zdarzenie DataBind dla kontrolki nie zawsze musi być wywołane! Przy wywołaniu PostBack może ono nie być wywołane powtórnie.
  • UWAGA: w przypadku wykorzystania Templated User Control metodę Page.DataBind należy jawnie wywołać, aby zapewnić, że kontener jest powiązany z layoutem.

Strona powiązana ze stroną wzorcową

Teraz przeanalizujmy, jak są wywoływane zdarzenia w przypadku wykorzystania strony wzorcowej (Master Page):
Wywołano OnPreInit().
MasterPage: Wywołano OnInit().
Wywołano OnInit().
Inicjalizacja zakończona (OnInitComplete()).
Wywołano OnPreLoad().
Wywołano OnLoad().
To jest nowe wywołanie.
Wywołano Page_Load().
MasterPage: Wywołano OnLoad().
MasterPage: To jest nowe wywołanie.
MasterPage: Wywołano Page_Load().
Ładowanie zakończone (OnLoadComplete()).
Wywołano OnPreRender().
Wywołano OnPreRenderComplete(). 
Warto tutaj zauważyć (pisałem też już o tym w post'cie: „Strony Wzorcowe (Master Pages)”), że nie ma reguły, że najpierw występuje zdarzenie dla MasterPage później dla Page (lub odwrotnie). Zobaczmy, że najpierw następuje etap Init dla MasterPage, później dla Page, natomiast w przypadku etapu Load, najpierw jest ładowanie strony, a później wzorca. Oczywiście takie zachowanie związane jest z faktem, że MasterPage jest tak na prawdę kontrolką, które na pewnym etapie dziedziczy po UserControl.

Podsumowanie

Mam nadzieję, że udało mi się wskazać, co to jest ten cykl życia strony i dlaczego jest on taki ważny. Teraz w ramach podsumowania chciałbym jeszcze teraz dać kilka uwag związanych z tematem:
  • W przypadku stron ASP.NET zastosowanie konstruktora, jako miejsca, gdzie możemy zainicjalizować pewne elementy, jest właściwie bezużyteczne, gdyż kontrolki nie są jeszcze nawet wytworzone.
  • Nie należy całego kodu inicjalizującego umieszczać w domyślnie dodawanej (przez Visual Studio) funkcji PageLoad:
  • Kontrolki wypełniane danymi (przez DataBinding) zostaną uzupełnione dopiero na późniejszym etapie.
  • Własne kontrolki powinniśmy dodawać wcześniej – w obsłudze zdarzenia PreInit, podobnie w przypadku ustawiania stron Wzorcowych (które też są kontrolkami), czy Skórek (Themes)

Literatura

Niniejsze opracowanie powstało na podstawie następujących źródeł:
  1. "MCTS Self-Paced Training Kit (Exam 70-562): Microsoft .NET Framework 3.5—ASP.NET Application Development", Autorzy: Mike Snell; Glenn Johnson; Tony Northrup; and GrandMasters, Wydawnictwo: Microsoft Press, 2009
  2. "Microsoft Visual C# 2005 Księga eksperta", Autor: Kevin HoffMan, Wydawnictwo: Helion, 2007
  3. http://msdn.microsoft.com/ 

Promuj

3 komentarze:

Posty powiązane / Related posts