Introduction
If you ever read an article on Microsoft's MSDN Web site, such as my
article An Extensive
Examination of User Controls, you'll notice that at the bottom of the article there's an interface for the reader
to rate the content. (See the screenshot to the right.) Specifically, the user can rate the quality of the article
on a scale from 1 to 9, with 1 being Poor, and 9 being Outstanding. In addition to affixing a numerical rating of
the article, the reader can also provide optional comments in a textbox. Also included is a graph showing how other
readers have rated the article. (CoDe Magazine's Web site uses a similar
content rating system.)
The other day I thought it would be neat to add such functionality to my blog, ScottOnWriting.NET.
Namely, I wanted to allow users to rate each blog entry, so that I could get a better idea of what content my visitors
found most compelling. Another benefit of allowing users to rank content, is that after sufficient data has been collected
you can provide users with a list of "most popular" content items.
In this article I will walk you through the steps I took to create the content rater used on my blog. You are more
than welcome to take my code and apply it to your ASP.NET Web site, or to extend it as needed.
First Things First: The Data Model
Each time a user rates a content item, the rating - along with any comments - are stored in a database (specifically, a Microsoft
SQL Server 2000 database). I opted to store
the ratings and comments in a database (as opposed to an XML file, for example), for one main reason: extracting metrics on
the data is a cinch when using SQL. For example, a simple, single SQL query can be used to quickly determine the average rating
for a particular content item. A more involved query could be used to determine what authors wrote the highest rated
content, or what days of the week the least popular items were published.
To store the ratings for my blog's entries, I created a single table, blog_Ratings, where each row in this
table would correspond to one rating from a user. The blog_Ratings table has the following structure:
Field Name
Datatype
RatingID
int, PK, IDENTITY
Rating
tinyint
Comments
nvarchar(3000)
ContentID
int, FK
DateAdded
datetime
The Rating field contains the numeric rating the user assigned to the content item, while
Comments stores the comments provided (if any). ContentID is a foreign key that relates the
rating back to a particular content item. (For my blog I use the .Text blog engine,
which stores each blog entry as a row in a table called blog_Content. This foreign-key ties a rating to
the particular blog entry being rated.) Finally, the DateAdded field specifies the date/time the rating
was made (with a default value of getdate()).
Creating the User Interface
With the data model complete, my next step was to create the user interface for rating a content item. Rather than
display a ranking of 1 to 9 like MSDN does, I decided to simplify my design and only allow for a rating from 1 to 5
instead. I started by creating a new User Control in my ASP.NET Web application, and crafted a nearly identical interface
to MSDN's content rater. The screenshot below shows my User Control in Visual Studio .NET and in action on my blog (you
can download the User Control at the end of this article).
The User Control is contained within an HTML <table>, dividing the interface into two halves: a left half,
which displays the rating interface; and a right-half, which displays the average rating and a graph depicting the rating history.
The left-half contains two Panel Web controls. The first Panel, pnlCastVote, contains the HTML markup and
Web controls for rating a content item. The second Panel, pnlVoteCast, contains just a message thanking the
user for rating the content. If a particular user has not rated a particular content item, they are shown the pnlCastVote
Panel. If, however, they have already rated the content item, they are shown the pnlVoteCast Panel. (Cookies
are used to remember what content items the user has rated.)
Specifying the Content Being Rated and Collecting the User's Rating
The User Control code-behind class contains a ContentID property that indicates the ID of the content
being rated. That is, to use the content rater in an ASP.NET Web page, you'd include the User Control and then in the
ASP.NET Web page's code-behind class you'd set the User Control's ContentID property to current content item's
ID value. It is essential that this property be set in the ASP.NET Web page (such as in the Page_Load event
handler), because the User Control must know what content item it is rating.
To rate a content item, the user selects one of the radio buttons and clicks the Rate Content button. This causes a postback
and runs an event handler wired up to the Rate Content button's Click event. The event handler code
checks to determine what radio button was selected, marks the user as having voted for the content item, and then
calls the blog_AddRating stored procedure, passing in the user's rating, comments, and the ID for the content
being rated. (The blog_AddRating stored procedure simply does an INSERT into blog_Ratings.)
Remembering If a User Has Rated a Content Item
Since we don't want one user to be able to rate a single content item dozens of times,
after a user has rated a content item they are shown a message thanking them for their rating, as opposed to seeing
the rating interface. For further protection, a cookie is used to record what
content items the user has rated. If the user comes to a content item they've already rated, rather than seeing the
rating interface, they are shown a message thanking them for their rating.
Cookies are by no means a fail-safe method to ensure that each visitor only rates a content item once. For one thing,
users can configure their browsers to not accept cookies. Also, a cookie is specific to a Web browser, so a single person
could use different browsers, or different computers, and record their rating more than once. The point is, the content
rater I created by no means guarantees the validity of the ratings. If you want to be certain that there is only one
rating per user, then you'd need to have a database of user accounts, and tie a rating to a particular user.
For the content rater I created, I created a Boolean HasVoted property in the User Control code-behind class that
can be used to determine if a user has already voted on a particular content item or not. The property's get
accessor checks to see if a cookie is in place, indicating that the user has rated this content item. The set
accessor adds the appropriate cookie. The code for the HasVoted property is shown below:
public bool HasVoted
{
get
{
// Return true if the cookie DOESN'T EXIST, false if it does
return (Request.Cookies["sowBlog"] != null &&
Request.Cookies["sowBlog"][String.Concat("ContentID-",
ContentID)] != null);
}
set
{
// Create the cookie
if (Request.Cookies["sowBlog"] == null)
Response.Cookies.Add(new HttpCookie("sowBlog"));
Response.Cookies["sowBlog"][String.Concat("ContentID-",
ContentID)] = "true";
Response.Cookies["sowBlog"].Expires = DateTime.Now.AddYears(1);
}
}
Note that the HasVoted property checks a cookie called sowBlog's value titled
ContentID-ID, where ID is the ID of the content item specified by the ASP.NET Web page
containing this User Control. (This property helps illustrate the importance of having the ASP.NET Web page
specify the User Control's ContentID property; the ContentID specifies what
cookie value to search for.)
Wrapping Up: Showing the Voting Statistics
The final piece of the puzzle is having the right-half of the rating interface display the total number of people
who have rated the content, along with a graph showing the vote breakdown. This is accomplished in the DisplayRateScreen()
method in the User Control, and is called from the User Control's Page_Load event handler. DisplayRateScreen()
calls the blog_GetRatingInfo stored procedure to get the information on how many votes for each rating there
were, along with the average rating and how many total ratings have been made. This stored procedure expects a
ContentID input parameter, specifying the content item for which to generate the stats, and has the following
syntax:
CREATE PROCEDURE blog_GetRatingInfo
(
@ContentID int
) AS
If Exists(SELECT 1 FROM blog_Ratings WHERE ContentID = @ContentID)
SELECT
SUM(CASE WHEN Rating = 1 THEN 1 ELSE 0 END) As Rating1Count,
SUM(CASE WHEN Rating = 2 THEN 1 ELSE 0 END) As Rating2Count,
SUM(CASE WHEN Rating = 3 THEN 1 ELSE 0 END) As Rating3Count,
SUM(CASE WHEN Rating = 4 THEN 1 ELSE 0 END) As Rating4Count,
SUM(CASE WHEN Rating = 5 THEN 1 ELSE 0 END) As Rating5Count,
AVG(CONVERT(float, Rating)) As AvgRating,
COUNT(1) As RatingCount
FROM
blog_Ratings
WHERE
ContentID = @ContentID
GROUP BY
ContentID
Else
SELECT
0 As Rating1Count,
0 As Rating2Count,
0 As Rating3Count,
0 As Rating4Count,
0 As Rating5Count,
0.0 As AvgRating,
0 As RatingCount
(The above stored procedure was provided by Richard Deeming...)
As you can see, the stored procedure returns one row with seven columns. The columns Rating1Count through
Rating5Count return the number of 1 through 5 ratings the content item received. AvgRating
returns the average rating, while RatingCount returns the total number of ratings recorded for this
content item.
The graph depicting the rating breakdown is generated with an HTML <table>, which one column for each
of the five possible ratings. Each column in the graph contains a single image, (bcImage.gif), which is
stretched to a height proportional to the number of ratings received. For example, if there are twice as many 5 ratings
for a content item than there are 4 ratings, the 5 rating column height will be twice that of the 4 column height.
The specific proportion is computed as the number of votes for that rating, divided by the maximum number of votes
for any rating, multiplied by 50 (to scale the maximum height to 50 pixels).
Learn About Adding Additional Features (April 30, 2004)...
On April 30, 2004, I authored a follow-up article: Improving the Content Rater.
The newer article examines how to add two features to the content rater: a means to show users the highest rated
items, and a page that shows all ratings and comments made, grouped by content item. Read on to learn more!
Conclusion
In this article we looked at a simple User Control to allow users to rate content on your Web site. The rating
User Control allows users to give a rating between 1 and 5, and displays an average rating along with a graph showing
the rating history for the content item. The User Control employs cookies to remember whether or not a particular user
has already rated this content item. While this method can easily be abused by a savvy user to rate a single content
item more than once, it was chosen because of ease of implementation and the fact that the content rating system need
not be infallible.
Once you have implemented this content rater on your site, you can add all sorts of other interesting features. For
example, you could have a page that listed the 10 most popular content items. You might also find it useful to
build an administrative Web page that listed the ratings for each content item, along with their comments, to give
site editors a quick pulse on their readers' preferences. If you think of any other nifty way to extend this
functionality, be sure to let me know!