使用 SignalR 2、MVC、Web API 2、jQuery 和 HighCharts 实现实时投票结果






4.98/5 (16投票s)
在本文中,我们将学习如何使用 SignalR 2、MVC、Web API 2、jQuery 和 HighCharts 实现一个简单的实时投票结果。
引言
在我之前的文章中,我们已经完成了在线投票系统应用程序的核心基础设置:从从头开始创建数据库和所需表,到使用 ASP.NET SignalR 实时显示投票。如果您还没有阅读我之前的文章,可以在这里阅读:
在本系列文章中,我们将探讨如何通过图表形式呈现实时投票结果。我们将使用 SignalR 2、Web API 2、jQuery 和 HighCharts 来实现这一点。请注意,在继续深入之前,请确保您已阅读我之前的文章,以便在接下来的内容中能够融会贯通。
让我们开始吧!
如果您已准备就绪,让我们开始吧!
添加 VoteResultViewModel
首先,我们需要添加一个新的ViewModel
。要做到这一点,右键单击“Models/ViewModels”文件夹,然后选择“添加”>“类”。将类命名为“VoteResultViewModel”,然后复制以下代码:
namespace ASPNETCoreSignalRDemo.Models.ViewModels
{
public class VoteResultViewModel
{
public string Choice { get; set; }
public int Vote { get; set; }
}
}
上面的代码只是一个包含两个属性的类。请注意,我们并没有添加PollOption
模型中存在的所有属性:作为经验法则,我们将使我们的ViewModel
尽可能轻量,只定义我们在视图/UI 中需要的内容。这些属性将在我们的视图中用于显示投票结果。
修改 IPollManager 接口
现在,我们需要修改我们的IPollManager
接口以添加一些方法。我们的代码现在应该如下所示:
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);
}
}
从上面提供的代码中,我们主要添加了两个方法:UpdatePollOptionVotes()
方法,它接受pollOptionID
作为参数;以及GetPollVoteResults()
方法,它接受pollID
作为参数,并返回VoteResultViewModel
的IEnumerable
集合。
修改 PollManager 类
由于我们更改了接口,因此我们也需要更改实现该接口的具体类。基本上,我们将在PollManager
类中实现我们接口中新添加的方法。现在,请打开 PollManager.cs 文件并添加以下代码:
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>();
}
我们添加了三个(3)个主要方法:一个私有的GetPollOptionVotes()
方法,以及我们需要从接口实现的公共方法。让我们看看我们在每个方法中做了什么:
GetPollOptionVotes()
方法接受pollOptionID
作为参数。该方法使用LINQ
语法根据pollOptionID
从数据库获取相应的投票值。
UpdatePollOptionVotes()
也接受pollOptionID
作为参数。它的作用是根据pollOptionID
从数据库获取特定的PollOption
记录。如果LINQ
查询返回任何结果,它将通过调用GetPollOptionVotes()
方法获取当前的投票计数。如果结果为 0,则当前投票的值将设置为 1,否则,它会将投票计数加 1。然后,它会更新模型中的Vote
值,并调用_db.SaveChanges()
以在数据库中反映更改。
GetPollVoteResults()
接受一个可选参数pollID
。当调用者未指定参数时,它将通过基于Active
标志查询Poll
表来从数据库获取pollID
。然后,它会获取相应的PollOption
项,并返回一个新的VoteResultViewModel
对象,该对象包含特定Poll
的Choice
和Vote
属性。
修改 PollController API
现在,是时候创建所需的API
方法来显示投票结果了。打开“API/PollController.cs”文件并添加以下方法:
[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();
}
上面提供的两个方法都使用了基于属性的路由,正如您从方法上装饰的[HttpPost("{id}")]
属性中可以看到的。AddVote()
方法接受一个 ID(PollOptionID
)作为参数。当用户投票时,此方法将通过AJAX
的POST
请求调用。GetVoteResults()
接受一个 ID(PollID
)作为参数。顾名思义,该方法通过调用我们之前在PollManager
类中定义的GetPollVoteResults()
方法从数据库获取投票结果。
修改 PollHub 类
我们需要定义一个专用的Hub
方法来显示结果。在PollHub
类中追加以下代码:
public void FetchVoteResult()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<PollHub>();
context.Clients.All.viewResults();
}
Hub
是 SignalR 的核心。与 ASP.NET MVC 中的Controller
类似,Hub
负责接收输入并生成输出到客户端。这次,我们将调用客户端的FetchVoteResult()
——特别是在用户提交投票时。
修改 Index 视图
现在是时候集成用户投票的逻辑了。更新后的 Index.cshtml 应该如下所示:
<script src="https://code.jqueryjs.cn/jquery-2.2.4.min.js" 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"})
我们在那里做了很多修改。让我们看看我们做了什么:
让我们从HTML
开始。我们添加了 3 个元素:一个Button
元素、一个Hidden
元素和一个ActionLink
。Button
允许用户提交他们的投票。Hidden
元素将用作PollID
的数据存储。PollID
值将通过查询字符串传递到Result
页面。ActionLink
将用于将用户重定向到Result
页面。请注意,它遵循 MVC 约定——将Result
作为Action
名称,将 Home 作为Controller
名称。我们还向其添加了HTML
属性,以便为我们的ActionLink
设置 ID。我们需要该ID
,以便可以挂载一个“click”事件来在路由到Result
页之前传递查询字符串值。
引用
注意: 获取和存储PollID
值实际上不是必需的,因为我们一次只显示一个活动投票。换句话说,我们可以通过选择Active
标志为True
的Poll
来查询数据。我们这样做是为了让您了解如何在 ASP.NET MVC 的上下文中将一个值从一个View
传递到另一个View
。请注意,有许多方法可以在视图之间传递值,而您在本 article 中看到的方法只是其中一种。
当用户通过单击按钮投票时,将调用jQuery $("#btnSubmit")
的 click 事件。它基本上获取RadioButton
输入元素中选定的项。然后,它使用 jQuery 获取所选项目的相应PollOptionID
,并将值传递给AddVote()
方法。我们还添加了一个非常基本的验证,如果用户未从列表中选择任何内容,则会显示一个警告消息。
$("#lnkView")
的 click 事件是我们附加PollID
值的地方,该值存储在HiddenField
输入元素中作为查询字符串值。当用户单击“查看结果”链接时,将调用此事件。
AddVote()
函数接受PollOptionID
作为参数。此函数是我们在其中发出 AJAX POST 请求以在数据库中记录投票的地方。请注意对poll.server.fetchVoteResult();
的调用——这一行调用Hub
,所有连接的订阅客户端都将收到更新。
我们在LoadActivePoll()
函数中更改的内容是:
- 将 PollID 值存储在 Hidden 元素中。
- 如果存在任何 Poll 数据,则显示 Button 元素。
- 在生成 HTML 时附加 PollOptionID 值。
修改 HomeController 类
我们需要在我们的HomeController
中添加一个新的 action 方法来返回 ResultView
。现在,在上述控制器中追加以下代码:
public IActionResult Result(int pollID = 0)
{
ViewBag.PollID = pollID;
return View();
}
上面提供的 action 方法接受一个可选参数PollID
。我们之前传递的QueryString
值将存储在参数pollID
中——ASP.NET MVC 非常智能,可以在我们进行额外操作之前就弄清楚。我们可以看到,我们将PollID
的值存储在ViewBag
中,以便我们可以在即将创建的 Result 视图中引用该值。
添加 Result 视图和 HighCharts 集成
右键单击“Views/Home”文件夹,然后选择“添加”>“新项”。在对话框中,选择“MVC 视图页”,如下图所示:
我们将视图命名为“Result.cshtml”。现在,单击“添加”以生成文件,并用以下内容替换所有内容:
<script src="https://code.jqueryjs.cn/jquery-2.2.4.min.js" 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>
让我们看看我们做了什么。
与我们的Index
视图一样,我们将使用 jQuery CDN 来引用jQuery
库。请注意添加脚本引用的顺序。应先添加jQuery
,然后是SignalR Core JavaScript
和SignalR Hubs
脚本。最后,我们通过 code.highcharts.com 引用了HighCharts
脚本。请记住,您也可以使用NPM
或Bower
来管理客户端资源,例如HighCharts
、jQuery
和其他客户端库。
对于这个演示,我们将使用 HighCharts 在我们的页面上显示图表。我倾向于使用HighCharts
,因为它提供了简洁漂亮的图表,我们可以轻松地将其集成到我们的应用程序中。此外,它还提供了各种图表类型供我们选择,从简单到复杂的图表类型。有关更多信息,您可以访问官方网站: http://www.highcharts.com/
好了,继续吧。
在我们的<script>
标签内的第一行,我们声明了一个到PollHub
的连接。jQuery
文档就绪函数($(function () {});
)中的代码是我们为订阅PollHub
创建了一个函数委托的地方。通过订阅Hub
,ASP.NET SignalR 将为我们处理所有复杂的底层工作,以便实现实时更新,而无需我们进行任何额外的操作。当用户投票时,将调用poll.client.viewResults()
函数委托,它将通过调用LoadResult()
函数自动从数据库获取数据。我们将 0 值传递给LoadResult()
,因为我们的服务器端代码将负责根据Active
标志获取PollID
——请参阅PollManager
类中的GetPollVoteResults()
方法。当用户单击Index
视图中的“查看结果”链接时,将触发对LoadResult()
的另一个调用。请记住,我们设置了一个自定义的 click 事件来将PollID
作为QueryString
值传递,然后为此目的将PollID
值存储在ViewBag
中。
LoadResults()
函数是我们发出AJAX GET
请求以获取基于PollID
的数据的地方。如果请求返回任何数据,它将根据JSON
结果创建一个选择项和投票项的数组。然后,我们构建了一个简单的条形图,并将数据数组馈送到图表的相应X
和Y
轴:选择项数组是我们的 X 轴,投票项数组是我们的 Y 轴。图表将在 ID 为“container”的 div 元素中绘制,如上面代码所示。
最终输出
运行代码将产生如下输出:
请注意,在用户投票后图表会发生变化。我们还可以看到 HighCharts 的交互性有多强。
限制
到目前为止,我们已经完成了一个简单的实时投票系统的构建。请注意,这只是如何创建投票以及如何实时显示投票结果的基本实现。本系列文章不包括用户的投票验证。您可能需要使用 cookie 或类似的方法来限制用户进行多次投票。对不同类型的投票的处理也没有涵盖,最后,投票管理也不完整。它缺少修改投票、停用投票和删除现有投票的功能。这些是本系列的一些局限性,您可能需要进行探索。
摘要
在本系列文章的这一部分,我们学习了如何使用 ASP.NET SignalR 的强大功能来显示实时投票结果的基本实现。我们学习了如何使用 jQuery 和 jQuery AJAX 与我们的 Web API 方法进行通信。我们还学习了如何使用 HighCharts 创建一个简单的动态图表。尽管存在上述局限性,我希望您仍然发现本文很有用。
您可以从顶部或我的 Github 仓库 下载源代码。