User controls can be especially beneficial to use in your applications - They can reduce the amount of code on a page, split functionality between pages, and be reused in different parts of the application.
I think that one thing that deters developers from using user controls to their full potential is the problem of passing information from the user control to the parent. Going from the parent to the user control is cake (public properties and methods) but reverse the process and it can become intimidating.
I’m sure I’m not the first developer who really started using user controls once I figured out how to raise events from the user control to the parent page. And I’m here now to teach you exactly how to do just that.
For this example, I have one page
EmailPerson.aspx and one user control,
SendEmail.ascx. On the EmailPerson page, I have a simple repeater with some fake data, including name, position, and email address. Inside each repeater item is the SendEmail user control.
EmailPerson.aspx ASPX code
-
<asp:Label runat="server" ID="lblMessage" Font-Bold="true" ForeColor="Red" />
-
<br />
-
<br />
-
<asp:Repeater runat="server" ID="rptPeople">
-
<HeaderTemplate>
-
<table cellpadding="0" cellspacing="0" class="table" style="width: 720px">
-
<tr>
-
<td class="tdheading" style="width: 220px;">
-
Name
-
</td>
-
<td class="tdheading" style="width: 300px;">
-
Position
-
</td>
-
<td class="tdheading">
-
Email Address
-
</td>
-
</tr>
-
</HeaderTemplate>
-
<ItemTemplate>
-
<tr>
-
<td>
-
<asp:Label runat="server" ID="lblName" Text='<%#Eval("Name") %>' />
-
</td>
-
<td>
-
<asp:Label runat="server" ID="lblPosition" Text='<%#Eval("Position") %>' />
-
</td>
-
<td>
-
<a href="javascript:;" runat="server" id="hlEmail"></a>
-
<asp:HiddenField runat="server" ID="hfEmailAddress" Value='<%#Eval("Email_Address") %>' />
-
</td>
-
</tr>
-
<tr>
-
<td colspan="3">
-
<asp:Panel runat="server" ID="pnlSendEmail" Style="display: none;">
-
<asp:SendEmail ID="emailPerson" runat="server" OnNotify="emailPerson_Notify" />
-
</asp:Panel>
-
</td>
-
</tr>
-
</ItemTemplate>
-
<FooterTemplate>
-
</table>
-
</FooterTemplate>
-
</asp:Repeater>
EmailPerson.aspx.cs C# Code
-
public partial class EmailPerson : System.Web.UI.Page
-
{
-
#region Declarations
-
-
DataTable dt = null;
-
DataRow dr = null;
-
-
#endregion
-
-
protected void Page_Load(object sender, EventArgs e)
-
{
-
if (!IsPostBack)
-
{
-
BindPersonData();
-
}
-
}
-
-
#region Get and Bind Data
-
-
protected DataTable GetPersonData()
-
{
-
// In the real world, this would be a database call. However, for this example I'm creating a datatable to bind to the repeater
-
dt = new DataTable();
-
-
DataColumn dc1 = new DataColumn("Name", System.Type.GetType("System.String"));
-
dt.Columns.Add(dc1);
-
-
DataColumn dc2 = newDataColumn("Position", System.Type.GetType("System.String"));
-
dt.Columns.Add(dc2);
-
-
DataColumn dc3 = new DataColumn("Email_Address", System.Type.GetType("System.String"));
-
dt.Columns.Add(dc3);
-
-
// Creating new row
-
dr = dt.NewRow();
-
-
// Assigning values to each row column. Again, this would usually be a database call
-
dr["Name"] = "Hannah Smith";
-
dr["Position"] = "Software Developer";
-
dr["Email_Address"] = "hannah.smith@thiscompany.com";
-
dt.Rows.Add(dr);
-
-
// Creating another new row
-
dr = dt.NewRow();
-
-
// Assign some more values
-
dr["Name"] = "Jason Alexander";
-
dr["Position"] = "Software Developer";
-
dr["Email_Address"] = "jason.alexander@thiscompany.com";
-
dt.Rows.Add(dr);
-
-
// Creating another new row
-
dr = dt.NewRow();
-
-
// Assign some more values
-
dr["Name"] = "Kevin Miller";
-
dr["Position"] = "Software Development Manager";
-
dr["Email_Address"] = "kevin.miller@thiscompany.com";
-
dt.Rows.Add(dr);
-
-
return dt;
-
}
-
-
protected void BindPersonData()
-
{
-
rptPeople.DataSource = GetPersonData();
-
rptPeople.DataBind();
-
-
// Loop through repeater to hook up modal/user control
-
foreach (RepeaterItem item in rptPeople.Items)
-
{
-
if (item.ItemType == ListItemType.AlternatingItem || item.ItemType == ListItemType.Item)
-
{
-
// Find the button control for each repeater item
-
HtmlAnchor hlEmail = (HtmlAnchor)item.FindControl("hlEmail");
-
-
string emailAddress = ((HiddenField)item.FindControl("hfEmailAddress")).Value;
-
-
// Customize email button text with person's name
-
hlEmail.InnerText = "Email " + ((Label)item.FindControl("lblName")).Text;
-
-
// Find the SendEmail control for each repeater item
-
SendEmail emailPerson = (SendEmail)item.FindControl("emailPerson");
-
-
// Set SendEmail properties accordingly
-
emailPerson.EmailAddress = emailAddress;
-
emailPerson.PersonName = ((Label)item.FindControl("lblName")).Text;
-
-
-
// Wire up show/hide panel Javascript using client IDs of panel and link
-
hlEmail.Attributes.Add("onclick", "ShowPanel('" + ((Panel)item.FindControl("pnlSendEmail")).ClientID + "','"
-
+ hlEmail.ClientID + "');");
-
}
-
}
-
-
}
-
-
#endregion
-
-
protected void emailPerson_Notify(object sender, EventArgs e)
-
{
-
// This is the method raised from the user control after it attempts to send the email
-
-
// Declare the user control (sender)
-
SendEmail sendEmail = (SendEmail)sender;
-
-
// Set the label to show the SentMessage from the user control
-
lblMessage.Text = sendEmail.SentMessage;
-
-
// Keep the correct panel open
-
((Panel)sendEmail.Parent.FindControl("pnlSendEmail")).Attributes.Add("style", "display:block;");
-
}
-
-
}
Let me quickly go through the methods.
On first page load, we call the Get/BindPersonData methods. For the sake of this example, I'm just creating a fake data table and some psuedo data in the get data method. The BindPersonData method is where it gets interesting. After binding the
rptPeople repeater, I'm looping through the items to accomplish a few tasks. Lines 83-87, I'm finding the SendEmail control nested in each repeater item (remember, I'm looping through each repeater item respectively). I'm then assigning the EmailAddress and PersonName properties of the SendEmai control based on the label values for that repeater item. For example, the SendEmail.PersonName property will get set to the value of label control lblName for that item.
Lastly, I wire up a Javascript hide/show panel method in line 91, passing in the panel client ID and email hyperlink client ID for each repeater item.
SendEmail.ascx Code
-
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="SendEmail.ascx.cs" Inherits="SendEmail" %>
-
<asp:UpdatePanel runat="server" ID="UpdatePanel">
-
<ContentTemplate>
-
<div style="padding-left: 10px;">
-
<asp:Label ID="lblMessage" runat="server" Visible="false" ForeColor="Red" />
-
</div>
-
<table cellpadding="0" cellspacing="0" class="table" style="width: 680px; border-top: solid 1px #b9c9d6; margin: 10px;">
-
<tr>
-
<td style="width: 150px; vertical-align: top; padding-left: 10px;">
-
To Email Address:
-
<asp:RegularExpressionValidator runat="server" ErrorMessage="E-mail address invalid."
-
ID="regEx1" ControlToValidate="txtToEmail" ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*"
-
SetFocusOnError="true" ValidationGroup="EmailPerson">*</asp:RegularExpressionValidator>
-
</td>
-
<td style="vertical-align: top;">
-
<asp:TextBox runat="server" ID="txtToEmail" CssClass="textBox" MaxLength="50" Width="360px" />
-
</td>
-
</tr>
-
<tr>
-
<td style="width: 150px; vertical-align: top; padding-left: 10px;">
-
From Email Address:
-
<asp:RegularExpressionValidator runat="server" ErrorMessage="From email address invalid."
-
ID="RegularExpressionValidator1" ControlToValidate="txtFromEmail" ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*"
-
SetFocusOnError="true" ValidationGroup="EmailPerson">>*</asp:RegularExpressionValidator>
-
</td>
-
<td style="vertical-align: top;">
-
<asp:TextBox runat="server" ID="txtFromEmail" CssClass="textBox" MaxLength="50" Width="360px" />
-
</td>
-
</tr>
-
<tr>
-
<td style="width: 150px; vertical-align: top; padding-left: 10px;">
-
Email Subject:
-
<asp:RegularExpressionValidator runat="server" ID="regEx2" ControlToValidate="txtEmailSubject"
-
ValidationExpression="[a-zA-Z0-9\s()%&$.!',/:-]{1,70}$" SetFocusOnError="true"
-
ValidationGroup="EmailPerson" ErrorMessage="Email subject - Max 70 characters, no special characters">*</asp:RegularExpressionValidator>
-
</td>
-
<td style="vertical-align: top;">
-
<asp:TextBox runat="server" ID="txtEmailSubject" CssClass="textBox" MaxLength="70"
-
Width="360px" Text="Salary Increase" />
-
</td>
-
</tr>
-
<tr>
-
<td colspan="2" style="height: 12px;">
-
<hr class="hr" />
-
</td>
-
</tr>
-
<tr>
-
<td style="vertical-align: top;">
-
<asp:RadioButton runat="server" ID="rbDefaultEmail" Checked="true" GroupName="EmailPerson"
-
Text="Send Default Email" Font-Bold="true" AutoPostBack="true" OnCheckedChanged="ToggleBold" />
-
</td>
-
<td>
-
<div style="padding: 10px; margin: 0px 10px 10px 0px; border: solid 1px #acabab;
-
background: url('../App_Themes/Beach_Theme/images/bg_textbox.gif' ); background-repeat: repeat-x;">
-
<asp:Literal runat="server" ID="litDefaultEmail">Dear <*PersonName*>,
-
<br />
-
You've received a salary increase to $1 million dollars a year. Congratulations!
-
<br />
-
<br />
-
Sincerely,
-
<br />Human Resources</asp:Literal>
-
</div>
-
</td>
-
</tr>
-
<tr>
-
<td colspan="2" style="height: 12px;">
-
</td>
-
</tr>
-
<tr>
-
<td style="vertical-align: top;">
-
<asp:RadioButton runat="server" ID="rbCustomEmail" Checked="false" GroupName="EmailPerson"
-
Text="Send Custom Email" AutoPostBack="true" OnCheckedChanged="ToggleBold" />
-
</td>
-
<td>
-
<asp:Editor ID="editorCustomEmail" runat="server" Height="240px" />
-
</td>
-
</tr>
-
<tr>
-
<td colspan="2" style="height: 22px;">
-
</td>
-
</tr>
-
<tr>
-
<td>
-
</td>
-
<td align="center">
-
<asp:Button runat="server" ID="btnSendEmail" Text="Send Email" CssClass="button"
-
OnClick="SendPersonEmail" ValidationGroup="EmailPerson"/>
-
</td>
-
</tr>
-
<tr>
-
<td colspan="2" style="height: 22px;">
-
</td>
-
</tr>
-
</table>
-
</ContentTemplate>
-
<Triggers>
-
<asp:PostBackTrigger ControlID="btnSendEmail" />
-
</Triggers>
-
</asp:UpdatePanel>
-
SendEmail.ascx.cs C#
-
-
public partial class SendEmail : System.Web.UI.UserControl
-
{
-
#region Public Properties
-
-
public string EmailAddress { get; set; }
-
-
public string PersonName { get; set; }
-
-
public string SentMessage { get; set; }
-
-
#endregion
-
-
#region Public Events
-
-
// Create a delegate for the notify event
-
// This delegate takes in two parameters, the sending object and the System.EventArgs class
-
public delegate void NotificationSent(object sender, EventArgs e);
-
-
// Create an event for the NotificationSend Delegate above
-
public event NotificationSent Notify;
-
-
#endregion
-
-
protected void Page_Load(object sender, EventArgs e)
-
{
-
if (!IsPostBack)
-
{
-
BindEmail();
-
}
-
}
-
-
protected void BindEmail()
-
{
-
if (this.EmailAddress == string.Empty)
-
{
-
lblMessage.Text = "There is no e-mail address on file for this requestor. Please enter an e-mail address before sending the compliance notification.";
-
}
-
else
-
{
-
// Set to-email textbox to the person's email address
-
txtToEmail.Text = this.EmailAddress;
-
}
-
-
// Replace placeholder with person's name
-
litDefaultEmail.Text = litDefaultEmail.Text.Replace("<*PersonName*>", this.PersonName);
-
-
editorCustomEmail.Content = litDefaultEmail.Text;
-
}
-
-
protected void ToggleBold(object sender, EventArgs e)
-
{
-
if (rbCustomEmail.Checked)
-
{
-
rbCustomEmail.Font.Bold = true;
-
rbDefaultEmail.Font.Bold = false;
-
}
-
else
-
{
-
rbCustomEmail.Font.Bold = false;
-
rbDefaultEmail.Font.Bold = true;
-
}
-
}
-
-
protected void SendPersonEmail(object sender, EventArgs e)
-
{
-
if (txtEmailSubject.Text == string.Empty || txtFromEmail.Text == string.Empty || txtToEmail.Text == string.Empty)
-
{
-
this.SentMessage = "Please complete all fields before sending the email.";
-
-
// Call method to trigger event for parent
-
UpdateNotificationMessage();
-
return;
-
}
-
-
string emailBody = string.Empty;
-
-
// Set email body string
-
switch (rbDefaultEmail.Checked)
-
{
-
case false:
-
emailBody = litDefaultEmail.Text;
-
break;
-
default:
-
emailBody = editorCustomEmail.Content;
-
break;
-
}
-
-
// Set the SentMessage property accordingly based on success or not
-
if (EmailMethods.SendEmail(txtToEmail.Text, txtFromEmail.Text, txtEmailSubject.Text, emailBody))
-
{
-
this.SentMessage = "A notification email has been successfully sent to " + txtToEmail.Text + ".<br />";
-
}
-
else
-
{
-
this.SentMessage = "An problem occurred while sending the email to " + txtToEmail.Text + ". The email has not been sent.<br />"
-
+ "The site administrator has been notified of this error.";
-
}
-
-
// Call method to trigger event for parent
-
UpdateNotificationMessage();
-
}
-
-
protected void UpdateNotificationMessage()
-
{
-
// This method invokes the Notify event for the user control so we can handle it in the parent ASPX page
-
// Always check for null before raising the event in case the parent page isn't utilizing it
-
if (Notify != null)
-
Notify(this, null);
-
}
-
-
}
The C# for the SendEmail user control is fairly straight forward. Take a close look at lines 16-21. To create an event, you first define a public delegate for that event. In this case, the event name is Notify. It appears as OnNotify in the SendEmail user control attributes. The delegate will take in two parameters, the sending object and an instance of the System.EventArgs class. Line 21 actually defines the public event.
The BindEmail method uses the properties assigned to the user control to handle the text box and email text. Keep in mind that if you put a break point on line 35, it would fire 3 times, once for each of the three rows in our repeater. The properties passed in each of those three times would depend on the values being passed in from the repeater (remember the foreach loop?)
The SendPersonEmail method does just that after some server side validation. It also sets the SendEmail.SentMessage property based on email sent success (lines 92 and 96). Lastly and most importantly, it calls the UpdateNotificationMessage() subroutine on line 101, which is where we have our event ready to fire on the parent page. Line 108 checks to make sure that the event is defined in the parent page (OnNotify is null in the SendEmail tag, basically) and then calls the event.
The emailPerson_Notify event on EmailPerson.aspx.cs (starting on line 100) will now fire. In a nutshell, this method creates an instance of the SendEmail control from the sender, grabs the SendEmail.SentMessage property, and then assigns the SentMessage property string value to the message label on the parent page. In essense, the result of the SendPersonEmail method in the SendEmail user control will display on the parent page because the Notify event is fired from the user control and caught on the parent page.