Подтвердить что ты не робот

Как реализовать данные в реальном времени для веб-страницы

(Это задается как вопрос стиля 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. Я собираюсь засеять его в моей функции Application_Start моего 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;

Если бы я использовал веб-формы, я бы отображал данные с помощью ретранслятора.

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

Если это MVC с помощью Razor, это просто простой 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>

Пусть веб-формы имеют небольшую страницу для добавления данных, поэтому мы можем наблюдать за обновлением данных в режиме реального времени. Я рекомендую вам создать два окна браузера, чтобы вы могли видеть форму и таблицу одновременно.

<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); */           
    }
4b9b3361

Ответ 1

SignalR

Это ответ, который я очень рад поделиться, потому что он представляет собой более чистую реализацию, которая является легкой и хорошо работает в сегодняшней мобильной (сжатой) среде.

На протяжении многих лет существовало несколько способов обеспечить "в реальном времени" передачу данных с сервера клиенту (или появление push-данных). Rapid Short Polling (аналогично моим ответам на AJAX), Long Polling, 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);, он выполняет функцию, назначенную hub.client.addGame для каждого клиента, подключенного к GameHub. SignalR занимается подготовкой событий для меня и автоматически конвертирует мой объект game на сервер в объект game на клиенте. И лучше всего, нет сетевого трафика назад и вперед каждые несколько секунд, поэтому он невероятно легкий.

Преимущества:

  • Очень светлый сетевой трафик
  • Легко развивается, но все же гибко
  • Не отправляет viewstate с запросом
  • Не проводит опрос на сервере непрерывно.

Заметьте, вы могли бы добавить функцию на клиенте для editedGame для простого сбрасывания измененных данных клиенту (то же самое для удаления).

Ответ 2

Таймер /UpdatePanel

Если вы используете веб-формы, вы можете использовать элемент управления, называемый 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, поэтому происходит частичная обратная передача, весь жизненный цикл страницы запускается снова, поэтому он перезагружает данные в событии Page Load, а затем все содержимое шаблона содержимого UpdatePanel заменяется свежим генерируемые данные с сервера. Посмотрите, как может выглядеть сетевой трафик:

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

Преимущества:

  • Простота реализации. Просто добавьте таймер, менеджер script и заверните ретранслятор в панель обновления.

Недостатки:

  • Тяжелый: ViewState отправляется на сервер с каждым запросом. Воздействие этого может быть облегчено, если вы отключите ViewState (что вы должны делать в любом случае).
  • Тяжело: изменились ли данные или нет, вы отправляете все данные по линии каждые 5 секунд. Это огромный кусок полосы пропускания.
  • Slow: занимает много времени с каждой частичной обратной передачей, поскольку все данные проходят через провод.
  • Трудно работать с: Когда вы начинаете добавлять дополнительные функции, может быть сложно правильно обрабатывать частичные обратные копии.
  • Не разумно: даже если предыдущий запрос не завершился, он продолжит отправку назад с помощью таймера.
  • Не разумно: нет простого способа борьбы с прерыванием сети.

Ответ 3

Опрос AJAX, лучшая реализация

Как и в случае с другим ответом на основе AJAX, вы можете постоянно опросить сервер. Но на этот раз вместо ответа на данные, которые нужно отобразить, мы ответим списком идентификаторов данных. Клиентская сторона будет отслеживать данные, которые они уже извлекли в массиве, затем добавит отдельный запрос 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>

Здесь контроллер веб-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 и ответ Таймера /UpdatePanel. Поскольку мы отправляем идентификатор только каждые 5 секунд, это намного легче на сетевые ресурсы. Также было бы довольно тривиально обращаться с ситуациями сетевого подключения или выполнять какое-то уведомление при загрузке новых данных, например, вызывать noty.

Преимущества

  • Не отправляет viewstate с запросом.
  • Не выполняет весь жизненный цикл страницы
  • Только идентификатор отправляется через провод (может быть улучшен, если вы отправили временную метку с запросом и только ответили данными, которые были изменены с момента времени) в качестве части опроса. Из базы данных извлекаются только новые объекты.

Недостатки - Мы все еще проводим опрос, генерируя запрос каждые несколько секунд. Если данные не изменяются очень часто, вы бесполезно используете пропускную способность.

Ответ 4

Опрос AJAX, плохая реализация

Если вы используете MVC или Web Forms, вы можете реализовать метод, называемый опросом AJAX. Это будет постоянно отправлять запрос AJAX на сервер. Сервер отправит ответ, содержащий самые последние данные. Это невероятно просто реализовать. Вам не нужно использовать jQuery для использования AJAX, но это намного упрощает. Этот пример будет использовать Web API для функциональности на стороне сервера. Web API похож на MVC, он использует маршрутизацию и контроллеры для обработки запросов. Это замена ASMX Web Services.

Это код веб-форм, но он очень похож на код 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>

Это запрос к веб-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. Вы по-прежнему отправляете все данные по кабелю с каждым запросом AJAX. Если вы посмотрите на мой другой ответ на AJAX, вы увидите лучший способ для проведения опроса AJAX.

Преимущества

  • Не отправляет viewstate с запросом.
  • Не выполняет весь жизненный цикл страницы

Недостатки

  • Создает запрос каждые несколько секунд
  • Ответ включает все данные, даже если он не изменился