web-dev-qa-db-fra.com

Comment implémenter des données en temps réel pour une page Web

(Ceci est conçu comme une question de style Q/A, destiné à être une ressource de référence pour les personnes qui posent des questions similaires. Beaucoup de gens semblent trébucher sur la meilleure façon de le faire parce qu'ils ne connaissent pas toutes les options La plupart des réponses seront spécifiques à ASP.NET, mais AJAX et d'autres techniques ont des équivalents dans d'autres cadres, tels que socket.io et SignalR.)

J'ai une table de données que j'ai implémentée dans ASP.NET. Je souhaite afficher les modifications de ces données sous-jacentes sur la page en temps réel ou presque en temps réel. Comment dois-je procéder?

Mon modele:

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;
        }
    }

Au lieu d'une base de données réelle pour cet exemple, je vais simplement stocker les données dans la variable Application. Je vais le semer dans mon Application_Start fonction de mon Global.asax.cs.

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;

Si j'utilisais des formulaires Web, j'afficherais les données avec un répéteur.

<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>

Et chargez ces données dans le code derrière:

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

S'il s'agissait de MVC utilisant Razor, ce n'est qu'un simple aperçu du modèle:

@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>

Utilisons les formulaires Web pour avoir une petite page pour ajouter des données afin que nous puissions ensuite regarder la mise à jour des données en temps réel. Je vous recommande de créer deux fenêtres de navigateur afin que vous puissiez voir le formulaire et le tableau en même temps.

<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" />

Et le code derrière:

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); */           
    }
17
mason

SignalR

C'est la réponse que je suis le plus heureux de partager, car elle représente une implémentation beaucoup plus propre, légère et qui fonctionne bien dans l'environnement mobile (données restreintes) d'aujourd'hui.

Il y a eu plusieurs méthodes au fil des ans pour fournir une poussée "en temps réel" des données du serveur au client (ou l'apparence de pousser des données). Sondage court rapide (similaire à mes AJAX), Sondage long , Forever Frame , Server Sent Events =, et WebSockets sont différents mécanismes de transport utilisés pour y parvenir. SignalR est une couche d'abstraction capable de sélectionner un mécanisme de transport approprié en fonction des capacités du client et du serveur. La meilleure partie d'utiliser SignalR est simple: vous n'avez pas à vous soucier du mécanisme de transport et le modèle de programmation est facile à comprendre.

Je vais définir un concentrateur SignalR, mais laissez-le simplement vide.

public class GameHub : Hub
    {
    }

Lorsque j'ajoute des données à la "base de données", je vais exécuter le bit de code ci-dessous. Si vous lisez la question, vous verrez que je l'ai commentée dans le formulaire "créer". Vous voudrez commenter cela.

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

Voici mon code de page:

<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>

Et le code derrière:

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

Remarquez ce qui se passe ici. Lorsque le serveur appelle context.Clients.All.addGame(game);, il exécute la fonction qui a été affectée à hub.client.addGame Pour chaque client connecté au GameHub. SignalR s'occupe de câbler les événements pour moi et de convertir automatiquement mon objet game sur le serveur en l'objet game sur le client. Et surtout, il n'y a pas de trafic réseau dans les deux sens toutes les quelques secondes, il est donc incroyablement léger.

Avantages:

  • Très léger sur le trafic réseau
  • Facile à développer, mais toujours flexible
  • N'envoie pas de vue avec la demande
  • N'interroge pas le serveur en continu.

Remarque, vous pouvez ajouter une fonction sur le client pour editedGame pour pousser facilement les données modifiées vers le client (de même pour la suppression).

19
mason

Timer/UpdatePanel

Si vous utilisez des formulaires Web, vous pouvez utiliser un contrôle appelé UpdatePanel. Le UpdatePanel est capable d'actualiser les sections de la page de manière asynchrone, sans provoquer de publication de la page entière. Combiné avec un asp: Timer, vous pouvez avoir la mise à jour de la table aussi souvent que vous le souhaitez. Voici le code:

<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>

Et le code derrière:

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

Parlons donc de la façon dont celui-ci fonctionne. Toutes les 5 secondes, la minuterie va déclencher un événement Tick. Ceci est enregistré en tant que serveur de publication asynchrone avec UpdatePanel, donc une publication partielle se produit, le cycle de vie de la page entière s'exécute à nouveau, donc il recharge les données sur l'événement de chargement de page, puis le contenu entier du modèle de contenu de UpdatePanel est remplacé par fraîchement généré des données à partir du serveur. Voyons à quoi pourrait ressembler le trafic réseau:

+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.

Avantages:

  • Simple à mettre en œuvre. Ajoutez simplement une minuterie, un gestionnaire de scripts et enveloppez le répéteur dans un panneau de mise à jour.

Désavantages:

  • Lourd: le ViewState est envoyé au serveur avec chaque demande. L'impact de ceci peut être allégé si vous désactivez ViewState cependant (ce que vous devriez faire quand même).
  • Lourd: que les données aient changé ou non, vous envoyez toutes les données sur la ligne toutes les 5 secondes. C'est une énorme bande passante.
  • Lent: prend beaucoup de temps avec chaque publication partielle, car toutes les données passent par le fil.
  • Difficile à travailler: lorsque vous commencez à ajouter plus de fonctionnalités, il peut être difficile de gérer correctement les publications partielles correctement.
  • Pas intelligent: même si la demande précédente n'a pas abouti, elle continuera de s'afficher grâce au minuteur.
  • Pas intelligent: pas de moyen facile de gérer l'interruption du réseau.
3
mason

Polling AJAX, meilleure implémentation

Semblable à l'autre AJAX réponse basée, vous pouvez continuellement interroger le serveur. Mais cette fois, au lieu de répondre avec les données à afficher, nous allons répondre avec une liste des ID des Le côté client va garder une trace des données qu'il a déjà récupérées dans un tableau, puis il fera une demande GET distincte au serveur pour les données lorsqu'il verra qu'un nouvel ID a été ajouté.

Voici notre code de page:

<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>

Voici le Web API Controller:

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();
        }
    }

