The newsletter signup control is the means to add
subscribers to a newsletter item in the database. Below is the class
definition for the control.
Listing 1
[
ValidationProperty("EmailAddress"),
DefaultProperty("EmailAddress"),
DefaultEvent("SigningUp")
]
public class NewsletterSignup : WebControl, IPostBackDataHandler, IPostBackEventHandler
{
}
This control inherits from WebControl and also makes use of
capturing post back information (through IPostBackDataHandler), as well as
raising events from within the control (IPostBackEventHandler), as we will soon
see. Notice the attributes used; EmailAddress is the property that contains
the email address used to sign up, which is the default. It's also the
property that is validated by any validation controls, as designated by the
ValidationProperty attribute. Lastly, DefaultEvent is the default event for
the control, so when double-clicking the control in the designer, SigningUp is
the event handler created. Below are some of the properties defined:
Listing 2
[DefaultValue("Signup"), Localizable(true)]
public string ButtonText
{
get
{
object o = ViewState["ButtonText"];
return (o == null) ? "Signup" : o.ToString();
}
set
{
ViewState["ButtonText"] = value;
}
}
[Localizable(true)]
public string Description
{
get
{
object o = ViewState["Description"];
return (o == null) ? null : o.ToString();
}
set
{
ViewState["Description"] = value;
}
}
public string EmailAddress
{
get
{
object o = ViewState["EmailAddress"];
return (o == null) ? null : o.ToString();
}
set
{
ViewState["EmailAddress"] = value;
}
}
public string NewsletterName
{
get
{
object o = ViewState["NewsletterName"];
return (o == null) ? null : o.ToString();
}
set
{
ViewState["NewsletterName"] = value;
}
}
NewsletterName is the property that links the signup control
to a specific newsletter. At the current moment, because the values are stored
in an underlying data source, the value has to be a manual string that must
match up to an existing newsletter defined using the provider. Designer
support has not yet been added, which it may be possible through a custom
designer.
The properties are stored in ViewState as the storage
mechanism. The attribute I will mention is the Localizable attribute. By
passing the true value, Localizable allows the value to be localized. When
creating a page resource file with all of the localization keys, these
properties can be included in the resource file, and the final value provided
at runtime.
This control has events that the page can subscribe to. Two
of the events are SigningUp and SignedUp, which the equivalent On<Event>
methods are shown below:
Listing 3
protected virtual void OnSignedUp(EventArgs e)
{
if (SignedUp != null)
SignedUp(this, e);
//Clear the email address after signing up
this.EmailAddress = null;
}
protected virtual void OnSigningUp(CancelEventArgs e)
{
if (SigningUp != null)
SigningUp(this, e);
}
The data posted back is collected via the LoadPostData
method handler, a part of the IPostBackDataHandler interface. This interface
marks a control for PostBack data handling and through this the email address
property is returned. Note that the unique ID is used; you will see later that
the control given for the Email Address control is assigned the UniqueID
property.
Listing 4
public bool LoadPostData(string postDataKey,
System.Collections.Specialized.NameValueCollection postCollection)
{
this.EmailAddress = postCollection[this.UniqueID];
return false;
}
The IPostBackEventHandler handles events that are raised
within the button controls. That will come next; however, let's look at what happens
when the event is raised:
Listing 5
public void RaisePostBackEvent(string eventArgument)
{
if (SigningUp == null && string.IsNullOrEmpty(this.NewsletterName))
throw new Exception("The SigningUp event must be handled.");
CancelEventArgs args = new CancelEventArgs(false);
this.OnSigningUp(args);
if (!args.Cancel)
{
if (string.IsNullOrEmpty(this.NewsletterName))
throw new NullReferenceException("The newsletter is null");
if (!Newsletter.NewsletterExists(this.NewsletterName))
throw new Exception("The newsletter provided does not exist");
Newsletter.AddSubscription(this.EmailAddress, this.NewsletterName);
this.OnSignedUp(EventArgs.Empty);
}
}
Normally, multiple events can be handled within this event
handler, noted by the event argument value. However, only one event is raised
and that is all that needs to be accounted for. When the event is raised, the
newsletter name must be provided through the NewsletterName property, or if the
property isn't assigned the SigningUp event must be handled. For this to work,
during the SigningUp event, the NewsletterName must be provided. If not
provided by the end, then an error is raised because the control doesn't know
for which newsletter to add the subscriber. The subscription is added and the SignedUp
event is raised. Below is the rendering process of the control:
Listing 6
protected override void Render(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
writer.RenderBeginTag(HtmlTextWriterTag.Span);
writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID);
writer.AddAttribute(HtmlTextWriterAttribute.Name, this.UniqueID);
writer.AddAttribute(HtmlTextWriterAttribute.Type, "text");
writer.AddAttribute(HtmlTextWriterAttribute.Value, this.EmailAddress);
writer.RenderBeginTag(HtmlTextWriterTag.Input);
writer.RenderEndTag(); //input
writer.Write(" ");
writer.AddAttribute(HtmlTextWriterAttribute.Id, this.UniqueID + "signup");
writer.AddAttribute(HtmlTextWriterAttribute.Href,
Page.ClientScript.GetPostBackClientHyperlink(this, "signup", true));
writer.RenderBeginTag(HtmlTextWriterTag.A);
writer.Write(this.ButtonText);
writer.RenderEndTag(); //a
//If the description exists, render it below the textbox.
if (!string.IsNullOrEmpty(this.Description))
{
writer.Write("<br>");
writer.Write(this.Description);
}
writer.RenderEndTag(); //span
}
I noted the process for using the HtmlTextWriter above;
notice that in the Render method, the process for creating the interface is
rendered. This is one of the processes to create an interface for a custom
control. Notice that the UniqueID property, assigned to the name HTML
attribute above, is used in the LoadPostData method. Since the UniqueID
property is assigned to the textbox, instead of for the link rendered below it,
it can be used to retrieve the ViewState information. The button doesn't matter
as much, except for the value provided to the href attribute.
Page.ClientScript.GetPostBackClientHyperlink returns a script with the event
argument "signup", which is passed the RaisePostBackEvent method.