FREE BOOK

Chapter 5: Event-Based Programming

Posted by Apress Free Book | ASP.NET January 02, 2009
In this chapter, we explore the intricacies of working with server control events.

Events

As you may have guessed by now, delegates are the heart and soul of event handling in .NET. They provide the underlying infrastructure for asynchronous callbacks and UI events in web applications under ASP.NET. In addition to the delegate keyword, there is also the event keyword in C#. The event keyword lets you specify a delegate that will fire upon the occurrence of some event in your code. The delegate associated with an event can have one or more client methods in its invocation list that will be called when the object indicates that an event has occurred, as is the case with a MulticastDelegate.

We can declare an event using the event keyword followed by a delegate type and the name of the event. The following event declaration creates a Click event with public accessibility that would be right at home on a Button control:

public event EventHandler Click;

The name of the event should be a verb signifying that some action has taken place. Init, Click, Load, Unload, and TextChanged are all good examples of such verbs used in the ASP.NET framework.

The event declaration causes the C# compiler to emit code that adds a private field to the class named Click, along with add and remove methods for working with the delegates passed in from clients. The nice thing about the event declaration and the code it generates is that it happens under the covers without your having to worry about it. Later on in this chapter, we discuss how to optimize event registration with respect to storage for controls that publish a large number of events, but only a small fraction of them are likely to be subscribed to for a given control instance.

System.EventHandler Delegate

The common denominator of the event declarations with .NET controls is the delegate class System.EventHandler. All the built-in controls in ASP.NET use its signature or some derivative of it to notify their clients when events occur. We recommend that you everage this infrastructure because it reduces the amount of custom event development required. In addition, the signature of EventHandler permits server controls in the .NET Framework and their clients to interoperate:

delegate void EventHandler(Object o, EventArgs e);

The first parameter to EventHandler is an object reference to the control that raised the event. The second parameter to the delegate, EventArgs, contains data pertinent to the event. The base EventArgs class doesn't actually hold any data; it's more of an extensibility point for custom events to override. The EventArgs class does have a read-only static field named Empty that returns an instance of the class that's syntactically convenient to use when raising an event that doesn't require any special arguments or customization.

Invoking an Event in a Control

After you add an event to a control, you need to raise the event in some manner. Instead of calling the event directly, a good design pattern followed by all the prebuilt server controls in ASP.NET is to add a virtual protected method that invokes the event with a prefix of On attached to the name of the method. This provides an additional level of abstraction that allows controls that derive from a base control to easily override the event-raising mechanism to run additional business logic or suppress event invocation altogether. The following code shows an OnClick protected method used to provide access to the Click event of class:

    protected virtual void OnClick(EventArgs e)
    {
        if (Click != null)
            Click(this, e);
    }

The first thing the protected method does is check to see if any client methods have registered themselves with the Click event instance. The event field will have a null value if no clients have registered a method onto the delegate's invocation list. If clients have subscribed to the Click event with a method having a matching signature, the event field will contain an object reference to a delegate that maintains the invocation list of all registered delegates. The OnClick routine next invokes the event using the function call syntax along with the name of the event. The parameters passed in are a reference to the control raising the event and the event arguments passed into the routine.

Adding an Event to the Textbox Control

The Textbox control that we started in Chapter 4 had the beginnings of a nice clone of the ASP.NET System.Web.UI.WebControls.TextBox control. It saves its values to View- State, emits the correct HTML to create a text box in the browser, and handles postback data correctly. The control is well on its way to becoming a respectable member of the family.

We next enhance our Textbox control by adding the capability to raise an event when the Text property of the control has changed, as detected by comparing the value currently stored in ViewState with postback data.

Enhancing the Textbox Control with a TextChanged Event

The next step in our Textbox journey is to add a TextChanged event to help bring its functionality more in line with that of the built-in ASP.NET text controls. This necessitates adding an event declaration and enhancing the implementation of the IPostBackDataHandler interface in our control. The most important upgrade is the addition of the TextChanged event field and a protected OnTextChanged method to invoke it:

    protected virtual void OnTextChanged(EventArgs e)
    {
        if (TextChanged != null)
            TextChanged(this, e);
    }

public event EventHandler TextChanged;


The second upgrade is the logic enhancement to the LoadPostData and RaisePostDataChanged methods. In LoadPostData, the ViewState value of the Text property is checked against the incoming value from postback for any differences.

If there is a difference, the Text property is changed to the new value in ViewState and true is returned from the routine. This guarantees that the event is raised when RaisePostDataChangedEvent is called by ASP.NET further on in the page life cycle.

public bool LoadPostData(string postDataKey, NameValueCollection postCollection)
{
string postedValue = postCollection[postDataKey];
if (!Text.Equals(postedValue))
{
Text = postedValue;
return true;
}
else
return
false;
}

The upgrade to the RaisePostDataChangedEvent method is the addition of a single line. Instead of being blank, it calls on our newly created OnTextChanged method to invoke the TextChanged event. We use the static field Empty of the EventArgs class to create an instance of EventArgs for us, as we don't need to customize EventArgs in this case:

public void RaisePostDataChangedEvent()
{
OnTextChanged(EventArgs.Empty);
}

The code in Listing 5-2 is full text of the control after the modifications required to add the TextChanged event.

Listing 5-2. Event-Based Programming

using System;
using System.Web;
using System.Web.UI;
using System.Collections.Specialized;
using System.ComponentModel;
namespace ControlsBookLib.Ch05
{
    [ToolboxData("<{0}:Textbox runat=server></{0}:Textbox>"),
    DefaultProperty("Text")]
    public class Textbox : Control, IPostBackDataHandler
    {
        public virtual string Text
        {
            get
            {
                object text = ViewState["Text"];
                if (text == null)
                    return string.Empty;
                else
                    return (string)text;
            }
            set
            {
                ViewState["Text"] = value;
            }
        }
        public bool LoadPostData(string postDataKey,
        NameValueCollection postCollection)
        {
            string postedValue = postCollection[postDataKey];
            if (!Text.Equals(postedValue))
            {
                Text = postedValue;
                return true;
            }
            else
                return false;
        }
        public void RaisePostDataChangedEvent()
        {
            OnTextChanged(EventArgs.Empty);
        }
        protected virtual void OnTextChanged(EventArgs e)
        {
            if (TextChanged != null)
                TextChanged(this, e);
        }
        public event EventHandler TextChanged;
        override protected void Render(HtmlTextWriter writer)
        {
            base.Render(writer);
            Page.VerifyRenderingInServerForm(this);
            // write out the <INPUT type="text"> tag
            writer.Write("<INPUT type=\"text\" name=\"");
            writer.Write(this.UniqueID);
            writer.Write("\" value=\"" + this.Text + "\" />");
        }
    }
}

Total Pages : 12 12345
Image Loading...