I l @ ve RuBoard |
All custom controls inherit from the System.Web.UI.Control class, which defines the events described in Table 12.1. Table 12.1. System.Web.UI.Control Events
These events describe the life cycle of all controls in ASP.NET, including all the built-in controls. As a control moves through its life cycle of creation and destruction, which happens on every page request, it will go through some or all of the following phases in the order described in Table 12.2. Table 12.2. Control Life Cycle
Notably missing from this list is the CreateChildControls() method, which is used to create the controls tree for a control. It is not listed in this life cycle diagram because it could be called during one of several phases, including the Load , DataBinding , and Render phases. Basically, this control is called whenever the ASP.NET page framework needs the controls tree to exist. You can ensure that this event is called prior to your accessing properties of child controls in your property setters and getters by calling the EnsureChildControls() method within these routines. This method checks to see if the CreateChildControls() method has already been called, and calls it only if it hasn't already been called. Understanding the life cycle of a control can be very useful when developing a complex custom control of your own. It can be very frustrating when you are trying to reference child controls in your declarative properties and you are getting null reference exceptions because the child controls don't exist yet. A good way to debug such situations is to trace through your code so you can see the order in which the events are being performed. You can easily do this using the VS.NET debugger, but that doesn't make nearly as good an example as demonstrating how to do this using the Trace feature that is built into ASP.NET. We're going to build an extension to our Hello World control that handles all the events described above and output Trace statements as each event is begun and completed. Some of these events will only be fired on PostBack , and some, like the ones that occur after Render , will not appear at all in the trace results because the page has already been rendered by the time these events are executing. In order to show the difference between setting properties declaratively within our control tag on the .aspx page, and setting them programmatically in our Page_Load event, we are also tracing whenever our property is set. Listing 12.7 shows the complete source code of our new class, HelloLifeCycle.cs. Listing 12.7 HelloLifeCycle.csusing System; using System.Collections.Specialized; using System.Web.UI; namespace ASPNETByExample { public class HelloLifeCycle : System.Web.UI.Control, IPostBackEventHandler, IPostBackDataHandler { System.Web.HttpContext cx; public HelloLifeCycle() { cx = System.Web.HttpContext.Current; cx.Trace.Write("HelloLifeCycle","Begin Constructor"); cx.Trace.Write("HelloLifeCycle","End Constructor"); } ~HelloLifeCycle() { cx.Trace.Write("HelloLifeCycle","Begin Destructor"); cx.Trace.Write("HelloLifeCycle","End Destructor"); } public string property { set { cx.Trace.Write("Property Set","Set Property: " + value); } } protected override void OnInit(EventArgs e) { cx.Trace.Write("OnInit","Begin OnInit"); base.OnInit(e); cx.Trace.Write("OnInit","End OnInit"); } protected override void OnLoad(EventArgs e) { cx.Trace.Write("OnLoad","Begin OnLoad"); base.OnLoad(e); cx.Trace.Write("OnLoad","End OnLoad"); } protected override void OnDataBinding(EventArgs e) { cx.Trace.Write("OnDataBinding","Begin OnDataBinding"); base.OnDataBinding(e); cx.Trace.Write("OnDataBinding","End OnDataBinding"); } protected override void OnPreRender(EventArgs e) { cx.Trace.Write("OnPreRender","Begin OnPreRender"); base.OnPreRender(e); cx.Trace.Write("OnPreRender","End OnPreRender"); } protected override void Render(System.Web.UI.HtmlTextWriter htwOutput) { cx.Trace.Write("Render","Begin Render"); htwOutput.Write("Hello World Life Cycle"); cx.Trace.Write("Render","End Render"); } protected override void OnUnload(EventArgs e) { cx.Trace.Write("OnUnload","Begin OnUnload"); base.OnUnload(e); cx.Trace.Write("OnUnload","End OnUnload"); } public override void Dispose() { cx.Trace.Write("Disposed","Begin Disposed"); base.Dispose(); cx.Trace.Write("Disposed","End Disposed"); } protected override void LoadViewState(object savedState) { cx.Trace.Write("LoadViewState","Begin LoadViewState"); base.LoadViewState(savedState); cx.Trace.Write("LoadViewState","End LoadViewState"); } protected override object SaveViewState() { cx.Trace.Write("SaveViewState","Begin SaveViewState"); try{ return base.SaveViewState();} finally{ cx.Trace.Write("SaveViewState","End SaveViewState"); } } protected override void TrackViewState() { cx.Trace.Write("TrackViewState","Begin TrackViewState"); base.TrackViewState(); cx.Trace.Write("TrackViewState","End TrackViewState"); } public virtual void RaisePostDataChangedEvent() { cx.Trace.Write("RaisePostDataChangedEvent","Begin RaisePostDataChangedEvent"); cx.Trace.Write("RaisePostDataChangedEvent","End RaisePostDataChangedEvent"); } public virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) { cx.Trace.Write("LoadPostData","Begin LoadPostData"); try{ return true;} finally { cx.Trace.Write("LoadPostData","End LoadPostData"); } } public void RaisePostBackEvent(String eventArgument) { cx.Trace.Write("RaisePostBackEvent","Begin RaisePostBackEvent"); cx.Trace.Write("RaisePostBackEvent","End RaisePostBackEvent"); } } } As you can see, this class has a lot of repeated code. Basically, it inherits from Control and implements the IPostBackEventHandler and IPostBackDataHandler interfaces. Next, it establishes a class variable, cx , to hold a reference to the System.Web.HttpContext.Current object, which lets us access the calling page's intrinsic properties and methods , such as Trace . The rest of the code is fairly self-explanatory. For the property, I output the value of the property in the trace output so that we can see whether it was set declaratively or programmatically (because I'm going to pass it the string "Declarative" or "Programmatic" depending on where I am setting it). For the methods that override the Control class, I make sure to call the base.MethodName() method, which is just a good habit to get into for these methods. Now, to actually see this control in action, we need an ASP.NET page to hold the control and implement tracing so we can follow the life cycle of the control. Listing 12.8 describes our ASP.NET page for testing this control. Listing 12.8 HelloEvents.aspx<%@ Page Language="C#" Trace="True" %> <%@ Register TagPrefix="Hello" Namespace="ASPNETByExample" Assembly="HelloWorld" %> <%@ Import Namespace="ASPNETByExample" %> <script runat="server"> void Page_Load(){ hello.property = "Programmatic"; } </script> <html> <body> <form runat="server"> <Hello:HelloLifeCycle runat="server" ID="hello" property="Declarative" /> <asp:Button runat="server" Text="PostBack" ID="btnPostBack" /> </form> </body> </html> As you can see, in this code I am just placing the control on the page, with a declarative property setting of Declarative and a programmatic property setting (in Page_Load ) of Programmatic . The btnPostBack button is there so that we can generate a PostBack and observe the object life cycle both with and without a PostBack . Since the PostBack is more interesting, you can see the trace output for this page after a PostBack in Figure 12.2. Figure 12.2. Trace information for HelloEvents.aspx.
Now that we have seen when these events take place, let's examine how to implement some of them, starting with using PostBacks and ViewState in your own custom controls. |
I l @ ve RuBoard |