Click here to Skip to main content
Click here to Skip to main content

Approaches for User Control Event Handling

, 9 Mar 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
In this article, we will discuss the different ways of handling events in user controls.

Introduction

In this article, we will be discussing the different ways of handling events in user controls. For more details on user controls, visit http://msdn.microsoft.com/en-us/library/y6wb1a0e.aspx.

You can think of a user control as a self contained composite control, like a control used to show logged in user details and which does not interact with the hosting pages. But we can have user controls which contain buttons, links, and are supposed to interact with the hosting page (Container).

Events raised by ASP.NET user controls work differently from those in Windows application. In ASP.NET or web based applications, events are raised at the client side and are handled at the server side. You can understand this by looking at the following user control event model diagram:

To handle this scenario normally, one of the following approaches are used.

Approach 1: Direct Subscription

Using this approach, the user control directly subscribes the container page event in itself. This approach is considered as a bad practice.

Let's see this by example.

Conceder a control having four buttons: Add, Delete, Update, and Reset, and we want these controls to be used in many screens in an ASP.NET project. To avoid duplicates and common appearance in all pages, let's create a common control as follows:

action.png

ASCX Code

<div style="width: 100%;">
    <asp:Button ID="btnAdd" runat="server" Text="Add" 
       OnClick="btnAdd_Click"></asp:button>
    <asp:button id="btnEdit" runat="server" 
       text="Edit" onclick="btnEdit_Click"> </asp:button>
    <asp:button id="btnDelete" runat="server" 
       text="Delete" onclick="btnDelete_Click">    </asp:Button>
    <asp:button id="btnReset" runat="server" 
       text="Reset" onclick="btnReset_Click"></asp:button>
</div>

The code-behind for the control is as follows:

public partial class Direct : System.Web.UI.UserControl
{
    protected void btnAdd_Click(object sender, EventArgs e)
    {
    }
    protected void btnEdit_Click(object sender, EventArgs e)
    {
    }
    protected void btnDelete_Click(object sender, EventArgs e)
    {
    }
    protected void btnReset_Click(object sender, EventArgs e)
    {
    }
}

Now add a page to the project which will be using this control.

ASPX Code (Designer)

Add the user control which we created earlier to the page. Your designer code will look like this:

<%@ Register src="Controls/Direct.ascx" 
       tagname="Direct" tagprefix="uc1" %>

<!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>
    
        <uc1:Direct ID="Direct1" runat="server" />
    
    </div>
    </form>
</body>
</html>

Now add four methods Add, Delete, Update, and Reset in your code-behind page. Your code-behind page will look like this:

public partial class Direct : System.Web.UI.Page
{
    public void Add()
    {
        Response.Write("Added.");
    }
    public void Delete()
    {
        Response.Write("Deleted.");
    }
    public void Update()
    {
        Response.Write("Updated.");
    }
    public void Reset()
    {
        Response.Write("Reset done.");
    }
}

To call the above methods on click of the Action control button, you need to write the logic directly in the control button event. Once you do this, your user control code-behind will look something like the following:

protected void btnAdd_Click(object sender, EventArgs e)
{
    //In case of multiple pages you need to check each and every page.
    Action.Direct direct = this.Page as Action.Direct;
    if (direct != null)
    {
        direct.Add();
    }

}

protected void btnEdit_Click(object sender, EventArgs e)
{
    //In case of multiple pages you need to check each and every page.
    Action.Direct direct = this.Page as Action.Direct;
    if (direct != null)
    {
        direct.Update();
    }
}

protected void btnDelete_Click(object sender, EventArgs e)
{
    //In case of multiple pages you need to check each and every page.
    Action.Direct direct = this.Page as Action.Direct;
    if (direct != null)
    {
        direct.Delete();
    }
}

protected void btnReset_Click(object sender, EventArgs e)
{
    //In case of multiple pages you need to check each and every page.
    Action.Direct direct = this.Page as Action.Direct;
    if (direct != null)
    {
        direct.Reset();
    }
}

