web-dev-qa-db-ja.com

Webページのリアルタイムデータを実装する方法

(これは、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<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フォームを使用して、データを追加するための小さなページを作成して、データの更新をリアルタイムで監視できるようにします。フォームとテーブルを同時に表示できるように、2つのブラウザウィンドウを作成することをお勧めします。

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

SignalR

これは私が共有することに最も興奮している答えです。軽量であり、今日のモバイル(データが制限された)環境で適切に機能する、はるかにクリーンな実装を表すからです。

サーバーからクライアントへの「リアルタイムの」データのプッシュ(またはデータのプッシュの外観)を提供する方法は、長年にわたっていくつかあります。高速ショートポーリング(my AJAXに基づく回答)と同様)、 ロングポーリングForever Frameサーバー送信イベント =、および 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の関数をクライアントに追加できます(削除の場合も同じ)。

19
mason

タイマー/ 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.

利点:

  • 実装が簡単です。タイマー、スクリプトマネージャーを追加し、リピーターを更新パネルでラップするだけです。

短所:

  • 重い:ViewStateはすべてのリクエストでサーバーに送信されます。ただし、ViewStateを無効にすると(これはとにかく行う必要があります)、この影響を軽減できます。
  • ヘビー:データが変更されたかどうかに関係なく、すべてのデータを5秒ごとに回線経由で送信しています。これは非常に大きな帯域幅です。
  • 低速:すべてのデータがネットワークを経由するため、部分的なポストバックごとに長い時間がかかります。
  • 作業が難しい:機能を追加し始めるときに、部分的なポストバックを正しく処理するのは難しい場合があります。
  • スマートではありません:前のリクエストが完了しなかった場合でも、タイマーのおかげで投稿は続行されます。
  • スマートではない:ネットワークの中断を処理する簡単な方法はありません。
3
mason

AJAXポーリング、より良い実装

他のAJAX=ベースの回答と同様に、サーバーを継続的にポーリングできます。ただし、今回は、表示するデータで応答する代わりに、IDのリストで応答します。データ:クライアント側は、既に取得したデータを配列で追跡し、新しい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=ベースの回答とTimer/UpdatePanel回答よりもはるかに優れた実装です。5秒ごとにIDを送信するだけなので、ネットワークへの負荷がはるかに軽くなります。また、ネットワーク接続がない場合や、新しいデータが読み込まれたときに noty をスローするなどの通知を実行するのも簡単です。

メリット

  • リクエストでビューステートを送信しません。
  • ページのライフサイクル全体を実行しない
  • ポーリングの一部として、IDのみがネットワーク経由で送信されます(リクエストと共にタイムスタンプを送信し、タイムスタンプ以降に変更されたデータのみで応答した場合に改善される可能性があります)。新しいオブジェクトのみがデータベースから取得されます。

短所-ポーリングを続け、数秒ごとにリクエストを生成します。データが頻繁に変更されない場合は、帯域幅を無駄に使用していることになります。

2
mason

AJAXポーリング、貧弱な実装

MVCまたはWebフォームを使用している場合、AJAXポーリングと呼ばれる手法を実装できます。これにより、AJAXリクエストがサーバーに常に送信されます。サーバーは最新のデータを含む応答を送信します。実装は非常に簡単です。AJAXを使用するために jQuery を使用する必要はありませんが、これにより、はるかに簡単になります。この例では、- Web API サーバー側の機能です。WebAPIは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メソッドに似ています。しかし、リクエストとともにビューステートデータを送信せず、長いページライフサイクルプロセスを実行しません。また、ポストバックにいるかどうか、または部分的なポストバックにいるかどうかを検出するために踊る必要もありません。したがって、これはTimer/UpdatePanelよりも優れていると考えています。

ただし、このメソッドには、Timer/UpdatePanelメソッドの主要な欠点の1つがまだあります。 AJAXリクエストごとにすべてのデータを有線で送信しています。他のAJAXベースの回答を見ると、 AJAXポーリングを実装するためのより良い方法。

メリット

  • リクエストでビューステートを送信しません。
  • ページのライフサイクル全体を実行しない

短所

  • 数秒ごとにリクエストを生成します
  • 変更されていない場合でも、応答にはすべてのデータが含まれます
1
mason