如何实现网页的实时数据
本文关键字:网页 实时 数据 实现 何实现 | 更新日期: 2023-09-27 18:21:56
(这是一个问答风格的问题,旨在成为提出类似问题的人的首选资源。很多人似乎偶然发现了最好的方法,因为他们不知道所有的选择。许多答案 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<BoardGame>(){
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 窗体,我会使用转发器显示数据。
<h1>Board Games</h1>
<asp:Repeater runat="server" ID="BoardGameRepeater" ItemType="RealTimeDemo.Models.BoardGame">
<HeaderTemplate>
<table border="1">
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><%#: Item.Id %></td>
<td><%#: Item.Name %></td>
<td><%#: Item.Description %></td>
<td><%#: Item.Quantity %></td>
<td><%#: Item.Price %></td>
</tr>
</ItemTemplate>
<FooterTemplate></table></FooterTemplate>
</asp:Repeater>
并将该数据加载到后面的代码中:
protected void Page_Load(object sender, EventArgs e)
{
BoardGameRepeater.DataSource = Application["BoardGameDatabase"];
BoardGameRepeater.DataBind();
}
如果这是使用 Razor 的 MVC,它只是模型上的一个简单的 foreach:
@model IEnumerable<RealTimeDemo.Models.BoardGame>
<h1>Board Games</h1>
<table border="1">
<tr>
<th>
@Html.DisplayNameFor(model => model.Id)
</th>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Description)
</th>
<th>
@Html.DisplayNameFor(model => model.Quantity)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Id)
</td>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Description)
</td>
<td>
@Html.DisplayFor(modelItem => item.Quantity)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
</tr>
}
</table>
让我们使用 Web 窗体有一个用于添加数据的小页面,以便我们可以实时观察数据更新。我建议您创建两个浏览器窗口,以便您可以同时查看窗体和表格。
<h1>Create</h1>
<asp:Label runat="server" ID="Status_Lbl" /><br />
Id: <asp:TextBox runat="server" ID="Id_Tb" /><br />
Name: <asp:TextBox runat="server" ID="Name_Tb" /><br />
Description: <asp:TextBox runat="server" ID="Description_Tb" /><br />
Quantity: <asp:TextBox runat="server" ID="Quantity_Tb" /><br />
Price: <asp:TextBox runat="server" ID="Price_Tb" /><br />
<asp:Button runat="server" ID="SubmitBtn" OnClick="SubmitBtn_Click" Text="Submit" />
以及背后的代码:
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<BoardGame>)Application["BoardGameDatabase"];
db.Add(game);
Application["BoardGameDatabase"] = db;
//only for SignalR
/*var context = GlobalHost.ConnectionManager.GetHubContext<GameHub>();
context.Clients.All.addGame(game); */
}
SignalR
这是我最兴奋分享的答案,因为它代表了一个更简洁的实现,它是轻量级的,并且在当今的移动(数据受限(环境中运行良好。
多年来,有几种方法可以提供数据从服务器到客户端的"实时"推送(或推送数据的外观(。快速短轮询(类似于我基于 AJAX 的答案(、长轮询、永久帧、服务器发送事件和 WebSockets 是用于实现此目的的不同传输机制。SignalR 是一个抽象层,能够根据客户端和服务器的功能选择合适的传输机制。使用 SignalR 最好的部分是它很简单。您不必担心传输机制,并且编程模型易于理解。
我将定义一个 SignalR 中心,但只需将其留空。
public class GameHub : Hub
{
}
当我将数据添加到"数据库"时,我将运行以下代码。如果你读了这个问题,你会看到我在"创建"表格中注释掉了它。您需要取消注释。
var context = GlobalHost.ConnectionManager.GetHubContext<GameHub>();
context.Clients.All.addGame(game);
这是我的页面代码:
<h1>SignalR</h1>
<asp:Repeater runat="server" ID="BoardGameRepeater" ItemType="RealTimeDemo.Models.BoardGame">
<HeaderTemplate>
<table border="1">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</thead>
<tbody id="BoardGameTblBody">
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><%#: Item.Id %></td>
<td><%#: Item.Name %></td>
<td><%#: Item.Description %></td>
<td><%#: Item.Quantity %></td>
<td><%#: Item.Price %></td>
</tr>
</ItemTemplate>
<FooterTemplate></tbody></table></FooterTemplate>
</asp:Repeater>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="Scripts/jQuery-1.6.4.min.js"></script>
<script src="Scripts/jquery.signalR-2.1.1.min.js"></script>
<script src="signalr/hubs"></script>
<script type="text/javascript">
var hub = $.connection.gameHub;
hub.client.addGame = function (game) {
$("#BoardGameTblBody").append("<tr><td>" + game.Id + "</td><td>" + game.Name + "</td><td>" + game.Description + "</td><td>" + game.Quantity + "</td><td>" + game.Price + "</td></tr>");
};
$.connection.hub.start();
</script>
以及背后的代码:
protected void Page_Load(object sender, EventArgs e)
{
BoardGameRepeater.DataSource = Application["BoardGameDatabase"];
BoardGameRepeater.DataBind();
}
注意这里发生了什么。当服务器调用context.Clients.All.addGame(game);
时,它正在为连接到 GameHub 的每个客户端执行分配给hub.client.addGame
的函数。SignalR 负责为我连接事件,并自动将服务器上的game
对象转换为客户端上的game
对象。最重要的是,每隔几秒钟就不会有网络流量来回,因此它非常轻巧。
优势:
- 网络流量非常轻
- 易于开发,但仍然灵活
- 不随请求一起发送视图状态
- 不连续轮询服务器。
请注意,您可以在客户端上添加一个函数,用于editedGame
轻松地将更改的数据推送到客户端(删除也是如此(。
Timer/UpdatePanel
如果您使用的是 Web 窗体,则可以使用称为 UpdatePanel 的控件。UpdatePanel 能够异步刷新页面的各个部分,而不会导致整个页面的回发。结合 asp:Timer,您可以根据需要随时更新表。代码如下:
<asp:ScriptManager runat="server" />
<h1>Board Games (using Update Panel)</h1>
<asp:Timer runat="server" ID="UP_Timer" Interval="5000" />
<asp:UpdatePanel runat="server" ID="Game_UpdatePanel">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="UP_Timer" EventName="Tick" />
</Triggers>
<ContentTemplate>
<asp:Repeater runat="server" ID="BoardGameRepeater" ItemType="RealTimeDemo.Models.BoardGame">
<HeaderTemplate>
<table border="1">
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><%#: Item.Id %></td>
<td><%#: Item.Name %></td>
<td><%#: Item.Description %></td>
<td><%#: Item.Quantity %></td>
<td><%#: Item.Price %></td>
</tr>
</ItemTemplate>
<FooterTemplate></table></FooterTemplate>
</asp:Repeater>
</ContentTemplate>
</asp:UpdatePanel>
以及背后的代码:
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.
优势:
- 易于实施。只需添加一个计时器、一个脚本管理器,然后将转发器包装在更新面板中即可。
弊:
- 重:视图状态随每个请求一起发送到服务器。但是,如果您禁用视图状态(无论如何您都应该这样做(,则可以减轻其影响。
- 繁重:无论数据是否更改,您每 5 秒通过该行发送一次所有数据。这是一大块带宽。
- 慢:每次部分回发都需要很长时间,因为所有数据都通过网络。
- 难以使用:当您开始添加更多功能时,正确处理部分回发可能会很棘手。
- 不智能:即使上一个请求没有完成,它也将继续回发,这要归功于计时器。
- 不智能:没有简单的方法来处理网络中断。
AJAX 轮询,更好的实现
与其他基于 AJAX 的答案类似,您可以持续轮询服务器。但这一次,我们将回复的数据列表,而不是使用要显示的数据进行响应。客户端将跟踪已经在数组中检索到的数据,然后在看到添加了新 ID 时向服务器发出单独的 GET 数据请求。
这是我们的页面代码:
<h1>Board Games (AJAX Polling Good)</h1>
<table id="BoardGameTbl" border="1">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</thead>
<tbody id="BoardGameTblBody">
</tbody>
</table>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript">
var loadedGames = [];
function getListOfGames() {
$.ajax({
type: "GET",
url: "api/GamesApi/GetGameIds",
dataType: "json"
})
.done(function (data) {
for (i = 0; i < data.length; i++) {
if (loadedGames.indexOf(data[i]) == -1) {
loadedGames[loadedGames.length] = data[i];
getGame(data[i]);
}
}
setTimeout(getListOfGames, 5000);
});
}
function getGame(id) {
$.ajax({
type: "GET",
url: "api/GamesApi/GetGame/" + id,
dataType: "json"
})
.done(function (game) {
$("#BoardGameTblBody").append("<tr><td>" + game.Id + "</td><td>" + game.Name + "</td><td>" + game.Description + "</td><td>" + game.Quantity + "</td><td>" + game.Price + "</td></tr>");
});
}
getListOfGames();
</script>
下面是 Web API 控制器:
namespace RealTimeDemo.Controllers
{
public class GamesApiController : ApiController
{
[Route("api/GamesApi/GetGameIds")]
public IEnumerable<int> GetGameIds()
{
var data = HttpContext.Current.Application["BoardGameDatabase"] as List<BoardGame>;
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<BoardGame>;
return data.Where(x => x.Id == id).SingleOrDefault();
}
}
现在,这是一个比我的其他基于 AJAX 的答案和计时器/更新面板答案更好的实现。由于我们每 5 秒才发送一次 ID,因此对网络资源的压力要小得多。处理没有网络连接的情况,或者在加载新数据时执行某种通知,例如抛出一个通知,这也是相当微不足道的。
优势
- 不随请求一起发送视图状态。
- 不执行整个页面生命周期
- 作为轮询的一部分,只有 ID 通过网络发送(如果您随请求发送时间戳,并且仅回复自时间戳以来更改的数据,则可以改进(。仅从数据库中检索新对象。
弊- 我们仍在轮询,每隔几秒钟生成一个请求。如果数据不经常更改,则不会不必要地耗尽带宽。
AJAX 轮询,实现不佳
如果使用 MVC 或 Web 窗体,则可以实现一种称为 AJAX 轮询的技术。这将不断向服务器发送 AJAX 请求。服务器将发送包含最新数据的响应。它的实现非常简单。你不必使用jQuery来使用AJAX,但它使它变得容易得多。此示例将使用 Web API 实现服务器端功能。Web API 类似于 MVC,它使用路由和控制器来处理请求。它是 ASMX Web 服务的替代品。
这是 Web 表单代码,但它与 MVC 代码非常相似,所以我将省略它:
<h1>Board Games (AJAX Polling Bad)</h1>
<table id="BoardGameTbl" border="1">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</thead>
<tbody id="BoardGameTblBody">
</tbody>
</table>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript">
function getData() {
$.ajax({
type: "GET",
url: "api/GamesApi/GetGameData",
dataType: "json"
})
.done(function (data) {
$("#BoardGameTblBody").empty();
for (i = 0; i < data.length; i++) {
$("#BoardGameTblBody").append("<tr><td>" + data[i].Id + "</td><td>" + data[i].Name + "</td><td>" + data[i].Description + "</td><td>" + data[i].Quantity + "</td><td>" + data[i].Price + "</td></tr>");
}
setTimeout(getData, 5000);
});
}
getData();
</script>
这是向 Web API 发出请求。API 返回所有游戏的 JSON 表示形式。
public class GamesApiController : ApiController
{
[Route("api/GamesApi/GetGameData")]
public IEnumerable<BoardGame> GetGameData()
{
var data = HttpContext.Current.Application["BoardGameDatabase"] as List<BoardGame>;
return data;
}
}
此方法的总体结果类似于 Timer/UpdatePanel 方法。但它不会随请求一起发送任何视图状态数据,也不会执行较长的页面生命周期过程。您也不必四处奔波检测您是否处于回发状态,或者是否处于部分回发状态。所以我认为这是对计时器/更新面板的改进。
但是,此方法仍然具有计时器/更新面板方法的主要缺点之一。您仍然通过每个 AJAX 请求通过网络发送所有数据。如果您查看我另一个基于 AJAX 的答案,您将看到实现 AJAX 轮询的更好方法。
优势
- 不随请求一起发送视图状态。
- 不执行整个页面生命周期
弊
- 每隔几秒钟生成一个请求
- 响应包括所有数据,即使它没有更改