Disadvantages

In this approach, the control is not generic; you need to check each and every page to execute anything. If a new page gets added in the project and wants to use the common control, the control might need modification for handling the new page.

Approach 2: Event Delegation

In this case, all the pages which want to use the control and like to perform any operation does that on the basis of the events raised by the user control. For this, the user control needs to publish events and all the consuming pages need to handle the events.

Normally, we go for this approach if we want complete encapsulation and don't want to make our methods public. This is the most recommended way to achieve the problem we are currently addressing.

Let's conceder the same example as mentioned for approach 1, and we will try to implement this using Event Delegation.

ASCX Code

<div style="width: 100%;">
  <asp:Button ID="btnAdd" runat="server" 
    Text="Add" OnClick="btnAdd_Click"></asp:button>
  <asp:button id="btnEdit" runat="server" 
    text="Edit" onclick="btnEdit_Click"> </asp:button>
  <asp:button id="btnDelete" runat="server" 
    text="Delete" onclick="btnDelete_Click">    </asp:Button>
  <asp:button id="btnReset" runat="server" 
    text="Reset" onclick="btnReset_Click"></asp:button>
</div>

The code-behind for the control is as follows:

public delegate void ActionClick();

public partial class EventDelegation : System.Web.UI.UserControl
{
    public event ActionClick OnAddClick;
    public event ActionClick OnDeleteClick;
    public event ActionClick OnEditClick;
    public event ActionClick OnResetClick;
    protected void btnAdd_Click(object sender, EventArgs e)
    {
        if(OnAddClick!= null)
        {
            OnAddClick();
        }
    }

    protected void btnEdit_Click(object sender, EventArgs e)
    {
        if (OnEditClick != null)
        {
            OnEditClick();
        }
    }

    protected void btnDelete_Click(object sender, EventArgs e)
    {
        if(OnDeleteClick!= null)
        {
            OnDeleteClick();
        }
    }

    protected void btnReset_Click(object sender, EventArgs e)
    {
        if(OnResetClick!= null)
        {
            OnResetClick();
        }
    }
}

The above code looks very different from the Approach 1 code.

Let me try to explain the main point. The user control specifies some public events like OnAddClick, OnEditClick,etc., which declare a delegate. Anyone who wants to use these events needs to add the EventHandler to execute when the corresponding button click event occurs.

Let's take the following code:

protected void btnAdd_Click(object sender, EventArgs e)
{
    if(OnAddClick!= null)
    {
        OnAddClick();
    }
}

In the above code snippet, we first check if anyone is attached to this event or not, and if found, then raise the event.

Now add a page to the project which will use this control

ASPX Code (Designer)

Add the user control which we created earlier to the page. Your designer code will look like this:

<%@ Register src="Controls/EventDelegation.ascx" 
   tagname="EventDelegation" tagprefix="uc1" %>

<!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>
        <uc1:Direct ID="Direct1" runat="server" />
    </div>
    </form>
</body>
</html>

The code-behind of the page will look like this:

public partial class EventDelegation : System.Web.UI.Page
{
    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        ActionControl.OnAddClick += ActionControl_OnAddClick;
        ActionControl.OnDeleteClick += ActionControl_OnDeleteClick;
        ActionControl.OnEditClick += ActionControl_OnEditClick;
        ActionControl.OnResetClick += ActionControl_OnResetClick;
    }

    private void ActionControl_OnResetClick()
    {
        Response.Write("Reset done.");
    }

    private void ActionControl_OnEditClick()
    {
        Response.Write("Updated.");
    }

    private void ActionControl_OnDeleteClick()
    {
        Response.Write("Deleted.");
    }

    private void ActionControl_OnAddClick()
    {
        Response.Write("Added.");
    }
}

In the above code, you can find that the container page needs to add event handlers during the OnInit event.

