Building a newsletter management service in ASP.NET: Part 1


by Jason Salas, KUAM.COM
January 20, 2003

 

Past editions:

A simple document management system in ASP with XML and XSLT

Password-protect your Web work

Error Handling in VBScript

Creating a banner ad rotation system

The Web-safe color palette for developers

Time-saving Windows shortcuts

Analyzing your site's traffic logfiles

Technologies Used:

Level of Difficulty

Download the source code for this project

One of the biggest requests I get as a developer is people asking how they can easily setup a mailing list manager. Having such functionality as part of your site can greatly benefit traffic, and gives you an additional avenue for users to check out your content. A mailing list additionally pushes content asynchronously to people in a manner that’s convenient to them (for the most part). Mailing lists also carry the potential for revenue opportunities, giving you a venue in which to sell sponsored real estate to advertisers.

Some people like to dedicate an entire page to their newsletter signup forms. I prefer to use such a service as a complementary feature of a page. If people want to signup for a mailing list, they shouldn’t have to reload the entire page, or click and have to visit a separate page. A smaller form for me does the trick. Let people signup and get on with their lives.

In this article, we’ll look at how to build an intelligent newsletter signup application. First, we’ll look at a simple process that allows a user to signup for multiple mailing lists. In the future, we’ll examine a fun process where you can easily generate and distribute newsletters to your mailing list recipients. 


Getting Started
The big challenge when writing the application is providing the right level of protection against useless information (improperly formatted e-mail addresses, duplicate entries, etc.) . This is accomplished through validation on both the client and the server, by defining and enforcing validation rules. In ASP.NET, this is easily done by using Validation server controls.

Obviously, the advantages to developing such a process using ASP.NET are:
1. The ease by which a developer can include client-side validation rules within a WebForm,
2. Consolidation of functionality all into a single page, rather than distributing code across multiple files,
3. The performance benefits of an application, relative to ASP Classic,
4. The separation of programming code from presentation content.


Upscaling from ASP Classic

I’ve written similar scripts in Active Server Pages 3.0 (ASP Classic), which essentially did the same thing as what we’re going to do here. A user visits a Web page and is presented with a form that includes elements for subscribing to one or more newsletters (see Figure 1). The user selects the newsletter(s) she wishes to subscribe to, and enters her e-mail address. The script processes this information and then returns a message to the browser, indicating that the user subscribed to her newsletter(s) and will receive future mailings through e-mail, or informing her that she already belongs to a mailing list and therefore doesn’t need to re-subscribe.


Figure 1: presenting the user with a newsletter signup form

I would also typically be relegated to having to distribute code across multiple .ASP files, usually having one page each for (1) the signup form, (2) the script processing the form’s entries, and (3) a page presenting error messages to the user if errors occurred (click here for an example of how to do this in ASP Classic). ASP.NET’s postback model allows us to write pages with enhanced functionality and features, all within the context of the same file. It’s much easier to debug this way, and subsequently, much easier to manage.

I’ll be the first to admit that the first couple of times that I did this using ASP Classic, I didn’t write the world’s best application. (You can find the code for the ASP Classic newsletter signup process in the code download). I wasn’t implementing as much data validation as I could, and, providing that the user entered a “valid” e-mail address (which meant only that the user submitted a non-blank string of characters, and in doing so including a “@” and “.” within the string), they could theoretically signup to receive newsletters as many times as they wanted to. And quite often did. Many newbies and neophyte ‘Netizens thought they had to subscribe over and over from week-to-week to get mailings, just because they didn’t know any better, and the newsletter didn’t tell them not to. This resulted in multiple mailings for each user, which leads to their frustration, and likely prevents them from signing up with you further.

I got around this problem by using a SQL statement using “SELECT DISTINCT…” to include only single records for matching e-mail addresses, but still, it builds up the database table, and doesn’t provide optimal solutions. It would be better to inform the user if they already belong to a mailing list as such, right when they signup, and avoids the issue of having duplicate entries in the database altogether.

