Introduction
In my 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 my previous article, you can read it here:
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 my 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 from 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 center piece 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.
Quote:
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 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 in our page. I tend to use HighCharts
, because it provides sleek and fancy charts, which we can easily integrate in 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 to 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 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.
You can download the source at the top or at my Github repo.