Capturing Postback with the Button ControlThe Textbox control does a great job in gathering input and raising state change events to their clients, but sometimes we need controls that provide action and post data back to the server. A perfect example of this type of control in the ASP.NET framework is the System.Web.UI.WebControls.Button control. The Button control exists for one reason: to post the page back to the server and raise events.We would be remiss if we only reverse-engineered the ASP.NET TextBox control and left out the Button control, so our next task is to build our own version of the Button control. We add some bells and whistles along the way, such as the capability for the control to display itself as a Button or as a hyperlink similar to the LinkButton control in ASP.NET. This new, amazing Button server control will be named SuperButton for all its rich functionality.Rendering the ButtonThe first decision we have to make when building our button relates to how it will render. Because we decided to render either as an <INPUT type="submit"> or an <A> tag, we choose to use a strongly-typed enumeration as a means to configure its display output. We call this enumeration ButtonDisplay and give it values that reflect how our button can appear in a Web Form:public enum ButtonDisplay{ Button = 0, Hyperlink = 1}The ButtonDisplay enumeration is exposed from our control through a Display property. It defaults to a Button value if nothing is passed into the control:public virtual ButtonDisplay Display{get{object display = ViewState["Display"];if (display == null)return ButtonDisplay.Button;elsereturn (ButtonDisplay) display;}set{ViewState["Display"] = value;}}We also have a Text property that has an identical representation in the code to our previous examples. It will appear as text on the surface of the button or as the text of the hyperlink.The button-rendering code needs to have an if/then construct to switch the display based on the enumeration value set by the developer/user. It also needs a way to submit the page back to the web server when using the hyperlink display mode. The hyperlink is normally used for navigation and is not wired into the postback mechanism that buttons get for free.The Page class comes to the rescue in this instance. It has a static method named GetPostBackClientHyperlink that registers the JavaScript necessary to submit the Web Form via an HTTP POST. In the Web Form example that hosts our SuperButton control, we examine the HTML output to see how it is integrated into the postback process. Here is the code that hooks into the postback mechanism:override protected void Render(HtmlTextWriter writer) { base.Render(writer); Page.VerifyRenderingInServerForm(this); if (Display == ButtonDisplay.Button) { writer.Write("<INPUT type=\"submit\""); writer.Write(" name=\"" + this.UniqueID + "\""); writer.Write(" id=\"" + this.UniqueID + "\""); writer.Write(" value=\"" + Text + "\""); writer.Write(" />"); } else if (Display == ButtonDisplay.Hyperlink) { writer.Write("<A href=\""); writer.Write(Page.GetPostBackClientHyperlink(this, "")); writer.Write("\">" + Text + "</A>"); } }Exposing a Click Event and the Events CollectionThe first event we add to our SuperButton control is a Click event. This is your gardenvariety System.EventHandler delegate type event, but our actual event implementation will be different this time around. Instead of adding an event field to the control class, we reuse a mechanism given to all controls from the System.Web.UI.Control base class. The Events read-only property inherited from the Control class provides access to an event collection of type System.ComponentModel.EventHandlerList. EventHandlerList provides access to delegates that represent the invocation list for each event the control exposes. This means that the only memory taken up to handle event delegates is by those events that have a client event handler method registered, unlike the previous technique, which takes a hit for each event, regardless of any clients using it. This can potentially save a fair amount of memory on a control that exposes many events. Figure 5-13 graphically depicts the benefits of using the Events collection.Figure 5-13. The difference between using an event field and using the Events collectionThe first thing we need to do for an event using this new model is provide a key for the delegate that is used to store it inside the Events collection. We add this at the top of our class by creating a generic static, read-only object to represent the key for our click-related delegate: private static readonly object ClickEvent = new object(); The second step is to use the syntax C# provides for custom delegate registration with our Click event. It is an expansion of the event declaration used previously that includes add and remove code blocks. It is similar to the get and set code blocks that programmers can use to define properties in C#. The result is the following Click event:public event EventHandler Click { add { Events.AddHandler(ClickEvent, value); } remove { Events.RemoveHandler(ClickEvent, value); } }The first thing to notice is the event declaration itself. It is declared with an event keyword, delegate type, name, and accessibility modifier as before. The new functionally is added via code blocks below the declaration. The add and remove code blocks handle the delegate registration process in whatever manner they see fit to do so. In this case, these code blocks are passed the delegate reference via the value keyword to accomplish their assigned tasks.The code in our Click event uses the Events collection to add the delegate via AddHandler or to remove the delegate via RemoveHandler. ClickEvent is the access key used to identify the Click delegates in our Events collection, keeping like event handlers in separate buckets.After we declare our event with its event subscription code, we need to define our OnClick method to raise the event. The code uses the Events collection and our defined key object to get the Click delegate and raise the event to subscribers:protected virtual void OnClick(EventArgs e) { EventHandler clickEventDelegate = (EventHandler)Events[ClickEvent]; if (clickEventDelegate != null) { clickEventDelegate(this, e); } }
The first step is to pull the delegate of type EventHandler from the Events collection. Our second step as before is to check it for a null value to ensure that we actually need to invoke it. The invocation code on the delegate is the same as we used previously with our event in the Textbox demonstrations. We invoke the delegate using function call syntax with the name of the delegate. At this point, our Click event is ready to go-all we need to do is raise it when a postback occurs.