I haven't found any disadvantages in this approach but there are a few things which make this a little problematic.

  1. You need to add an event handler for each and every event. If you do not add the event handlers in the OnInit event of the page, you might face some problems that on page post back, you will lose the event assignment (as ASP.NET is stateless, which is not the case with Windows controls). In this approach, you need to respect the page life cycle events.
  2. Some times when you are working on the Designer, there might be a case when the event handler gets lost without your notice.
  3. Even if you have not added the event handler, you will not get any errors or warnings.
  4. If you have multiple pages for performing the same action, there is no guarantee that all the method names will be same; the developer can choose their own method names, which reduces the maintainability of the code.

Approach 3: Indirect Subscription

Here we are discussing an approach which is an extension of direct subscription and tries to resolve the problems in our implementation. The main problem with the Direct Subscription approach is the maintenance; every time we add a page to the project which wants to use the common control, we need to modify the common control to handle the page event. This is because we are directly referring to the container in the control; whenever the container changes, that needs to be handled in a different way.

Let's discuss the Indirect Subscription approach using the code. Here we are considering the same example which we discussed in Approach 1 and Approach 2.

IAction Interface

public interface IAction
{
    void Add();
    void Update();
    void Delete();
    void Reset()
}

In the above code, we are creating an interface IAction where we are defining the rules which our user control is going to support. In other words, we are defining the contract here rather than the actual implementation. This will help us in de-coupling the user control and the container (Page).

ASCX Code

<div style="width: 100%;">
  <asp:Button ID="btnAdd" runat="server" 
    Text="Add" OnClick="btnAdd_Click"></asp:button>
  <asp:button id="btnEdit" runat="server" 
    text="Edit" onclick="btnEdit_Click"> </asp:button>
  <asp:button id="btnDelete" runat="server" 
    text="Delete" onclick="btnDelete_Click"> </asp:Button>
  <asp:button id="btnReset" runat="server" 
    text="Reset" onclick="btnReset_Click"></asp:button>
</div>

The code-behind for the control is as follows:

public partial class ActionBar : System.Web.UI.UserControl
{
    protected void btnAdd_Click(object sender, EventArgs e)
    {
        Action.Add();
    }

    protected void btnEdit_Click(object sender, EventArgs e)
    {
        Action.Update();
    }

    protected void btnDelete_Click(object sender, EventArgs e)
    {
        Action.Delete();
    }

    protected void btnReset_Click(object sender, EventArgs e)
    {
        Action.Reset();
    }

    private IAction Action
    {
        get
        {
            IAction action = Page as IAction;
            // Check if Page implements IAction interface or not.
            if (action == null)
            {
                throw new Exception(
                    "No A valid Container type. Page should " + 
                    "implement the IAction interface."
                    );
            }
            return action;
        }
    }
}

In the above code, the property Action returns an IAction type object; if the container does not implement the IAction interface, an exception will be thrown.

The methods get raised without checking if someone is subscribing to the event handler or not.

For example:

Action.Add();

Now add a page which will use the user control and add the user control to it. The page designer code will look like the following:

<%@ Register src="Controls/ActionBar.ascx" tagname="ActionBar" tagprefix="uc1" %>

<!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>
        <uc1:ActionBar ID="ActionBar1" runat="server" />
    </div>
    </form>
</body>
</html>

The server-side code will look like:

public partial class _Default : System.Web.UI.Page, IAction
{
    #region IAction Members

    public void Add()
    {
       Response.Write("Added.");
    }

    public void Update()
    {
        Response.Write("Updated.");
    }

    public void Delete()
    {
        Response.Write("Deleted.");
    }

    public void Reset()
    {
        Response.Write("Reset done.");
    }

    #endregion
}

In the above code, you can see that we are implementing the IAction interface, which forces us to implement the Add, Update, Delete, and Reset methods in it. You can implement all the methods and write the page specific code in those blocks. Notice that here we are not bothered about registering the event handler. This is the responsibility of the user control itself to invoke the methods on the appropriate event (button click).

