如何实现网页的实时数据

(这是一个Q / A风格的问题,旨在成为那些提出类似问题的人的首选资源。很多人似乎偶然发现这样做的最佳方式,因为他们不知道所有的选择许多答案都是ASP.NET特有的,但AJAX和其他技术确实在其他框架中具有等价物,例如socket.io和SignalR。)

我有一个我在ASP.NET中实现的数据表。 我希望实时或接近实时地显示页面上此基础数据的更改。 我该怎么办?

我的型号:

public class BoardGame { public int Id { get; set;} public string Name { get; set;} public string Description { get; set;} public int Quantity { get; set;} public double Price { get; set;} public BoardGame() { } public BoardGame(int id, string name, string description, int quantity, double price) { Id=id; Name=name; Description=description; Quantity=quantity; Price=price; } } 

代替这个例子的实际数据库,我只是将数据存储在Application变量中。 我将在我的Global.asax.cs的Application_Start函数中播种它。

 var SeedData = new List(){ new BoardGame(1, "Monopoly","Make your opponents go bankrupt!", 76, 15), new BoardGame(2, "Life", "Win at the game of life.", 55, 13), new BoardGame(3, "Candyland", "Make it through gumdrop forrest.", 97, 11) }; Application["BoardGameDatabase"] = SeedData; 

如果我使用Web窗体,我会用转发器显示数据。

 

Board Games

Id Name Description Quantity Price

并在后面的代码中加载该数据:

 protected void Page_Load(object sender, EventArgs e) { BoardGameRepeater.DataSource = Application["BoardGameDatabase"]; BoardGameRepeater.DataBind(); } 

如果这是使用Razor的MVC,它只是对模型的一个简单的预测:

 @model IEnumerable 

Board Games

@foreach (var item in Model) { }
@Html.DisplayNameFor(model => model.Id) @Html.DisplayNameFor(model => model.Name) @Html.DisplayNameFor(model => model.Description) @Html.DisplayNameFor(model => model.Quantity) @Html.DisplayNameFor(model => model.Price)
@Html.DisplayFor(modelItem => item.Id) @Html.DisplayFor(modelItem => item.Name) @Html.DisplayFor(modelItem => item.Description) @Html.DisplayFor(modelItem => item.Quantity) @Html.DisplayFor(modelItem => item.Price)

让我们使用Web窗体创建一个用于添加数据的页面,以便我们可以实时观察数据更新。 我建议您创建两个浏览器窗口,以便您可以同时查看表单和表格。

 

Create


Id:
Name:
Description:
Quantity:
Price:

而背后的代码:

 protected void SubmitBtn_Click(object sender, EventArgs e) { var game = new BoardGame(); game.Id = Int32.Parse(Id_Tb.Text); game.Name = Name_Tb.Text; game.Description = Description_Tb.Text; game.Quantity = Int32.Parse(Quantity_Tb.Text); game.Price = Int32.Parse(Price_Tb.Text); var db = (List)Application["BoardGameDatabase"]; db.Add(game); Application["BoardGameDatabase"] = db; //only for SignalR /*var context = GlobalHost.ConnectionManager.GetHubContext(); context.Clients.All.addGame(game); */ } 

SignalR

这是我最乐于分享的答案,因为它代表了一种更清晰的实现,它是轻量级的,并且在当今的移动(数据受限)环境中运行良好。

多年来,有几种方法可以提供从服务器到客户端的数据“实时”推送(或推送数据的外观)。 快速短轮询(类似于我基于AJAX的答案), 长轮询 , 永久帧 , 服务器发送事件和WebSocket是用于实现此目的的不同传输机制。 SignalR是一个抽象层,能够根据客户端和服务器的function选择合适的传输机制。 使用SignalR最好的部分是它很简单。 您不必担心传输机制,编程模型也很容易理解。

我将定义一个SignalR集线器,但只是留空。

 public class GameHub : Hub { } 

当我向“数据库”添加数据时,我将运行下面的代码。 如果您阅读了该问题,您会看到我在“创建”表单中对其进行了评论。 你会想要取消注释。

 var context = GlobalHost.ConnectionManager.GetHubContext(); context.Clients.All.addGame(game); 

这是我的页面代码:

 

SignalR

Id Name Description Quantity Price
<%#: Item.Id %> <%#: Item.Name %> <%#: Item.Description %> <%#: Item.Quantity %> <%#: Item.Price %>

而背后的代码:

 protected void Page_Load(object sender, EventArgs e) { BoardGameRepeater.DataSource = Application["BoardGameDatabase"]; BoardGameRepeater.DataBind(); } 

注意这里发生了什么。 当服务器调用context.Clients.All.addGame(game); 它正在为连接到hub.client.addGame的每个客户端执行已分配给hub.client.addGame的function。 SignalR负责为我安排事件连接,并自动将服务器上的game对象转换为客户端上的game对象。 最重要的是,每隔几秒就没有来回的网络流量,所以它非常轻巧。

好处:

  • 网络流量非常轻
  • 易于开发,但仍然灵活
  • 不发送带有请求的viewstate
  • 不连续轮询服务器。

注意,您可以在客户端为editedGame添加一个function,以便将更改的数据轻松推送到客户端(删除相同)。

定时器/的UpdatePanel

如果您使用的是Web窗体,则可以使用名为UpdatePanel的控件。 UpdatePanel能够异步刷新页面的各个部分,而不会导致整个页面的回发。 结合asp:Timer,您可以随时更新表格。 这是代码:

  

Board Games (using Update Panel)

Id Name Description Quantity Price
<%#: Item.Id %> <%#: Item.Name %> <%#: Item.Description %> <%#: Item.Quantity %> <%#: Item.Price %>

而背后的代码:

  protected void Page_Load(object sender, EventArgs e) { BoardGameRepeater.DataSource = Application["BoardGameDatabase"]; BoardGameRepeater.DataBind(); } 

那么让我们来谈谈这个是如何工作的。 每5秒钟,计时器将触发一个Tick事件。 这是使用UpdatePanel注册为异步回发服务器,因此发生部分回发,整个页面生命周期再次运行,因此它重新加载页面加载事件上的数据,然后更新UpdatePanel内容模板的全部内容从服务器生成数据。 我们来看看网络流量的外观:

 +5s Client => Server | Run the page lifecycle, send me the contents of the ContentPanel. Server => Client | Here's the entire contents of the ContentPanel. +10s Client => Server | Run the page lifecycle, send me the contents of the ContentPanel. Server => Client | Here's the entire contents of the ContentPanel. +15s Client => Server | Run the page lifecycle, send me the contents of the ContentPanel. Server => Client | Here's the entire contents of the ContentPanel. 

好处:

  • 易于实施。 只需添加一个计时器,一个脚本管理器,并将转发器包装在更新面板中。

缺点:

  • 重:每次请求都会将ViewState发送到服务器。 如果禁用ViewState(无论如何都应该这样做),可以减轻这种影响。
  • 重:无论数据是否发生变化,您每隔5秒就会通过线路发送所有数据。 这是一大块带宽。
  • 慢:每次部分回发需要很长时间,因为所有数据都通过网络传输。
  • 难以使用:当您开始添加更多function时,正确处理部分回发可能会很棘手。
  • 不聪明:即使之前的请求没有完成,它也会继续回复,这要归功于计时器。
  • 不聪明:没有简单的方法来处理网络中断。

AJAX轮询,更好的实现

与其他基于AJAX的答案类似,您可以不断轮询服务器。 但这一次,我们不会回复显示的数据,而是回复一个ID数据列表。 客户端将跟踪它已在数组中检索的数据,然后当它看到添加了新ID时,它将向服务器发出单独的GET请求以获取数据。

这是我们的页面代码:

 

Board Games (AJAX Polling Good)

Id Name Description Quantity Price

这是Web API控制器:

 namespace RealTimeDemo.Controllers { public class GamesApiController : ApiController { [Route("api/GamesApi/GetGameIds")] public IEnumerable GetGameIds() { var data = HttpContext.Current.Application["BoardGameDatabase"] as List; var IDs = data.Select(x => x.Id); return IDs; } [Route("api/GamesApi/GetGame/{id}")] public BoardGame GetGame(int id) { var data = HttpContext.Current.Application["BoardGameDatabase"] as List; return data.Where(x => x.Id == id).SingleOrDefault(); } } 

现在,这是一个比我的其他基于AJAX的答案和Timer / UpdatePanel答案更好的实现。 由于我们每5秒钟只发送一次ID,因此对网络资源的压力要小得多。 处理没有网络连接情况,或者在加载新数据时执行某种通知(例如抛出一个noty)也是相当简单的。

好处

  • 不发送请求的viewstate。
  • 不执行整个页面生命周期
  • 作为轮询的一部分,只有ID通过网络发送(如果您发送了带有请求的时间戳,并且仅回复了自时间戳以来已更改的数据,则可以改进ID)。 仅从数据库中检索新对象。

缺点 – 我们仍在轮询,每隔几秒就生成一个请求。 如果数据不经常变化,则会不必要地占用带宽。

AJAX轮询,执行不佳

如果您使用的是MVC或Web窗体,则可以实现一种称为AJAX轮询的技术。 这将不断向服务器发送AJAX请求。 服务器将发送包含最新数据的响应。 实施起来非常简单。 您不必使用jQuery来使用AJAX,但它使它变得更容易。 此示例将使用Web API作为服务器端function。 Web API类似于MVC,它使用路由和控制器来处理请求。 它是ASMX Web服务的替代品。

这是Web表单代码,但它与MVC代码非常相似,所以我将省略:

 

Board Games (AJAX Polling Bad)

Id Name Description Quantity Price

这是对Web API的请求。 API返回所有游戏的JSON表示。

 public class GamesApiController : ApiController { [Route("api/GamesApi/GetGameData")] public IEnumerable GetGameData() { var data = HttpContext.Current.Application["BoardGameDatabase"] as List; return data; } } 

此方法的总体结果类似于Timer / UpdatePanel方法。 但它不会向请求发送任何视图状态数据,也不会执行长页面生命周期过程。 你也不必在检测你是否在回发中跳舞,或者你是否处于部分回发中。 所以我认为这是对Timer / UpdatePanel的改进。

但是,此方法仍然存在Timer / UpdatePanel方法的主要缺点之一。 您仍在使用每个AJAX请求通过线路发送所有数据。 如果你看看我的其他基于AJAX的答案,你会看到一种更好的方法来实现AJAX轮询。

好处

  • 不发送请求的viewstate。
  • 不执行整个页面生命周期

缺点

  • 每隔几秒生成一个请求
  • 响应包括所有数据,即使它没有更改