This lack of proper design sooner or later causes problems, and you can’t place blame on your user for improperly using an application that is poorly built.

The bottom line is that the ASP Classic solution was hacky, not optimized, and required a lot of code traversing multiple pages to get the job done.

For this reason, we’re going to build a robust, smart application. We’ll employ ASP.NET’s Validation Server Controls to enforce strict guidelines of people entering data, specifically, in regards to the user’s e-mail address. The user’s e-mail field in our WebForm is required, and we’ll use a robust regular expression to certify the validity of the user’s e-mail address.


Beneath the Hood
In the background, the ASP.NET WebForm calls a SQL Server stored procedure, which uses logic to determine the correct action to take, depending on whether the user is currently subscribed to the newsletter, or not. The process is simple: the stored procedure runs a simple query to extract all of the records within the database table with the user’s e-mail address. If no such records exist, the stored procedure adds their e-mail address and other information as a subscription profile to the database. If the number of records is greater than zero, this indicates that the user previously filled out the form and currently subscribes to a mailing list, and doesn’t add them to the database. 

In either situation, the stored procedure returns a numerical code to the client (either “1” for a successfully subscribing the user, or “-1” for an unsuccessful attempt), which is used by the ASP.NET WebForm to generate either a confirmation or error message, which is presented to the user.


How it Works
OK, enough yakking, let’s get right down to the code. The main goal here isn’t to fall victim to the ASP Classic syndrome of spreading line after line of spaghetti code across multiple pages, rather we should clearly separate all our logic and presentation code in a structured, segmented format, all within the same file. 

First things first, we’re going to use multiple Panel Web Server Controls, which demonstrates one of my favorite tricks in ASP.NET. By using the Panel controls, we can contain page-level elements within multiple Panels, and manipulate each panel’s Visible property (a Boolean value) to have the panel not be available to user when certain events occur. This is how we gain the advantage of not needing multiple .ASP files for the same process – we just turn set the visibility of the panel containing the form elements to “false” after the form posts back to itself.

In this example, we use two Panel controls, one with an ID attribute of “pnlForm” and the other with an ID attribute of “pnlConfirmation”. The former contains the HTML <FORM> tags and form elements of the page, while the latter contains only a single ASP.NET Label Web Server Control, to display a message to the user, letting her know whether she successfully signed up for the newsletter, or if she’s already subscribed to it.

Now let’s look at our form. Our example uses three newsletters, KUAM News, KUAM-TV, and Breaking News, each rendered as an HTML checkbox on the WebForm by way of a single CheckBoxList server control. Each checkbox can be accessed programmatically as a ListItem object, which we’ll see shortly. Note the use of the OnInit attribute of our CheckBoxList, which references a private method we’ve defined in the server-side scripting declaration at the top of the file, Make_Form_Arrays. This method raises the Init event of the ASP.NET page lifecycle, and populates the control with key-value pairs and renders the control as a series of checkboxes.

<html>
<head>
<title>Newsletter signup service</title>
<style>BODY {font-family:Arial;font-size:10pt;} </style>
</head>
<body>
<asp:Panel id="pnlForm" runat="server">
<form runat="server">
<asp:CheckBoxList id="Mailers" OnInit="Make_Form_Arrays" runat="server"/>
<br/>
Enter your e-mail address:
<br/>
<asp:TextBox id="txtEmail" runat="server"/>
<asp:RequiredFieldValidator id="validateEmail" controltovalidate="txtEmail" display="dynamic" text="* E-mail is required" runat="server"/>
<asp:RegularExpressionValidator id="regexValidate" validationexpression="^[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}$" controltovalidate="txtEmail" display="dynamic" text="Your e-mail address is not in the proper format" runat="server"/>
<br/><br/>
Enter your first name:
<br/>
<asp:TextBox id="txtFirstName" runat="server"/>
<br/><br/>
Enter your last name:
<br/>
<asp:TextBox id="txtLastName" runat="server"/>
<br/>
<asp:Button id="processForm" text="Click here" runat="server"/>
</form>
</asp:Panel>
<asp:Panel id="pnlConfirmation" runat="server">
<asp:Label id="lblMessage" visible="true" runat="server"/>
</asp:Panel>
</body>
</html>

