Status 2 - When a visitor has logged in (is a member of the website)
Status 3 - After rating the post
Status 4 - When a visitor had rated the post and coming back to the same post again and browser cookie is still present in visitors' machine.
Status 5 - When a visitor had voted and browser cookies are cleared and he/she tried to vote again for the same post.
Lets start developing Star rating system
Step 1
Create a partial view named _VoteNow.cshtml in the Views/Shared folder and copy-paste below code.
@model string
@{
var url = Request.Url.AbsolutePath;
}
@if (!User.Identity.IsAuthenticated)
{
<text>Please <a href="/Account/Login?ReturnUrl=@Request.Url.AbsolutePath" title="Login to rate">Login</a> to rate</text>
return;
}
@if (Request.Cookies[url] == null) {
<div id="ratingDiv" class="smallText"> Poor
<img src="/images/whitestar.gif" alt="" class="ratingStar" data-value="1" /><img src="/images/whitestar.gif" alt="" class="ratingStar" data-value="2" /><img src="/images/whitestar.gif" alt="" class="ratingStar" data-value="3" /><img src="/images/whitestar.gif" alt="" class="ratingStar" data-value="4" /><img src="/images/whitestar.gif" alt="" class="ratingStar" data-value="5" /> Excellent
<label id="lblResult"></label>
</div>
<style type="text/css">
.ratingStar {
cursor:pointer;
}
</style>
<script type="text/javascript">
var clickedFlag = false;
$(".ratingStar").mouseover(function () {
$(this).attr("src", "/images/yellowstar.gif").prevAll("img.ratingStar").attr("src", "/images/yellowstar.gif");
});
$(".ratingStar, #radingDiv").mouseout(function () {
$(this).attr("src", "/images/whitestar.gif");
});
$("#ratingDiv").mouseout(function () {
if (!clickedFlag)
{
$(".ratingStar").attr("src", "/images/whitestar.gif");
}
});
$(".ratingStar").click(function () {
clickedFlag = true;
$(".ratingStar").unbind("mouseout mouseover click").css("cursor", "default");
var url = "/Home/SendRating?r=" + $(this).attr("data-value") + "&s=5&id=@Model&url=@url";
$.post(url, null, function (data) {
$("#lblResult").html(data);
});
});
$("#lblResult").ajaxStart(function () {
$("#lblResult").html("Processing ....");
});
$("#lblResult").ajaxError(function () {
$("#lblResult").html("<br />Error occured.");
});
</script>
}else{
<text><span style="color:green;">Thanks for your vote !</span></text>
}
In the above code snippet, we are doing following
- Getting current browser url
ApplicationPath
(path without domain name) that will be used to send to the server to create unique cookie for this post
- Checking whether User is authenticated, if not showing the Login link (refere to Status 1 above).
- Checking if user has already voted, If yes, go to else block and write Thanks message (refer to Status 4). If not, show the rating control (Status 2) that contains following
- 5 white images
- a html lable element
- a .ratingStar class
- lines of JavaScript code
- mouseover() - When a visitor mouse over white stars, the same gets replaced with yellow star with the help of
.ratingStar
css class. Notice the .prevAll
function of jQuery that is intelligent enough to find all previous elements with same .css class and change its src
value. - mouseout()
- When user keeps his mouse out of image, we are keeping the white images back
- If user has already rated and then keeping mouse out of image then we are NOT replacing yellow image to white. This is being checked with the help of
clickedFlag
variable.
- click() When user click on the star images
clickedFlag
will be set to true and mouseover, mouseout, click
events will be unbounded from the img
element.
jQuery.post method is used - to send the data-value of the start rating image
- section for which this post belongs to
- the unique id of this post and
- the url (ApplicationPath) of this post.
All above data is being sent to /Home/SendRating action under HomeController. You may change the name of controller and action method if that suits you better.
Once successful, the data coming from server is being written to the label
element.
- .ajaxStart() When user clicks on the star images and ajax post event occurs, we are writing "Processing ..." status to notify user that his rating is being processed.
- .ajaxError() When an error occurs, error message is written in the label. The error message can be can be changed.
Step 2
Create a SendRating
action method in HomeController
.
public JsonResult SendRating(string r, string s, string id, string url)
{
int autoId = 0;
Int16 thisVote = 0;
Int16 sectionId = 0;
Int16.TryParse(s, out sectionId);
Int16.TryParse(r, out thisVote);
int.TryParse(id, out autoId);
if (!User.Identity.IsAuthenticated)
{
return Json("Not authenticated!");
}
if (autoId.Equals(0))
{
return Json("Sorry, record to vote doesn't exists");
}
switch (s)
{
case "5" : // school voting
// check if he has already voted
var isIt = db.VoteModels.Where(v => v.SectionId == sectionId &&
v.UserName.Equals(User.Identity.Name, StringComparison.CurrentCultureIgnoreCase) && v.VoteForId == autoId).FirstOrDefault();
if (isIt != null)
{
// keep the school voting flag to stop voting by this member
HttpCookie cookie = new HttpCookie(url, "true");
Response.Cookies.Add(cookie);
return Json("<br />You have already rated this post, thanks !");
}
var sch = db.SchoolModels.Where(sc => sc.AutoId == autoId).FirstOrDefault();
if (sch != null)
{
object obj = sch.Votes;
string updatedVotes = string.Empty;
string[] votes = null;
if (obj != null && obj.ToString().Length > 0)
{
string currentVotes = obj.ToString(); // votes pattern will be 0,0,0,0,0
votes = currentVotes.Split(',');
// if proper vote data is there in the database
if (votes.Length.Equals(5))
{
// get the current number of vote count of the selected vote, always say -1 than the current vote in the array
int currentNumberOfVote = int.Parse(votes[thisVote - 1]);
// increase 1 for this vote
currentNumberOfVote++;
// set the updated value into the selected votes
votes[thisVote - 1] = currentNumberOfVote.ToString();
}
else
{
votes = new string[] { "0", "0", "0", "0", "0" };
votes[thisVote - 1] = "1";
}
}
else
{
votes = new string[] { "0", "0", "0", "0", "0" };
votes[thisVote - 1] = "1";
}
// concatenate all arrays now
foreach (string ss in votes)
{
updatedVotes += ss + ",";
}
updatedVotes = updatedVotes.Substring(0, updatedVotes.Length - 1);
db.Entry(sch).State = EntityState.Modified;
sch.Votes = updatedVotes;
db.SaveChanges();
VoteModel vm = new VoteModel()
{
Active = true,
SectionId = Int16.Parse(s),
UserName = User.Identity.Name,
Vote = thisVote,
VoteForId = autoId
};
db.VoteModels.Add(vm);
db.SaveChanges();
// keep the school voting flag to stop voting by this member
HttpCookie cookie = new HttpCookie(url, "true");
Response.Cookies.Add(cookie);
}
break;
default:
break;
}
return Json("<br />You rated " + r + " star(s), thanks !");
}
In the above code, we are doing following
- retrieving all the data (like rate given, section, unique post id and url) coming in to this method from the _VoteNow partial view.
- Doing some basic validation, liked if a user is logged in or not and whether unique post id (autoid) exists
- Using switch case based on the sectionId, If we do not have different sections in the website, we can write following code without switch case block.
- In the switch case block, we are doing following
- Checking if this user has already voted, if yes we are returning already voted message after creating cookies based on the url so that next time user comes the rating control will not display but Thanks message appears.
- Getting the record for which this rate was given and retrieving the current Votes (Rates). The rates are being stored in this field as "0,0,0,0,0" format.
- Where first value is the count of how many users rated 1
- second value is the count of how many users rated 2
- third value is the count of how many users rated 3 and so on
- Based on what rate has been give, the corresponding value is increased by one. If not, probably, this post is being rated first time so default value is being set and corresponding rate count is increased to 1.
- Again, we are looping through the array of current votes and updating into the Votes field back.
- Instantiating the VoteModel (VoteLog table) and saving corresponding data into the database. In this scenario, this table is being used only to check if a particular user has already rated however this table can also be used to count how active a user is in voting or to give point or appreciation for a particular user etc.
- We are creating a cookie in the browser based on the url so that next time when this user visits this page, he gets thanks message but not rating option.
- At last we are returning a Json result of how much rate was given by this user and thanks !
Step 3
Displaying current rating.
Till now, we have developed enough infrastructure for user to rate a post. Now its time to display the ratings. Create _VoteShow.cshtml partial view in the Views/Shared folder and copy-paste below code.
@model string
@{
Single m_Average = 0;
Single m_totalNumberOfVotes = 0;
Single m_totalVoteCount = 0;
Single m_currentVotesCount = 0;
Single m_inPercent = 0;
var thisVote = string.Empty;
if (Model.Length > 0)
{
// calculate total votes now
string[] votes = Model.Split(',');
for (int i = 0; i < votes.Length; i++)
{
m_currentVotesCount = int.Parse(votes[i]);
m_totalNumberOfVotes = m_totalNumberOfVotes + m_currentVotesCount;
m_totalVoteCount = m_totalVoteCount + (m_currentVotesCount * (i + 1));
}
m_Average = m_totalVoteCount / m_totalNumberOfVotes;
m_inPercent = (m_Average * 100) / 5;
thisVote = "<span style=\"display: block; width: 65px; height: 13px; background: url(/images/starRating.png) 0 0;\">" +
"<span style=\"display: block; width: "+ m_inPercent + "%; height: 13px; background: url(/images/starRating.png) 0 -13px;\"></span> " +
"</span>" +
"<span class=\"smallText\">Overall ratings: <span itemprop=\"ratingCount\">" + m_totalNumberOfVotes + "</span> | Rating: <span itemprop=\"ratingValue\">" + m_Average.ToString("##.##") + "</span> out of 5 </span> ";
}
}
<div itemprop="aggregateRating" itemscope itemtype="http://schema.org/AggregateRating">
<meta itemprop="bestRating" content="5" />
<meta itemprop="worstRating" content="1">
<meta itemprop="ratingValue" content="@m_Average.ToString("##.##") %>" />
@Html.Raw(thisVote)
</div>
Calling rating controls (partial vies) on the post page.
<div class="tr" style="background-color:#f1f1f1;">
<div class="td0">Please rate this school</div>
<div class="td">@Html.Partial("_VoteNow", Model.AutoId.ToString())</div>
<div class="td">@Html.Partial("_VoteShow", Model.Votes)</div>
</div>
While calling _VoteNow, we are passing the unique post id as parameter and while calling _VoteShow, we are passing current votes (Rates) in the form of "x,x,x,x,x" as parameter and here is the final output displays on the web page.
Hurrah!!! our own rating system is up and live! Have a look at it
here.
Conclusion
In this article, we learnt how to create a star rating system in ASP.NET MVC. There are many plug-ins available however they may not suit specific needs of every project. Developing our own gives us more control over how and what we want to display.