Total votes: 0
Print: Print Article
Please login to rate or to leave a comment.
Published: 25 Mar 2011
Download Sample Code
This article shows how to build an up or down voting system using ASP.NET and jQuery.
The Internet is awash in user-created content. Two billion videos are watched on YouTube every day. Over 3,000 images are uploaded to Flickr each minute.
And there are an estimated 152 million bloggers creating content each day. Search engines work hard to discover and assign a rating to each of these pieces
of user-created content so that they can provide optimal search results. But rating user-created content is not just for search engines – it's also useful
for websites that accept user-submitted content.
Consider a question and answer website like Stackoverflow, whose content is the millions of questions asked and answered by users visiting the site. Really interesting questions and
answers should be marked as such; questions or answers that are offensive or off-topic should be similarly noted. By rating the questions and their answers,
a user visiting the site can quickly see what questions are of interest and what answers should receive the focus of their attention. So how does
Stackoverflow go about rating its millions of questions and answers? Given the volume of questions and answers it would be impractical to assign this task to
employees. Instead, Stackoverflow turns to its community, allowing users to rate questions and answers as they see fit.
Stackoverflow lets its users
rate questions and answers using an up or down voting system. With this system, for each question and for each answer a Stackoverflow user can either:
abstain from voting (0); cast an up vote (+1); or cast a down vote (-1). The cumulative vote score – the sum of all up votes less the negative votes – is
displayed next to each question and answer, providing a quick indication of the quality of a partiuclar question or answer.
This article shows how to
build an up or down voting system using ASP.NET and jQuery. We'll build a website that allows users to share their favorite online articles. The website's
homepage lists the 25 most recently shared articles, their current vote score, and arrows for casting an up or down vote.
Download the Demo!
This article's download includes two working demos – an ASP.NET WebForms website and an ASP.NET MVC application. However,
any code presented in this article is from the WebForms demo.
I suggest downloading the demo and having it open in Visual Studio as you read through
Creating the Needed Database Tables
Any website that lets users rate content must have, at minimum, three associated tables: one that contains a record for each user in the system; one that
contains a record for each content item; and one that records how a particular user has rated a particular content item.
For our website let's use
ASP.NET's Membership system to manage user accounts. The demo application
have been configured to support forms authentication and to use the
SqlMembershipProvider provider; if you are following along at your
computer, refer to my tutorial Creating the
Membership Schema in SQL Server for instructions on how to implement this configuration. The
SqlMembershipProvider provider stores user
account information in the
aspnet_Users database table; each user is uniquely identified by their
UserId, which is of type
Articles table contains a record for each user-submitted, recommended article, and has the following
Table 1: The Articles table
IDENTITY column; is the primary
The recommended article's title.
The recommended article's URL.
Stores the article's cumulative vote score.
The date and time the article was
Votes table records each user's votes.
Votes establishes a many-to-many join between
Articles, since each user can vote for any number of articles and each article can have votes from any number of users. The
table has the following columns:
Table 2: The Votes table
A composite primary key constraint exists on the
Stores the user's vote for this article and must be a
value of either +1 or -1. There is a
CHECK CONSTRAINT in place to enforce this business rule.
The date and time the vote was cast.
Figure 1 shows these three tables and their relationships.
Figure 1: An up and down voting user interface requires at least three tables in the data model.
Calculating an Article's Cumulative Vote Score
VoteScore column reports the cumulative votes for each article. However, it is redundant as an article's
cumulative vote score can be computed by running a subquery on the
Votes table like so:
Listing 1: Query to compute an article's cumulative vote score.
The reason I decided to denormalize the
Articles table by adding the
VoteScore field was because an article's vote
score is needed very often, namely anytime the list of articles is displayed on the homepage. By storing the vote score in
Articles I save the
database from needing to recompute it each time someone visits the homepage. Admittedly, this is an example of an anti-pattern – premature
The downside of storing the cumulative vote score in the
Articles table (aside from being a premature optimization) is that
the stored cumulative vote score could become out of sync with the actual vote score. To ensure that the stored vote score reflects the article's true vote
score we need to make sure to update the
VoteScore field whenever a vote is cast.
The database includes a
stored procedure named
RecalculateArticleVoteScore that updates (and returns) a particular article's cumulative vote score.
RecalculateArticleVoteScore is invoked by the website whenever a user's vote is logged.
Listing 2: The RecalculateArticleVoteScore stored procedure recalculates an article's cumulative vote score.
Accessing the Database from the ASP.NET Application
The demo applications available for download share a common Class Library project named Models. The Models library contains a LINQ to SQL file named
Articles.dbml that defines the classes that model the three database tables we examined eariler.
The Models library also contains a class
ArticleRepository.cs, which has three methods:
AddArticle(article) – this method is passed an
Article object, which is added to the database. This method is called
SubmitArticle.aspx page when a user submits a recommended article.
GetRecentArticles(userId, page, pageSize) – returns the requested page of recent articles. Page is a zero-based value that
indicates the page of data to return, whereas pageSize is the number of records per page. For example, passing in values of 0 and 10 for page and pageSize
returns the most recent 10 articles. Setting page to 1 would return the 11th through 20th most recent articles. Page and pageSize are optional parameters; if
omitted, they default to values of 0 and 25, respectively.
- If userId is not
null then the data returned from this method includes how the specified user voted on each of the articles. This
information is used when displaying the list of articles to show the logged on user what articles she's voted for and whether she's voted up or down.
Vote(articleId, voteValue, userId) – records the voteValue made by userId for articleId and returns the article's new cumulative
The demo applications interface with the Models library's
ArticleRepository class rather than communicating directly with the LINQ to
Article class created by the LINQ to SQL OR/M does not include a property to track the currently
logged on user's voting record. To capture this information I created a new class named
ArticleWithVotingInfo that has properties that specify
article-related information –
VoteScore – as well as
a property that indicates how the currently logged on user voted for the article in question –
Figure 2: The ArticleWithVotingInfo class diagram.
UsersVoteValue property is a nullable integer. When the articles
are being displayed for an anonymous user, or if the logged in user has not voted for a particular article, then
UsersVoteValue will be
null. However, if the articles are being viewed by a logged in and that user has voted for a particular article then that article's
UsersVoteValue property reports the user's vote – either a +1 or -1.
method returns an enumeration of
Displaying the List of Recommended Articles
The list of recommended articles are displayed in the website's homepage,
Default.aspx. A ListView control renders a two-column
<table> with a row for each recommended article. The left column of each row displays the voting user interface and the article's
cumulative vote score. The right column shows the details about the article, including its title and the date and time it was submitted. Figure 3 shows the
output of the ListView when viewed through a browser.
Figure 3: The list of the most recent recommended articles.
Before we examine the ListView's markup, let's first look at the
code used to populate the ListView. As noted earlier, the ArticleRepository's
GetRecentArticles method is used to get the list of recent
articles. In the
Page_Load event handler we bind the results of the
GetRecentArticles method to the ListView.
Listing 3: The results of the GetRecentArticles method are bound to the ListView.
Note that if an anonymous user is visiting the page then we pass in a
null value for the
first input parameter, userId. However, if the user visiting is authenticated then we retrieve their
UserId from the Membership system and pass
this value in. Also note that the user interface in this demo application is not setup to allow the user to page through the most recent articles to see
older entries. The call to the
GetRecentArticles method does not specify the page or pageSize attributes, meaning that this method call will
always return the 25 most recent articles. With a little bit of effort, however, you could add a paging user interface, allowing users to view older
recommended articles. For more information along this vein, see The Ultimate
DataPager Interface if you are using WebForms and Displaying a Paged Grid
of Data in ASP.NET MVC if you are using MVC.
The markup for the ListView follows. The
LayoutTemplate starts by rendering a
<table> tag. Then, for each
ArticleWithVotingInfo object returned by the
GetRecentArticles method a table row
is rendered. The left column renders the voting user interface while the right column renders information about the article.
Listing 4: The ListView renders a table row for each recommended article.
Let's take a deeper look at the voting user interface column. First, note the
<div> element, which displays the cumulative
vote score for the article. In Figure 3 this is the number positioned between the up and down arrows. For example, the Customizing ELMAH's Error Emails
article has a cumulative vote score of 3.
The up and down arrows are rendered by the
methods, which are defined in the code-behind class. These methods emit a
<div> element with the following
The articleId value reports the article's
For every voting arrow, the
class attribute value specifies the
voteArrow class. Furthermore, every up voting arrow specifies the
whereas every down voting arrow specifies the
down class. If the user viewing the page has already voted for an article then either the
downvoted class is also specified.
downvoted classes each define a different background image:
up – displays a black up arrow.
upvoted – displays a green up arrow, indicating that the user has up voted this article.
down – displays a black down arrow.
downvoted – displays a red down arrow, indicating that the user has down voted this article.
The CSS rules for these four classes is displayed below; in the demo, this CSS can be found in the
Listing 5: The CSS rules for the up, down, upvoted, and downvoted classes
Improving Performance with CSS Sprites
downvoted classes in
the demo application each define their own background image, which means when a user makes an up vote or down vote for the first time the associated image
needs to be downloaded by the browser. Moreover, when loading a page with both up votes and down votes the browser must download four separate
A more elegant and better performing solution is to use CSS sprites. With CSS sprites all four images are stored in one image and CSS rules are
used to selectively display a portion of that master image. Such an approach requires only one image to be downloaded from the server.
information on implementing CSS sprites in an ASP.NET application, see Optimize
Images Using the ASP.NET Sprit and Image Optimization Framework.
Figure 3 shows all four of these arrows in use. For instance, the How to add a custom dictionary in Word recommended article displays both black up
and down arrows, indicating that the logged on user has not yet cast his vote for this article. The markup for these up and down voting arrows follows.
In Figure 3, the up arrow for Customizing ELMAH's Error Emails is green, indicating that this user cast an up vote. The markup for this
article's up and down arrows follows. Note that regardless of whether an article has been voted on, the
down classes are
always present. If a vote has been cast, then the
downvoted) class is also included.
As you may have surmised, the
downvoted classes are specified depending on the value of the
UsersVoteValue property for the article in question. If
UsersVoteValue equals +1 then the
upvoted class is included
in the up arrow
<div> element, otherwise it is omitted. Similarly, if
UsersVoteValue equals -1 then the
class is included in the down arrow
<div> element, otherwise it is omitted.
Whenever an authenticated user clicks one of
to cast – the user may be voting up or down for the first time or they may be changing or recalling their vote. The browser then needs to send the user's
vote to the web server so that it can be recorded. The next two sections look at implementing this functionality.
Casting a Vote When a Vote Arrow Is Clicked
Whenever a user clicks one of the voting arrows we need to record her vote. At first blush, this may seem straightforward – if the user clicks the up
arrow then we record a vote of +1, but if she clicks the down arrow then we record a vote of -1. While this is true when a user votes for a particular
article for the first time, things are a slightly more complex when a user wishes to change or recall her vote on a previously voted-on article.
Figure 3, the user has downvoted the "Learn all about ASP.NET!" article as evidenced by the red down arrow. The user may decide to change her vote by
clicking the up arrow. At that point we need to update her previously recorded vote from a -1 to a +1. Similarly, she may decide to recall her downvote,
which she can do by clicking the down arrow. In this case we want to delete her previously recorded vote.
After a user has voted we also need to update
the article's cumulative vote score. For instance, if an article has a cumulative vote score of 6 and a user up votes it, the score should be updated to the
new cumulative score. This new score may be 7, but it may some other number if others have also voted on this article in between the time the user loaded the
page and placed her vote.
This logic – determining the user's vote, submitting the vote to the server, and updating the cumulative vote score – is
handled via client-side script using the jQuery library. The following script defines a client-side event
handler for the voting arrows on the page. Note that this event handler is executed whenever any voting arrow on the page is clicked. That is, the same code
is executed whether an up arrow or down arrow is clicked or whether the vote is cast for the first article, the second, the third, et cetera.
Voting and Anonymous Visitors
The script presented in this article is the script that is emitted for authenticated users. Anonymous users
cannot vote. If an anonymous user visits the site, the ASP.NET page emits different script for the voting arrow click event handler, namely script that
displays a modal popup explaining that the user needs to log in before they can vote.
In particular, anytime a vote arrow is clicked the
vote function is executed and passed a reference to the particular element that was
vote function resides in a separate file,
Listing 6: The vote function is called whenever a voting arrow is clicked.
vote function, shown below, starts by reading in the clicked voting arrow's
articleId attribute, which is
needed when submitting the vote to the server. Next, we need to determine whether the arrow that was just clicked was an up or down arrow – if the arrow
specifies the CSS class
up then an up arrow was clicked, otherwise a down arrow.
If a user has already down voted an article and then
decides to change his vote to an up vote, we need to both add the
upvoted CSS class to the up arrow and remove the
class from the down arrow. For this reason, we need to have a reference to the other vote arrow. That is, if the user clicked the up arrow we also need a
reference to the down arrow; if the down arrow was clicked, we need a reference to the up arrow. This other arrow reference is stored in the
We also need a reference to the
<div> element that displays the cumulative votes for the
article so that we can update the article's vote score after the vote is cast. This reference is stored in the
Once the articleId attribute has been read and we've determined whether the user clicked the up or down arrow and we have references to the
<div> elements and to the other vote arrow, we're ready to update the vote arrow CSS classes to reflect the user's vote.
For example, if the user just clicked the up arrow then we want to toggle its
upvoted CSS class. Toggling adds the
class if it doesn't exist, which is the case when up voting an article that either had not been voted on or had been down voted. Toggling removes the
upvoted class if it already exists, which implies that the user had already up voted this article. Removing the
returns the arrow back to its default, non-voted state (a black up arrow as opposed to a green one). In addition to toggling the
we also remove the
downvoted class (if it exists) from the down arrow. This ensures that if the user had down voted the article and then chose
to up vote it, we both add the
upvoted class to the up arrow and remove the
downvoted class from the bottom arrow.
point, the user interface has been updated to reflect the user's vote, but we still haven't sent the vote to the server. Before we can do this we need to
determine the vote value to send to the server. Did the user up vote this article (+1), down vote it (-1), or do they want to recall their vote (0)?
The vote is cast by making an asychronous HTTP request to a vote service on the web server. In the WebForms demo this is handled by the
Vote.ashx HTTP Handler, which we examine in the next section. In the MVC demo this is handled by the VoteController's
action. The server-side service returns a JSON payload that defines an object with a property named
NewVoteScore, which is the new cumulative vote score for the article. This value is used to update the article's total score displayed in the
Listing 7: The vote function updates the user interface and submits the vote to the server-side service.
Saving a User's Vote to the Database
When a user clicks a voting arrow an HTTP request is sent to server-side voting service, passing along the
articleId and the
values. The user's identity is also passed to the server via the forms authentication ticket cookie. With these three pieces of information we can
add, update, or delete the user's vote.
The following code, taken from the
Vote.ashx HTTP Handler, starts by reading in the
articleId and the
voteValue values from the querystring and the
UserId value of the currently logged in user. Next,
it calls the
Vote method, which records the vote in the database and returns the new cumulative vote
score for the article in question.
The article's cumulative vote score is returned as a JSON payload to the client. Specifically, an object with a
single property –
NewVoteScore – is serialized using Microsoft's
Listing 8: The server-side voting service records the vote and returns the article's new cumulative vote score as JSON.
Vote method is fairly straightforward. It starts by retrieving the current voting
record for this article and user. If no such record exists then a new vote is recorded. However, if there is a recorded vote than it is either deleted or
updated, depending on the vote value: if the user is recalling their vote then the vote is deleted; if they are chainging it from an up vote to a down vote,
or vice versa, then the existing vote is updated.
After logging the vote the
RecalculateArticleVoteScore stored procedure is called. This
recomputes the article's cumulative vote score, updating the
VoteScore column. The new cumulative vote score is
also returned from the stored procedure, which is then returned from the
Listing 9: The ArticleRespository class's Vote method records the user's vote.
For more information on using jQuery to make asynchronous HTTP requests from the client to the server, and for options on how to create
server-side services that accept such requests and return data back to the client, see my article series, Accessing Server-Side Data from Client Script.
Ranking content and separating the signal from the noise is an important aspect for any website that accepts user-generated content. While you or an
editorial team can rank content on your own, such a policy does not scale. Instead, you'll need to turn to your users. An up and down voting system is a
simple and intuitive mechanism for letting the community rank content on your website.
This article walked through building an up and down voting
web server and updates the user interface to show their cast vote and to update the article's cumulative vote score.
Scott Mitchell, author of eight ASP/ASP.NET books and founder of 4GuysFromRolla.com, has been working with Microsoft Web technologies since 1998. Scott works as an independent consultant, trainer, ...
This author has published 16 articles on DotNetSlackers. View other articles or the complete profile here.
Please login to rate or to leave a comment.