Real-Time Poll Vote Results Using SignalR 2, MVC, Web API 2, jQuery And HighCharts
Download Source Code here.
Introduction
In our previous article, we’ve done setting up the core foundation of our Online Poll System app: starting from creating a database and the required tables from the scratch, up to displaying the poll in real-time, using ASP.NET SignalR. If you haven’t gone through the previous article, you can read it here:
Building A Real-Time Online Poll System With SignalR 2, jQuery, EF Core, Core MVC And Web API 2
In this particular series, we will take a look at how to implement a real-time voting result by presenting it in a form of a chart. We’ll be using SignalR 2, Web API 2, jQuery and HighCharts to implement it. Note, before you go down further; make sure that you have referred the previous article, so you will be able to connect the dots in the picture as we move along.
Let's Get Started!
If you’re ready, let’s go ahead and start cracking!
Adding the VoteResultViewModel
The first thing, we need to do is to add a new ViewModel. To do that, right-click on “Models/ViewModels” folder and then select Add > Class. Name the class as “VoteResultViewModel” and then copy the code, given below:
namespace ASPNETCoreSignalRDemo.Models.ViewModels
{
public class VoteResultViewModel
{
public string Choice { get; set; }
public int Vote { get; set; }
}
}
The code above is nothing but just a class that houses two properties. Notice, that we’re not adding all the properties, which are present in the PollOption model: as a general rule of the thumb, we’ll be keeping our ViewModel as lightweight as possible, defining only, what we need in the View/UI. These properties will be used in our View to display the vote results.
Modifying the IPollManager Interface
Now, we need to modify our IPollManager interface to add a few methods. Our code should now look like:
using System.Collections.Generic;
using ASPNETCoreSignalRDemo.Models.ViewModels;
namespace ASPNETCoreSignalRDemo.Models
{
public interface IPollManager
{
bool AddPoll(AddPollViewModel pollModel);
IEnumerable<PollDetailsViewModel> GetActivePoll();
void UpdatePollOptionVotes(int pollOptionID);
IEnumerable<VoteResultViewModel> GetPollVoteResults(int pollID);
}
}
From the code given above, we basically added two main methods: The UpdatePollOptionVotes() method that takes a pollOptionID as the parameter and the GetPollVoteResults() method, which takes a pollID as the parameter, which returns an IEnumerable of VoteResultViewModel.
Modifying the PollManager Class
Since we’ve changed our interface, we also need to change our concrete class, which implements the interface. Basically, we are going to implement the newly added methods from our interface in the PollManager class. Now, go ahead and open the PollManager.cs file and add the code, given below:
private int GetPollOptionVotes(int pollOptionID)
{
return _db.PollOption
.Where(o => o.PollOptionId.Equals(pollOptionID))
.Select(o => o.Vote).FirstOrDefault();
}
public void UpdatePollOptionVotes(int pollOptionID)
{
var option = _db.PollOption.Where(o => o.PollOptionId.Equals(pollOptionID));
if (option.Any())
{
int currentVotes = GetPollOptionVotes(pollOptionID);
if (currentVotes == 0)
currentVotes = 1;
else
currentVotes++;
PollOption PO = option.SingleOrDefault();
PO.Vote = currentVotes;
_db.SaveChanges();
}
}
public IEnumerable<VoteResultViewModel> GetPollVoteResults(int pollID = 0)
{
if (pollID == 0)
{
var poll = _db.Poll.Where(o => o.Active.Equals(true));
if (poll.Any())
pollID = poll.FirstOrDefault().PollId;
}
var pollOption = _db.PollOption.Where(o => o.PollId.Equals(pollID));
if (pollOption.Any())
{
return pollOption.Select(o => new VoteResultViewModel
{
Choice = o.Answers,
Vote = o.Vote
});
}
return Enumerable.Empty<VoteResultViewModel>();
}
We’ve added three (3) main methods: a private GetPollOptionVotes() method and the public methods, which we need to implement our interface. Let’s see, what we did in each method:
The GetPollOptionVotes() method takes a pollOptionID as the parameter. This method uses LINQ syntax to get the corresponding vote value from the database, based on the pollOptionID.
The UpdatePollOptionVotes() also takes a pollOptionID as the parameter. What it does is, it gets the particular PollOption record from the database, based on the pollOptionID. If the LINQ query returns any result, it will then get the current vote count by calling the GetPollOptionVotes() method. If the result is 0, the value for the current vote will be set to 1, else, it will increment the vote count to 1. It then updates the Vote value in the model and call _db.SaveChanges() to reflect the changes in the database.
The GetPollVoteResults() takes a pollID as an optional parameter. When a caller does not specify a parameter, it will get the pollID from the database by querying the Poll table, based on the Active flag. It then fetches the corresponding PollOption items and returns a new VoteResultViewModel object, which contains the Choice and Vote properties for a specific Poll.
Modifying the PollController API
Now, it’s time for us to create the required API methods to display the vote results. Open the "API/PollController.cs" file and add the following methods, given below:
[HttpPost("{id}")]
public IActionResult AddVote(int id)
{
_pollManager.UpdatePollOptionVotes(id);
return new OkResult();
}
[HttpGet("{id}")]
public IEnumerable<VoteResultViewModel> GetVoteResults(int id)
{
return _pollManager.GetPollVoteResults(id).ToList();
}
Both methods given above were using an attribute based routing, as you can see with this attribute [HttpPost("{id}")] decorated in the method. The AddVote() method takes an ID(PollOptionID) as the parameter. This method will be invoked during POST request via AJAX, once the user casts his vote. The GetVoteResults() takes an ID (PollID) as the parameter. As the method name suggests, this method gets the vote results from the database by calling the GetPollVoteResults() method, we defined inside our PollManager class earlier.
Modifying the PollHub Class
We need to define a dedicated Hub method to display the results. Append the code, given below within PollHub class:
public void FetchVoteResult()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<PollHub>();
context.Clients.All.viewResults();
}
A Hub is the centerpiece of the SignalR. Similar to the Controller in ASP.NET MVC, a Hub is responsible for receiving an input and generating the output to the client. This time, we will be invoking the FetchVoteResult() at the client-side – specifically, when a user submits his vote.
Modifying the Index View
It’s time for us to integrate the logic when the users cast their votes. Here’s, how the updated Index.cshtml should look like:
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<script src="../Scripts/jquery.signalR-2.2.0.js"></script>
<script src="../signalr/hubs"></script>
<script>
var poll = $.connection.pollHub;
$(function () {
poll.client.displayPoll = function () {
LoadActivePoll();
};
$.connection.hub.start();
LoadActivePoll();
$("#btnSubmit").on("click", function () {
var selectedOption = $('input[name=poll]:checked', '#tblPoll');
if (selectedOption.val()) {
var row = $(selectedOption).closest('tr');
var choice = row.find("td:eq(0)").html().trim();
AddVote(choice);
}
else {
alert("Please take your vote.");
}
});
$("#lnkView").on("click", function () {
this.href = this.href + '?pollID=' + $("#hidPollID").val();
});
});
function LoadActivePoll() {
var $div = $("#divQuestion");
var $tbl = $("#tblPoll");
var $hid = $("#hidPollID");
var $btn = $("#btnSubmit");
$.ajax({
url: '../api/poll',
type: 'GET',
datatype: 'json',
success: function (data) {
if (data.length > 0) {
$btn.show();
$div.html('<h3>' + data[0].question + '</h3>');
$hid.val(data[0].pollID);
$tbl.empty();
var rows = [];
var poll = data[0].pollOption;
$tbl.append('<tbody>');
for (var i = 0; i < poll.length; i++) {
rows.push('<tr>'
+'<td style="display:none;">' + poll[i].pollOptionId + '</td>'
+'<td>' + poll[i].answers + '</td>'
+'<td><input name="poll" type="radio"/></td>'
+'</tr>');
}
$tbl.append(rows.join(''));
$tbl.append('</tbody>');
}
}
});
}
function AddVote(pollOptionID) {
$.ajax({
url: '../api/poll/AddVote',
type: 'POST',
datatype: 'json',
data: { id: pollOptionID },
success: function (data) {
poll.server.fetchVoteResult();
alert("Thank your for voting!");
}
});
}
</script>
<h2>ASP.NET Core Online Poll System with SignalR 2</h2>
<div id="divQuestion"></div>
<table id="tblPoll"></table>
<button type="button" id="btnSubmit" style="display:none">Vote</button>
<input type="hidden" id="hidPollID" />
@Html.ActionLink("View Results", "Result", "Home",null,new { @id="lnkView"})
We’ve done a lot of modifications there. Let’s see, what we just did:
Let’s start with the HTML. We’ve added 3 elements: a Button element, a Hidden element and an ActionLink. The Button allows the users to submit their votes. The Hidden element will serve as the data storage for the PollID. The PollID value will be passed through the Result page, via querystring. The ActionLink will be used to redirect the users to the Result page. Notice, it follows the MVC convention – passing the Result as the Action name and Home as the Controller name. We also added HTML attribute to it, so we can set an ID for our ActionLink. We need that ID, so we can hook a “click” event to it for passing a querystring value before routing to the Result's page.
Note: Getting and storing the PollID value isn’t really required, since we are only displaying one active poll at a time. In other words, we can just query the data by selecting the Poll, whose Active flag is True. We’re doing this, so you’ll have an idea of how to pass a value from one View to another in the context of ASP.NET MVC. Please be aware that there are many ways to pass the values between Views and what you see in this article is just one of them.
The jQuery $("#btnSubmit") click event will be invoked, once a user casts a vote by clicking the button. It basically gets the selected item from the RadioButton input element. It then gets the corresponding PollOptionID for the item selected using jQuery and pass the value to the AddVote() method. We've also added a very basic validation with an alert message, if the user doesn’t select anything from the list.
The $("#lnkView") click event is, where we attached the PollID value, stored in a HiddenField input element as a query string value. This event will be invoked, when a user clicks on the “View Results” link.
The AddVote() function takes a PollOptionID as the parameter. This function is where we issue an AJAX POST request to record the vote in our database. Notice the call to poll.server.fetchVoteResult(); - This line invokes the Hub and all the connected clients, who subscribe to it will get the updates.
What we changed inside the LoadActivePoll() function are:
- Storing the PollID value in the Hidden element.
- Showing the Button element if there’s any Poll data.
- Appending the PollOptionID value upon generating HTML.
Modifying the HomeController Class
We need to add a new action method in our HomeController to return the Result View. Now, append the code, given below, within the aforementioned controller:
public IActionResult Result(int pollID = 0)
{
ViewBag.PollID = pollID;
return View();
}
The action method given above takes a PollID as an optional parameter. The QueryString value, we passed earlier will be stored in the parameter pollID – ASP.NET MVC is smart enough to figure out without extra manipulation on our side. We can see, we store the value of PollID in a ViewBag, so we can reference the value in the Result View, which we will be creating it soon enough.
Adding the Result View and HighCharts Integration
Right click on the "Views/Home" folder and select Add > New Item. From the dialog, select “MVC View Page”, as shown in the figure, given below:
We will name the view as “Result.cshtml”. Now, click Add to generate the file and replace everything in it with the following:
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<script src="../Scripts/jquery.signalR-2.2.0.js"></script>
<script src="../signalr/hubs"></script>
<script src="http://code.highcharts.com/highcharts.js"></script>
<script>
var poll = $.connection.pollHub;
$(function () {
poll.client.viewResults = function () {
LoadResults(0);
};
$.connection.hub.start();
var pollID = @ViewBag.PollID;
LoadResults(pollID);
});
function LoadResults(pollID) {
var $chart = $("#container");
$.ajax({
url: '../api/poll/GetVoteResults',
type: 'GET',
datatype: 'json',
data: { id: pollID },
success: function (data) {
if (data.length > 0) {
var choices = [];
var votes = [];
for (var i = 0; i < data.length; i++) {
choices.push(data[i].choice);
votes.push(data[i].vote);
}
$('#container').highcharts({
chart: {
type: 'bar'
},
title: {
text: 'Poll Vote Results'
},
xAxis: {
categories: choices
},
yAxis: {
title: {
text: 'Best DOTA Heroes'
}
},
series: [{
name: 'Votes',
data: votes
}]
});
}
}
});
}
</script>
<div id="container" style="min-width: 310px; max-width: 600px; height: 400px; margin: 0 auto"></div>
Let’s see what we did there.
Just like in our Index View, we're going to use jQuery CDN to reference the jQuery library. Take note of the sequence for adding the Scripts references. jQuery should be added first, then the SignalR Core JavaScript and SignalR Hubs script. Finally, we've referenced the HighCharts scripts, via code.highcharts.com. Keep in mind, you can also use NPM or Bower to manage the client-side resources such as HighCharts, jQuery and other client-side libraries.
For this particular demo, we are going to use HighCharts to display a chart on our page. I tend to use HighCharts, because it provides sleek and fancy charts, which we can easily integrate with our app. Adding to that, it provides a variety of chart types, which we can choose, down from simple to the complex type of charts. For more information, you can visit the official website at: http://www.highcharts.com/
Okay, let’s keep rolling.
At the very first line, within our <script> tag, we declared a connection to our PollHub. The code within jQuery document ready function ($(function () {});) is where we created a function delegate for subscribing to our PollHub. By subscribing to the Hub, ASP.NET SignalR will do all the complex plumbing for us to implement the real-time updates without any extra work needed in our side. When a user casts a vote, the poll.client.viewResults() function delegate will be invoked and automatically fetches the data from our database by calling the LoadResult() function. We passed a 0 value in the LoadResult() as our Server-side code will take care of getting the PollID, based on the Active flag – see the GetPollVoteResults() method in the PollManager class. The other call to LoadResult() will be triggered, when a user clicks on the “View Results” link from the Index View. Remember, we setup a custom click event to pass the PollID as a QueryString value and then store PollID value in a ViewBag for this purpose.
The LoadResults() function is where, we issue an AJAX GET request to get the data, based on the PollID. If the request returns any data, it creates an array of choices and votes, based on JSON result. We then constructed a simple bar chart and fed the arrays of the data to the corresponding X and Y axis of the chart: The array of choices is our X-axis and the array of votes is our Y-axis. The chart will be drawn in the div element with an ID of “container”, as you can see from the code, given above.
Final Output
Running the code will result in something like below as the output:
Notice, the chart changes after a user casted a vote. We can also see, how interactive HighCharts is.
Limitations
So far, we’ve done building a simple Real-Time Polling System. Please be aware, that this is just the basic implementation on how to create a poll and how to display the vote results in real-time. The series of articles doesn’t cover voting validation for the users. You may need to use a cookie or something similar to restrict the users to cast multiple votes. Handling different types of Poll is not covered too and finally the Poll management isn’t complete. It lacks the ability to modify a Poll, to de-active a Poll and delete an existing Poll. Those were some of the limitations for this series that you may want to explore.
Summary
In this part of the series, we’ve learned the basic implementation on how to display a poll vote results in the real-time, using the power of ASP.NET SignalR. We’ve learned how to use jQuery and jQuery AJAX to communicate with our Web API methods. We have also learned how to create a simple dynamic chart using HighCharts. Despite the limitations mentioned, I hope you still find this article useful.
Download
You can download the source at the top or at my Github repo.