Maintenant, c'est une implémentation bien meilleure que mon autre AJAX et la réponse Timer/UpdatePanel. Puisque nous n'envoyons les identifiants que toutes les 5 secondes, c'est une pression beaucoup plus légère sur le réseau Il serait également assez banal de ne gérer aucune situation de connexion réseau, ou d'exécuter une sorte de notification lorsque de nouvelles données ont été chargées, comme lancer un noty .

Les avantages

  • N'envoie pas viewstate avec demande.
  • N'exécute pas le cycle de vie de la page entière
  • Seuls les identifiants sont envoyés sur le câble (pourraient être améliorés si vous avez envoyé un horodatage avec la demande et que vous n'avez répondu qu'avec des données modifiées depuis l'horodatage) dans le cadre de l'interrogation. Seuls les nouveaux objets sont récupérés de la base de données.

Inconvénients - Nous continuons d'interroger, générant une demande toutes les quelques secondes. Si les données ne changent pas très souvent, vous utilisez inutilement de la bande passante.

2
mason

Sondage AJAX, mauvaise mise en œuvre

Si vous utilisez MVC ou Web Forms, vous pouvez implémenter une technique appelée AJAX. Cela enverra constamment une requête AJAX) au serveur. Le serveur envoyer une réponse contenant les dernières données. C'est incroyablement simple à implémenter. Vous n'avez pas besoin d'utiliser jQuery pour utiliser AJAX, mais cela le rend beaucoup plus facile. Cet exemple va utiliser - API Web pour la fonctionnalité côté serveur. L'API Web est similaire à MVC, elle utilise le routage et les contrôleurs pour traiter les demandes. C'est le remplacement de ASMX Web Services .

Ceci est le code des formulaires Web, mais il est très similaire au code MVC, je vais donc omettre cela:

<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>

Cela fait une demande à une API Web. L'API renvoie une représentation JSON de tous les jeux.

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

Le résultat global de cette méthode est similaire à la méthode Timer/UpdatePanel. Mais il n'envoie aucune donnée de l'état d'affichage avec la demande, et il n'exécute pas un processus de cycle de vie de longue page. Vous n'avez pas non plus à danser pour détecter si vous êtes dans une publication ou non, ou si vous êtes dans une publication partielle ou non. Je considère donc cela comme une amélioration par rapport à Timer/UpdatePanel.

Cependant, cette méthode présente toujours l'un des principaux inconvénients de la méthode Timer/UpdatePanel. Vous envoyez toujours toutes les données sur le fil avec chaque AJAX demande. Si vous regardez mon autre AJAX réponse basée, vous verrez un meilleure façon de mettre en œuvre AJAX.

Les avantages

  • N'envoie pas de vue avec demande.
  • N'exécute pas le cycle de vie de la page entière

Désavantages

  • Génère une demande toutes les quelques secondes
  • La réponse inclut toutes les données, même si elles n'ont pas changé
1
mason