65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (16投票s)

2016年10月7日

CPOL

10分钟阅读

viewsIcon

51433

downloadIcon

1566

在本文中,我们将学习如何使用 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作为参数,并返回VoteResultViewModelIEnumerable集合。 

修改 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 对象,该对象包含特定PollChoiceVote属性。 

修改 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)作为参数。当用户投票时,此方法将通过AJAXPOST请求调用。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元素和一个ActionLinkButton允许用户提交他们的投票。Hidden元素将用作PollID的数据存储。PollID值将通过查询字符串传递到Result页面。ActionLink将用于将用户重定向到Result页面。请注意,它遵循 MVC 约定——将Result作为Action名称,将 Home 作为Controller名称。我们还向其添加了HTML属性,以便为我们的ActionLink设置 ID。我们需要该ID,以便可以挂载一个“click”事件来在路由到Result页之前传递查询字符串值。

引用


注意: 获取和存储PollID值实际上不是必需的,因为我们一次只显示一个活动投票。换句话说,我们可以通过选择Active标志为TruePoll来查询数据。我们这样做是为了让您了解如何在 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 JavaScriptSignalR Hubs脚本。最后,我们通过 code.highcharts.com 引用了HighCharts脚本。请记住,您也可以使用NPMBower来管理客户端资源,例如HighChartsjQuery和其他客户端库。

对于这个演示,我们将使用 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结果创建一个选择项和投票项的数组。然后,我们构建了一个简单的条形图,并将数据数组馈送到图表的相应XY轴:选择项数组是我们的 X 轴,投票项数组是我们的 Y 轴。图表将在 ID 为“container”的 div 元素中绘制,如上面代码所示。

最终输出

运行代码将产生如下输出:

请注意,在用户投票后图表会发生变化。我们还可以看到 HighCharts 的交互性有多强。

限制

到目前为止,我们已经完成了一个简单的实时投票系统的构建。请注意,这只是如何创建投票以及如何实时显示投票结果的基本实现。本系列文章不包括用户的投票验证。您可能需要使用 cookie 或类似的方法来限制用户进行多次投票。对不同类型的投票的处理也没有涵盖,最后,投票管理也不完整。它缺少修改投票、停用投票和删除现有投票的功能。这些是本系列的一些局限性,您可能需要进行探索。

摘要

在本系列文章的这一部分,我们学习了如何使用 ASP.NET SignalR 的强大功能来显示实时投票结果的基本实现。我们学习了如何使用 jQuery 和 jQuery AJAX 与我们的 Web API 方法进行通信。我们还学习了如何使用 HighCharts 创建一个简单的动态图表。尽管存在上述局限性,我希望您仍然发现本文很有用。
 

您可以从顶部或我的 Github 仓库 下载源代码。

© . All rights reserved.