Within the method, a Hashtable is declared and populated with keys and value, which constitute the names of tables within our database and real names for those tables, respectively. The data is set as the datasource of the CheckBoxList control and then bound to the control by calling the DataBind method.

// create the arrays for the form in the Page_Init event
private void Make_Form_Arrays(object sender,EventArgs e)
{
Hashtable ht = new Hashtable();
ht.Add("MyNews","Local News");
ht.Add("MyFinancial","Local Financial Reports");
ht.Add("BreakingNews","Breaking News");

Mailers.DataSource = ht;
Mailers.DataValueField = "Key";
Mailers.DataTextField = "Value";
Mailers.DataBind();
}

The form also includes the various elements that are used to gather information from the user. We use ASP.NET’s RequiredFieldValidator and RegularExpressionValidator Validation Controls to ensure that the e-mail field is filled out, and in the right format, respectively. Also, note the ValidationExpression property of the RegularExpressionValidator Validation control, being set to "^[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}$". I got this value from Steve Smith’s excellent online Regular Expression Library, which according to the author validates 99.9% of all e-mail addresses used today, with the exception of IP address-based e-mail address (which are rarely used anyway). 

This takes care of providing adequate client-side validation for the e-mail field, except for ensuring that the user clicked at least one of the checkboxes, which we’ll perform manually.


Server-Side Programming
Now that we’ve laid our WebForm’s look out, let’s dive right into the code. We start out by instantiating a number of protected objects - a SqlConnection, SqlDataAdapter, and SqlParameter – since we’re using the SQL Server Managed Provider, which uses the System.Data.SqlClient namespace. This is done so that the variables can be used within different methods and events within our script code.

public void Page_Load(object sender, EventArgs e)
{
conn = new SqlConnection("server=localhost;uid=sa;pwd=;database=DB-NAME;");

if(!IsPostBack)
{
pnlForm.Visible = true;
pnlConfirmation.Visible = false;
}
else
{
pnlForm.Visible = false;
pnlConfirmation.Visible = true;

try
{
StringBuilder sb = new StringBuilder();

conn.Open();

foreach(ListItem li in Mailers.Items)
{
if(li.Selected)
{
SubmitSubscriptionApplication(li.Text,li.Value,txtEmail.Text.Trim(),txtFirstName.Text.Trim(),txtLastName.Text.Trim());
sb.Append(li.Value);
}
}

if(sb.Length == 0)
{
lblMessage.Text = "<font color=\"red\"><b>You didn't signup to receive any newsletters</b></font>"
+ "<br><br><a href=\"javascript:history.go(-1)\">Return and try again</a>";
}
}
catch(SqlException ex)
{
Response.Write("<h2>A database-related error occurred when trying to subscribe to one or more of the newsletters.</h2><br>"
+ "<a href=\"mailto:webmaster@yoursite.com?subject=ERROR REPORT: DB error in mailer subscription&Body=" + ex.Message + "\">Report this error</a>");
}
catch(Exception ex)
{
Response.Write("<h2>A general error occurred when trying to subscribe to one of more of the newsletters.</h2><br>"
+ "<a href=\"mailto:webmaster@yoursite.com?subject=ERROR REPORT: General error in mailer subscription&Body=" + ex.Message + "\">Report this error</a>");
}
finally
{
if(conn.State == ConnectionState.Open)
{
conn.Close();
}
}
}
}

Within the Page_Load event we create our SqlConnection variable. We then setup a simple process by checking the WebForm’s PostBack state. If the user requests the page for the first time, the form is presented; if the user has filled out the form and clicked the button, a confirmation message is shown. As mentioned above, we display or hide the various parts of the WebForm by setting the Visible property of each of the Panel server controls to either true or false to make this happen.

We start the meat of our code within a try…catch…finally codeblock developed within Page_Load event. Within the try block we loop through the ListItems in the Items collection of the CheckBoxList control using a foreach statement, and nest an if… conditional statement to execute a custom method when a checkbox within the CheckBoxList is checked.

A StringBuilder object’s length is also evaluated, returning an error message to the user if none of the checkboxes were checked but the form was still submitted (if the Length property of the StringBuilder object is zero). We couldn’t use a validation control to do client-side validation like we did for the e-mail field, so this provides server-side validation and ensures that a user subscribes to at least one newsletter.

Two catch… blocks are used to trap and report errors, and a finally… block closes any open database connections.

Now for the magic of the process – the custom method.


Our Custom Method
The custom method SubmitSubscriptionApplication takes five arguments in its signature, specifically the newsletter’s formal name, the name of the database table the newsletter belongs to, the user’s e-mail, and the user’s first and last names. It returns string data that populates the Text property of the Label server control contained within the pnlConfirmation Panel server control.

// a custom method that essentially wraps the ExecuteNonQuery method
private string SubmitSubscriptionApplication(string mailerName,string newsletter,string email,string fname,string lname)
{
da = new SqlDataAdapter();
da.InsertCommand = new SqlCommand("SubscribeToNewsletters",conn);
da.InsertCommand.CommandType = CommandType.StoredProcedure;
da.InsertCommand.Parameters.Add("@email",email);
da.InsertCommand.Parameters.Add("@tablename",newsletter);
da.InsertCommand.Parameters.Add("@firstname",fname);
da.InsertCommand.Parameters.Add("@lastname",lname);
param = da.InsertCommand.Parameters.Add("@returnval",SqlDbType.Int);
param.Direction = ParameterDirection.ReturnValue;
da.InsertCommand.ExecuteNonQuery();

if((int)da.InsertCommand.Parameters["@returnval"].Value != -1)
{
lblMessage.Text += "<li>You successfully subscribed to receive the " + mailerName + " newsletter</li>";
}
else
{
lblMessage.Text += "<li>You already belong to the " + mailerName + " mailing list</li>";
}

return lblMessage.Text;
}

// create the arrays for the form in the Page_Init event
private void Make_Form_Arrays(object sender,EventArgs e)
{
Hashtable ht = new Hashtable();
ht.Add("MyNews","Local News");
ht.Add("MyFinancial","Local Financial Reports");
ht.Add("BreakingNews","Breaking News");

Mailers.DataSource = ht;
Mailers.DataValueField = "Key";
Mailers.DataTextField = "Value";
Mailers.DataBind();
}

Once called, the method instantiates the protected SqlDataAdapter object declared earlier, and sets up the parameters used within the stored procedure (which we’ll look at shortly). It then executes the stored procedure by calling the ExecuteNonQuery method. 

The stored procedure’s includes a return value parameter, which acts as the indicator for our results message, stored in the Text property of the Label control. This return value is evaluated in an if…else conditional statement, displaying a confirmation message made up of bulleted items for each newsletter successfully signed up for, or a series of bulleted items for those newsletters the user already belongs to.


Stored Procedure
The parameterized stored procedure SubscribeToNewsletters first runs a simple query to determine the number of rows in a recordset including the e-mail address that is passed to it. This number if the value assigned to a local variable, taken from SQL Server’s @@ROWCOUNT server variable. If the number returned is “0”, the user’s e-mail address does not exist in the database, and an INSERT INTO… statement is executed, adding the user. A value of “1” is also returned to the calling client. Anything other value than “0” implies that the user has previously signed up for a newsletter, in which case the stored procedure returns a value of “-1” to the client, and does nothing else.

-- define our stored procedure 
CREATE PROCEDURE SubscribeToNewsletters
@email VARCHAR(100),
@tablename VARCHAR(60),
@firstname VARCHAR(60) = NULL,
@lastname VARCHAR(80) = NULL

AS

DECLARE @insertstring VARCHAR(2000)
DECLARE @checkforexistingrecord VARCHAR(60)
DECLARE @add VARCHAR(2000)

SET @insertstring = 'DECLARE @checkforexistingrecord VARCHAR(60)
SELECT Email FROM ' + @tablename + ' WHERE Email = ''' + @email + ''''
EXEC(@insertstring)
SELECT @checkforexistingrecord = @@ROWCOUNT
PRINT('The number of rows returned by the query is: ' + @checkforexistingrecord)
PRINT('')

IF @checkforexistingrecord = 0
BEGIN 
SET @add = 'INSERT INTO ' + @tablename + ' (Email,FirstName,LastName) VALUES (''' + @email + ''',''' + @firstname + ''',''' + @lastname + ''')'
EXECUTE(@add)
PRINT('Added the following string: ' + @add)
PRINT('')
PRINT('Your e-mail address has been added to the DB table, and will receive the newsletter you specified.')
RETURN 1
END
ELSE
BEGIN
PRINT('You already exist in the DB!')
RETURN -1
END
GO



Give it a Spin
Now, all you have to do is test the process a few times to see it in action. Browse to the page and try adding your own e-mail address, first and last names, and signup for one or more newsletters. The result should look like Figure 2. Then, do the exact same thing again – the result this time should look like Figure 3. Then, do what all great programmers do – try to break your app. Enter invalid information, try submitting malformed e-mail addresses, don’t select any newsletters and submit the form, etc. The validation rules built into the WebForm, coupled with the pre-checking in the stored procedure on the database should prevent any tampering, and make for a better user experience.


Figure 2: a successful newsletter subscription


Figure 3: an unsuccessful newsletter subscription



Alternative Solutions
Of course, the way I presented to create the newsletter service isn’t the sole way this can be done. Another architecture could create the entire listing of available newsletters in a single database table, and use multiple Bit (or Boolean) fields. Then, using the same logic within the stored procedure, the IF…ELSE conditional statement would run and execute a SQL “UPDATE…” statement that would subscribe and/or unsubscribe the user from receiving the various newsletters by changing the field values.

Also, although I haven’t done it here, most newsletter services send a confirmation greeting e-mail to the user immediately upon confirmation of their subscription, including information on how to unsubscribe. This normally includes the standard information on when they will receive the information, whether they’d like to receive the newsletter in plaintext or HTML format, etc. This is completely doable, and very easy with ASP.NET by instantiating the .NET Framework’s MailMessage class.

The expandability of code, once written, is the best part about ASP.NET. You can take this simple service and do a number of things with it.

You could also expand on this idea and very easily turn the ASP.NET WebForm into an XML Web service and allow remote clients (i.e., Windows applications, other people’s Web sites) to consume it and use your newsletter service remotely, signing up on their sites or their platforms. I actually used a modified version of this in an intranet application for the television news station I work for I built so reporters could get newsfeeds from the Internet distributed to them, without getting someone else’s stories.

Again, I believe in letting people signup, if they so choose, and get on with their life. I typically make use of this type of feature in projects rolled up into an ASP.NET user control, not having too much of a user interface and taking up minimal page real estate. For example, I’d typically embed this within a page by referencing the SRC attribute of <IFRAME> tags. Thus, it sits somewhere in the corner of a page, and doesn’t force the user’s page to be entirely refreshed.

For the more daring developer, you could further enhance and improve upon this design and expand it by turning it into a custom ASP.NET server control, and make it redistributable, and even sell it to interested parties.


Conclusion 
Building great applications for the Web means constructing in great functionality and creating a positive user experience by having the foresight to prevent erroneous data or situations, whether deliberate or unintentional. A lot of this has to do with experience and knowing what people would do, drawing from what you’ve done in the past. 

It also stems from the fact that you can make your life a whole lot more livable if you don’t have to continually do manual and often-tedious changes to data structures that should be simple.

So that’s it for Part 1. Set this up on your own server, or with your ISP, and start building a mailing list for your newsletter(s). In Part 2, we’ll build a script using ASP.NET to seamlessly mail out your newsletters, with no headache to you.

Download the source code for this project

Have fun using this in your own projects!