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:
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)
{
Action.Direct direct = this.Page as Action.Direct;
if (direct != null)
{
direct.Add();
}
}
protected void btnEdit_Click(object sender, EventArgs e)
{
Action.Direct direct = this.Page as Action.Direct;
if (direct != null)
{
direct.Update();
}
}
protected void btnDelete_Click(object sender, EventArgs e)
{
Action.Direct direct = this.Page as Action.Direct;
if (direct != null)
{
direct.Delete();
}
}
protected void btnReset_Click(object sender, EventArgs e)
{
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.
- 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. - Some times when you are working on the Designer, there might be a case when the event handler gets lost without your notice.
- Even if you have not added the event handler, you will not get any errors or warnings.
- 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;
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
- You don't need to register the event handlers in the page.
- 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.
- 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
{
};
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: