To read the article online, visit http://www.4GuysFromRolla.com/articles/082907-1.aspx

Creating an ASP.NET 2.0 Polling User Control: Building the Polling User Control

By Scott Mitchell


Introduction


Welcome to the second article in a three-part series on building a polling User Control. In the first installment, Design Decisions and the Data Model, we looked at the design decisions made prior to creating the control as well as the poll's data model, which consisted of three tables in a SQL Server 2005 database.

We are now ready to start building the poll's user interface. Since my poll control only allows authenticated users to vote, if an anonymous visitor reaches the poll or if the authenticated user has already voted then the User Control displays the results of the poll in a read-only interface. If visited by an authenticated user who has yet to take the poll, it allows the user to vote, after which the results are stored in the database and the results displayed to the user.

In addition to building the polling User Control, we'll also look at how to add the poll to a web page. Read on to learn more!

Enumerating the Different Pieces of the Polling User Control


The typical polling user interface includes some question text, like "What is your favorite programming language?", along with a series of radio buttons listing possible answers, like "C#", "Visual Basic", "Pascal", and so forth. The visitor can select an answer and then cast his vote. After voting, the interface changes to show the current voting breakdown per answer. Therefore our User Control needs two separate displays: one that shows the poll answers and allows voting; and one that shows the answers and the voting breakdown, but prohibits voting.

When the voting interface is displayed, each poll answer needs to be rendered as a radio button option; there also needs to be a button that, when clicked, casts the user's vote. On the other hand, when the read-only interface is shown, each poll answer needs to list how many total people voted for that option along with the percentage of votes. In addition to the total numbers and percentage, my polling User Control provides a line depicting the percentage of votes. For example, if a particular answer has received 40% of the votes, a line underneath it will extend to 40% of the width of the User Control.

Regardless of whether the read-only or voting interface is displayed, the poll's question needs to be displayed at the top of the User Control.

The challenges facing us, then, are thus:

  • Create the user interface elements - we will use a variety of data Web controls to build our user interface, including a FormView, DataList, and RadioButtonList. There will also be Label and Button controls present.
  • Allow the page developer to specify the poll to display - a single instance of the poll User Control displays a poll specified by a PollID property in the User Control's code-behind class.
  • Insert a record into the UserResponses table when a vote is cast - the particular answer plus the ID of the currently logged on user must be inserted into a new row in UserResponses.
  • Display the poll results in the read-only interface - this involves determining the total number of votes cast for the poll and calculating and displaying the percentages both as text and as a line.
  • Determine when to show the read-only interaface vs. the voting interface - we need to add logic that can ascertain whether to show the current user the voting interface or the read-only interface. As discussed in Design Decisions and the Data Model, only authenticated users who have not yet taken the poll should see the voting interface; all other visitors will see the read-only interface.
The remainder of this article delves into each of these four challenges and then is capped off with a look at adding the polling control to an ASP.NET page.

Creating the User Interface Elements


Since the User Control needs to display information about the specified poll, I started by adding a FormView to the page and binding it to a SqlDataSource that returns all of the fields from the Polls table for the specified poll using the following SELECT query:

SELECT * FROM [Polls]
WHERE [PollID] = @PollID

The SqlDataSource is a control new to ASP.NET 2.0 that makes it a breeze to work with data. I used the two SqlDataSource controls in the poll User Control and a few others in the administration pages, as we'll discuss in the third installment. For more information on working with data in ASP.NET 2.0, check out my multi-part article series: Accessing and Updating Data in ASP.NET 2.0.

After configuring the SqlDataSource control and binding it to the FormView, I edited the FormView's ItemTemplate so that it displayed just the selected poll's DisplayText value.

Next, I needed to add controls within the FormView's ItemTemplate to display either the read-only or voting interface. To separate these two interfaces, I first added two Panel controls to the ItemTemplate beneath the Label displaying the poll's question. I (arbitrarily) decided to create the voting interface in the first Panel and added to it a RadioButtonList and Button control. I bound the RadioButtonList control to a new SqlDataSource control named PollAnswersDataSource that includes both a SelectCommand and InsertCommand. The SelectCommand returns all of the answers for the poll being displayed and sorts them by the SortOrder column in ascending order:

SELECT * FROM [PollAnswers]
WHERE [PollID] = @PollID
ORDER BY [SortOrder]

The InsertCommand will be executed when the Button is clicked. This will require writing a bit of code, but we'll get to that later in this article. The InsertCommand adds a new record to the UserResponses table, indicating the answer that the currently logged on user voted:

INSERT INTO UserResponses (UserID, PollAnswerID)
VALUES (@UserID, @PollAnswerID)

Finally, in the second Panel, which displays the read-only interface, I added a DataList control and bound it to a SqlDataSource named PollResultsDataSource that returns a record for each poll answer along with the total number of votes received for the answer. This total vote number was obtained by joining the PollAnswers with UserResponses and using a COUNT aggregate function to determine the number of votes for each answer, as the following query shows:

SELECT a.PollAnswerID, a.PollID, a.DisplayText, a.SortOrder,

COUNT(r.UserID) as Votes


FROM PollAnswers
   LEFT JOIN UserResponses r ON
      a.PollAnswerID = r.PollAnswerID
WHERE a.PollID = @PollID

GROUP BY a.PollAnswerID, a.PollID, a.DisplayText, a.SortOrder


ORDER BY a.SortOrder

For more information on aggregate functions in SQL (like COUNT) and using the GROUP BY clause, see Using the Group By Clause.

Specifying the Poll to Display


Since our poll database could include numerous polls, we need a way to let the page developer specify what poll to display in the polling User Control. While we could just show the most-recently added poll or use some sort of date scheme, this would prohibit scenarios where we might want to show several different polls on a website. For maximum flexibility, my polling User Control requires that the developer specify the poll to display. Since each poll is uniquely identified in the database via its PollID value, this is an ideal way to specify what poll to show in the User Control.

I added a public property called PollID to the User Control and had its value persisted in view state (so that it would be remembered across postbacks if it was assigned programmatically):

public int PollID
{
   get
   {
      if (ViewState["PollID"] == null)
         return -1;
      else
         return (int)ViewState["PollID"];
   }
   set { ViewState["PollID"] = value; }
}

Each of the SqlDataSources in User Control include a @PollID value that is used in the SelectCommand to return poll details or answers for the specified poll. In order to have the User Control's PollID property value used as these parameter values, I needed to create event handlers for the SqlDataSource controls' Selecting events and set the parameter value using code like so:

e.Command.Parameters["@PollID"].Value = PollID;

Inserting a Record Into UserResponses When a Vote is Cast


As mentioned earlier, the SqlDataSource used by the voting interface includes an InsertCommand that, when executed, will add a row to the UserResponses table. In order for this insert to occur, we need to call the SqlDataSource's Insert() method; we want to do this when the "Vote" Button is clicked. To accomplish this I created an event handler for the Button's Click event using the following code:

SqlDataSource answersDataSource = PollFormView.FindControl("PollAnswersDataSource") as SqlDataSource;
answersDataSource.Insert();

PollFormView.DataBind();

Since the PollAnswersDataSource SqlDataSource control is within the FormView's ItemTemplate, we have to use the FindControl("controlID") method to obtain a reference to the control. Once we have that reference we can simply call the Insert() method. After doing so, we want to update the display so that the user sees the read-only interface (since they just voted). Moreover, we want to update the read-only interface to include the user's vote (as well as any votes that may have occurred between the time the user loaded up the page and submitted her vote). To accomplish this, all we need to do is have the FormView rebind its data. This will requery the database and reevaluate whether the user should see the voting or read-only interface - hence the call to the FormView's DataBind method is all that is needed!

Displaying the Poll Results in the Read-Only Interface


Displaying the list of poll answers in the voting interface is simple: the RadioButtonList renders the appropriate interface for use based on the records returned from the PollAnswersDataSource SqlDataSource. The read-only interface is a bit more challenging. The PollResultsDataSource SqlDataSource returns a record for each answer in the poll along with the total number of votes made for that answer. However, in order to display the percentage of votes each answer has received, we need to know the total number of votes made for this poll. This is accomplished by programmatically connecting to the database and running the following query:

SELECT COUNT(*)
FROM UserResponses r
   INNER JOIN PollAnswers a
      ON r.PollAnswerID = a.PollAnswerID
WHERE a.PollID = @PollID

Once we have this information we can properly display the percentage next to each answer, since it's merely the number of votes for that answer divided by the total number of votes. We can make this calculation in the DataList's ItemDataBound event handler, which is raised as each item in the DataList is bound to data. For this to all work, it is imperative that we have the total number of votes before the ItemDataBound event fires for the first time.

This is accomplished by putting the above SQL query and associated logic in an event handler for the DataList's DataBinding event. The DataBinding event fires before the ItemDataBound events. Here is the code used in the DataBinding event handler:

// Calculate the total # of votes
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["PollConnectionString"].ConnectionString))
{
   conn.Open();
   SqlCommand cmd = new SqlCommand("SELECT COUNT(*) FROM UserResponses r INNER JOIN PollAnswers a ON r.PollAnswerID = a.PollAnswerID WHERE a.PollID = @PollID", conn);
   cmd.Parameters.Add(new SqlParameter("@PollID", PollID));
   totalVotes = (int)cmd.ExecuteScalar();
   conn.Close();
}

// Display the # of votes
Label TotalVotesLabel = PollFormView.FindControl("TotalVotesLabel") as Label;
TotalVotesLabel.Text = string.Format("{0:d} votes...", totalVotes);

The total number of votes is calculated and stored in the page variable totalVotes. TotalVotesLabel is a Label in the read-only interface that shows the total number of votes.

In the ItemDataBound event handler each answer's total number of votes is retrieved and the percentage is calculated. The percentage is then displayed both textually (through the PercentageLabel Label) and graphically by specifying the width of the PercentageImage image control as the percentage of votes. This image width setting stretches (or shrinks) the line image, which can be found at ~/Images/bar.jpg.

// Determine how many votes were made for this answer...
int votes = (int)DataBinder.Eval(e.Item.DataItem, "Votes");

// Programmatically access the Label & Image controls...
Label percentLabel = (Label)e.Item.FindControl("PercentageLabel");
Image percentImage = (Image)e.Item.FindControl("PercentageImage");

// Calculate the percentage...
if (totalVotes > 0)
{
   double pct = (Convert.ToDouble(votes) / Convert.ToDouble(totalVotes)) * Convert.ToDouble(100);
   percentLabel.Text = pct.ToString("0.0") + "%";
   percentImage.Width = Unit.Percentage(pct);
}
else
{
   percentLabel.Text = "0%";
   percentImage.Visible = false;
}

The net result is that each answers total number of votes and percentage are shown, as the following screen shot illustrates:

Determining When to Show the Read-Only Interface vs. the Voting Interface


The last piece of the puzzle here is determining when the read-only interface should be shown versus when the voting interface should be shown. The polling User Control should automatically be able to make this determination without requiring any code or property settings from the web page that contains the User Control. As discussed in Design Decisions and the Data Model, only authenticated users who have not yet taken the poll should see the voting interface; all other visitors will see the read-only interface.

When designing this control I wanted to make it easy for a reader to change this poll display logic. Therefore, I put the logic in a User Control method named CanUserTakePoll(), which returns a Boolean value indicating whether the current visitor can or cannot take the poll. If you want to alter the poll taking criteria, all you need to do is update this method with your own custom logic. (Note: you will likely also need to add additional logic when the vote is cast, somehow noting that the current visitor has just made a vote so that she can see the results and to reduce the likelihood of ballot stuffing.)

My implementation of CanUserTakePoll() returns False if the visitor is anonymous or if the user has already taken the poll:

private bool CanUserTakePoll()
{
   // Anonymous visitors cannot take poll
   if (!Request.IsAuthenticated)
      return false;

   // Determine if this user has already taken this poll... if so, she cannot retake it.
   MembershipUser currentUser = Membership.GetUser();
   if (currentUser != null)
   {
      Guid userID = (Guid)currentUser.ProviderUserKey;
      bool hasUserTakenPoll = false;

      using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["PollConnectionString"].ConnectionString))
      {
         conn.Open();
         SqlCommand cmd = new SqlCommand("SELECT COUNT(*) FROM UserResponses r INNER JOIN PollAnswers a ON r.PollAnswerID = a.PollAnswerID WHERE a.PollID = @PollID AND r.UserID = @UserID", conn);
         cmd.Parameters.Add(new SqlParameter("@PollID", PollID));
         cmd.Parameters.Add(new SqlParameter("@UserID", userID));

         hasUserTakenPoll = ((int)cmd.ExecuteScalar()) > 0;
         conn.Close();
      }

      return hasUserTakenPoll == false;
   }

   return false;
}

The CanUserTakePoll() method is called from the FormView's DataBound event handler, which uses this value to determine what Panel will be visible.

// Determine if the user can take the poll
bool showResults = !CanUserTakePoll();

// Show/hide the Panels based on the value of showResults
Panel takePollPanel = PollFormView.FindControl("pnlTakePoll") as Panel;
if (takePollPanel != null)
   takePollPanel.Visible = !showResults;

Panel pollResultsPanel = PollFormView.FindControl("pnlPollResults") as Panel;
if (pollResultsPanel != null)
   pollResultsPanel.Visible = showResults;

Using the Polling User Control in an ASP.NET Web Page


The download available at the end of this article provides a working demo of the polling User Control and administration pages. You can practice with adding the polling User Control to a new page via this demo. Start by adding a new web page. Next, drag the Poll.ascx User Control from the Solution Explorer onto the page's Design surface. This will add the User Control and the appropriate @Register directive to the page.

All that remains at this point is to specify the poll to display. This can be done declaratively by setting the PollID in the Properties window. Alternatively, this property can be set programmatically. The Default.aspx page in the demo application shows how to set PollID programmatically. It includes a DropDownList that lists all of the polls in the database. Upon selecting a new poll, the User Control's PollID value is set and the polling User Control is updated. If the user is not logged in or if they've already taken the poll then they'll see the read-only interface. If they are signed in and have yet to take that poll, its voting interface will be displayed.

Looking Forward...


At this point we have looked at the design goals of the polling User Control, its data model, and how the control was constructed. And, at this point, all of the essential functionality is complete. However, it would be nice to add web-based administration pages to enable admins to add or edit polls and view the results of existing polls. I have created such a set of web pages; they are the topic for the third and final installment!

Until then... Happy Programming!

  • By Scott Mitchell


    Attachments


  • Download the Polling User Control and demo application (ZIP format)
  • Article Information
    Article Title: ASP.NET.Creating an ASP.NET 2.0 Polling User Control: Building the Polling User Control
    Article Author: Scott Mitchell
    Published Date: August 29, 2007
    Article URL: http://www.4GuysFromRolla.com/articles/082907-1.aspx


    Copyright 2017 QuinStreet Inc. All Rights Reserved.
    Legal Notices, Licensing, Permissions, Privacy Policy.
    Advertise | Newsletters | E-mail Offers