Advantages

  1. You don't need to register the event handlers in the page.
  2. All pages which are going to use this control should have the same method name; this will help in code uniformity and will improve maintainability.
  3. For any new subscription, you don't need to modify the user control.

But this approach has a major flaw in an object oriented programming view point, and that is it doesn't allow encapsulation. As we are implementing the IAction interface, all the methods are by default public. If you are designing a control library so that some third party can use it, then go for the second approach.

Unsubscribing Events

Unsubscribing of events plays an important role when we working with events. In some scenarios, if we no longer want our events, we need to unsubscribe the attached events.

You need to handle this depending on the type of application you are working on. In the case of an ASP.NET application, we don't need to unsubscribe the events as the page instance and all of its associated components will go out of scope when a particular request completes. Once the request is served, they become eligible for GC. So in our case, the event attached in the page will go out of scope along with the page/user controls on it. In normal cases, we don't need to unsubscribe an event for the web pages, but you need to unsubscribe your event that belongs to some singleton object which remains active for every request. If you don't unsubscribe the event, the reference of the event will remain with the page and even after the request is complete, it will not be picked by GC. This can lead to potential memory leaks, and in the worst scenario, your website can die due to memory crunch.

Unsubscribing an event is very simple and straightforward; you can achieve this as shown here:

ActionControl.OnAddClick -= ActionControl_OnAddClick;

There is another way to subscribe an event using anonymous methods. For that, we need the assignment operator (+=) to attach an anonymous method to the event. Following is an example that shows how to subscribe an event with an anonymous method:

ActionControl.OnAddClick += 
                delegate
                    {
                        //Do the add operation here.
                };

For anonymous methods, we cannot easily unsubscribe from an event. To unsubscribe, we need to store the anonymous method in a delegate variable and then we need to add the delegate to the event. Here I will suggest that if there is a case where you need to unsubscribe your event, then don't go for the anonymous methods.

Event unsubscribing is very important if we are working on Windows applications. If we do not unsubscribe an event properly, we may face resource leaks. It is very much required to unsubscribe from events before we dispose a subscriber object. If you don't unsubscribe an event, for those which are referenced by some publishing object (like MDI in Windows), if they hold a reference to the subscriber object (like a Windows form), they will not be collected by the garbage collector.

Weak Reference

Till now, we talked about references. All of those references are called "strong references".This means that as long as the reference exists, GC will not collect the object even after dispose. A weak reference is a special type of reference where the GC can collect the object even though there exists some reference to it.

For more details on Weak Reference, you can take a look at the following articles:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Prakash Kalakoti
Technical Lead
India India
Its me Smile | :)

Comments and Discussions

 
QuestionApproach 3: Indirect Subscription - Best Pinmembersmerp6915-Aug-13 17:33 
GeneralThanks PinmemberRaja Ravi varaman9-Jul-13 2:34 
GeneralMy vote of 5 Pinmembermanoj kumar choubey17-Feb-12 21:44 
GeneralMy vote of 5 PinmemberMonjurul Habib3-Mar-11 8:00 
5
GeneralRe: My vote of 5 PinmemberPrakash Kalakoti4-Mar-11 20:03 
GeneralMy vote of 4 PinmvpPete O'Hanlon2-Mar-11 5:36 
GeneralRe: My vote of 4 PinmemberPrakash Kalakoti4-Mar-11 20:03 
GeneralRe: My vote of 4 PinmvpPete O'Hanlon8-Mar-11 6:11 
GeneralRe: My vote of 4 PinmemberPrakash Kalakoti9-Mar-11 0:47 
GeneralMy vote of 5 PinmemberShashankSinghThakur1-Mar-11 7:31 
GeneralRe: My vote of 5 PinmemberPrakash Kalakoti4-Mar-11 20:01 
GeneralMy vote of 5 Pinmembersenguptaamlan28-Feb-11 22:36 
GeneralRe: My vote of 5 PinmemberPrakash Kalakoti4-Mar-11 20:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 9 Mar 2011
Article Copyright 2011 by Prakash Kalakoti
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid