65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 SignalR 构建幻灯片应用

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2017年5月24日

CPOL

10分钟阅读

viewsIcon

12718

downloadIcon

375

使用 SignalR 创建一个幻灯片应用程序,它可以异步更新所有 Web 客户端,以便他们看到你正在看的相同幻灯片。

引言

多年来,我参加了许多幻灯片演示,其中有本地和远程与会者。我见过的演示者总是使用 Microsoft PowerPoint,我注意到远程用户总是不得不自己翻页。

注意:本文将比初学者文章进展得更快。如果您想了解 SignalR 的逐步入门,请参阅我在 CodeProject 上的另一篇文章:通过 ASP.NET 使用 SignalR 的初学者指南[^]

这个想法的起因

为什么不能有一种方法让每个人都访问一个特定的 URL,然后当主持人翻到下一张幻灯片时,所有观众也能看到新幻灯片呢?我知道你可以使用 Goto Meeting 或其他工具,但许多人没有 WebEx 或 GoTo Meeting,而且这些工具有时很难设置。

也许现在 PowerPoint 中有办法做到这一点,我不确定。然而,这就是我这个想法背后的动机。

主要思想

使用 SignalR,我们可以创建这样一个网站。在这个逐步讲解中,我们将创建一个应用程序,允许您实时更改远程用户看到的图像。用户所要做的就是访问您的 URL,然后每次您提供图像 URL 并点击广播按钮时,她都会在她的浏览器中看到新图像。

快速示例

这是一个通过动画 GIF 展示的例子,显示了三个不同的浏览器(Chrome、Firefox、Edge)都在查看相同的 URL。用户只需提供一个图像的 URL 并点击 [广播] 按钮,所有其他浏览器都会更新。请仔细观察,因为它很快。(我预先将图像的 URL 粘贴到文本框中,以缩短动画 GIF 的时间。)

slideshow overview

背景

一旦我知道我想做什么,我想知道的第一件事是更新 <img> 标签的 src 属性会有多困难。使用 jQuery,这非常容易。

我写了一个快速示例来测试它。

获取代码示例 v000

首先,您可以获取代码并试用。只需下载本文顶部的 v000 版本代码。解压缩并将其放到本地驱动器上的某个文件夹中。双击 index.htm 文件,您的默认浏览器就会启动。这个版本几乎什么都没做。

它做什么?

最初,您会在页面上看到一个丑陋的测试图像(红色涂鸦 x)。当您点击该图像时,第二个丑陋的测试图像(蓝色 x)将加载。如果您再次点击该图像,它将恢复为第一个图像。

ugly red x ugly blue x

我警告过你它很丑。然而,它让我确定了我需要做的事情(更新 <img> 标签的 src 属性是可能的并且容易)。我们来看看代码,但首先请允许我说明为什么项目是这样构建的。

Visual Studio 项目结构

在我写上一篇文章(通过 ASP.NET 使用 SignalR 的初学者指南[^])期间,我了解了 Visual Studio 为空项目创建的项目结构,所以我从上一篇文章的项目中“偷”了它。这让我能够创建一个 ready to be copied into my final ASP.NET project that supports SignalR 的 index.htm。我在这里的重点是为什么 jQuery 文件位于 /Scripts 目录中。项目结构如下:

project structure

不那么有趣

我知道这并不是非常有趣,但了解这些事情可以使您的生活更轻松。现在我可以将我的 index.htm 放入我的 ASP.NET 项目中(我将在本文后面这样做),它将正确引用 jQuery 脚本。好了,说够了,我们来看看 index.htm 和更新图像的简单 jQuery。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Slideshow</title>
    <link rel="stylesheet" href="css/main.css" />
    <script src="Scripts/jquery-1.6.4.min.js"></script>
    <script src="Scripts/jquery.signalR-2.2.2.min.js"></script>
    <script src="signalR/hubs"></script>
</head>
<body>
    <img src="assets/1_local.png" id="slides" onclick="setImageSource()"/>

    <script src="js/slideshow.js"></script>
</body>
</html>

您可以看到这基本上只是一个引用所有脚本的骨架。只有一个 <img> 标签,您可以看到如果用户点击 <img>,我们就会调用一个名为 setImageSource() 的 JavaScript 方法。

以下是 slideshow.js 中的代码

function setImageSource(sourcePath) {
    if (sourcePath === undefined) {
        if ($("#slides").attr("src") == "assets/1_local.png") {
            $("#slides").attr("src", "assets/2_local.png");
        }
        else {
            $("#slides").attr("src", "assets/1_local.png");
        }
    }
    else {
        $("#slides").attr("src", sourcePath);
    }
}

脚本做的第一件事是检查 sourcePath 是否已定义。在我们这里,它没有定义,因为我们的方法没有传入任何东西。这意味着这个外部 if 语句在这个应用程序中将始终被评估为 true(我们的 SignalR 应用程序稍后将使用它传入图像的 URL)。

接下来我们看到的是如何使用 jQuery 获取图像 src 的值。这非常容易。我们使用 jQuery 选择器来选择我们的 <img> 标签的 id,即“slides”。我们使用名为 attr() 的 jQuery 方法,传入 src 的值。在 if 语句中,我们正在检查 src 值是否已经等于第一个图像。如果是,我们只需使用相同的 attr() 方法将 src 值设置为我们的第二个图像,该图像位于我们的 assets 目录中 (2_local.png)。

这意味着什么?

就是这样。我已经证明我们可以更新 src 值并实时看到图像变化(当用户点击图像时)。

既然我知道我可以这样做,我现在也知道我可以使用 SignalR 将 sourcePath 广播到其他浏览器。这意味着我将能够设置图像 src 值,当我这样做时,所有其他正在查看我的 URL 的人也会在他们的浏览器中看到新图像。这是一种幻灯片,我可以用它来异步或实时控制其他人在我页面上看到的内容。

让我们将此代码转换为 SignalR 应用程序。

设置 SignalR 项目

我将让您从本文顶部下载已完成的项目,然后我将解释代码,因为它非常简单。如果您想知道如何逐步完成 Visual Studio 并设置此项目,请参阅我之前几次提到的第一篇文章。

我有代码了:下载 v001

从文章顶部获取 v001 版本的代码,在 Visual Studio 中构建它(我的是在 VStudio 2017 上构建的)并运行它。

完成此操作后,您将在浏览器中看到以下页面。

signalR app - initial view

是的,它仍然有点丑,并显示了那个丑陋的原始图像。但是,请注意它现在包含一个 <input> 文本框和一个 <button>

现在您可以将任何 URL(指向图像)添加到文本框中,然后点击 广播 按钮,任何正在查看该页面的人都会在他们的浏览器中看到它。您可以打开两个浏览器窗口到您的 localhost URL 并尝试一下。如果您加载相同的马里奥和路易吉图像,它会是这样的。:)

mario and luigi

我稍后会展示使其工作的代码。但现在,您只需明白,当您点击 广播 按钮时,它会将 URL 发送到所有远程浏览器。这反过来又会在远程浏览器中触发 setImageSource() 方法,并更新图像 src,远程用户就会看到您正在查看的图像。我认为这几乎是神奇的,但我是一个极客。您从 SignalR 获得了所有这些魔力。在测试过程中出现了一个有趣的副作用。

一个有趣的副作用

现在,这里变得更加有趣。如果您仔细查看上一张图片中文本框中的文本,您可能会注意到它以 data:image/jpeg;base64 开头。

如果您曾经使用过 HTML5 Canvas 元素,您可能已经注意到,如果您右键单击 Canvas,它可以从 Canvas 生成图像数据,并且它使用的就是这种确切的格式。它基本上是整个图像的 base64 编码。它相当大。

从 Google 获取图片

第一个有趣的事情

第一个有趣的事情是我只是通过在 Google 上搜索马里奥和路易吉的图像,然后在 Chrome 中右键单击并选择“复制图片地址”来获得这些图像。为什么尝试获取图片地址会给我整个图片的 base64 编码数据?我不确定这是 Chrome 还是 Google 的问题。

copy image address

第二个有趣的事情

它有效。有趣的是,由于您可以通过将 src 值设置为 base64 编码数据来将图像数据嵌入到 <img> 标签中,所以我的解决方案仍然有效。你看,当我添加该数据然后点击广播按钮时,我期望它是一个指向图像位置的 URL。然而,在这种情况下——由于浏览器或 Google 网站的原因——广播的数据是整个图像的 base64 编码数据,它仍然有效。它会慢一点,因为它数据量很大,但它确实有效。更多魔法!

当然,它也能很好地与普通 URL 配合使用。

让我们看看代码是如何工作的并总结一下。再次强调,如果您想了解 SignalR 工作的详细信息,请查看我的第一篇文章。

代码!

该项目中实现广播的主要类(一个 SignalR Hub)名为 SlideHub,它看起来像下面这样

using System;
using Microsoft.AspNet.SignalR;

namespace Slideshow
{
    public class SlideHub : Hub
    {
        public void Send(String sourcePath) 
        {
            // Call the broadcastMessage method to update clients.
            Clients.Others.broadcastMessage(sourcePath);
        }
    }
}

这就是为我们生成 JavaScript 的类,客户端将使用它来执行广播。当调用 Send() 方法时,SignalR API 方法 broadcastMessage() 被触发。我想在这里指出的一点是,我们在 Others 集合对象上调用该方法。这将消息广播给所有其他浏览器,除了点击按钮的那个浏览器。

我们可以在 Clients.All 集合对象上调用 broadcastMessage,然后我们的本地客户端也会收到消息。然而,通过 JavaScript / jQuery 在本地更新 src 并仅广播给 Others 会更快,所以我就是这样实现的。

最后,这是整个 JavaScript 实现,它在很大程度上是不言自明的——特别是如果您已经阅读了我的第一篇文章。 :)

//slideshow.js
var slideR = null;

$(document).ready(function () {
    slideR = $.connection.slideHub;
    slideR.client.broadcastMessage = function (sourcePath) {
        setImageSource(sourcePath);
    };

    $.connection.hub.start().done(function () {
        console.log("Hub is started.");
    });
});

function setImageSource(sourcePath) {
    if (sourcePath === undefined) {
        if ($("#slides").attr("src") == "assets/1_local.png") {
            $("#slides").attr("src", "assets/2_local.png");
        }
        else {
            $("#slides").attr("src", "assets/1_local.png");
        }
    }
    else {
        $("#slides").attr("src", sourcePath);
    }
}

function broadcastMessage() {
    setImageSource($('#sourcePath').val());
    slideR.server.send($('#sourcePath').val());
}

$(document).ready() 只是 jQuery 确保代码在整个 index.htm 加载后运行的方式。该代码只是为我们初始化 SignalR。

您可以看到我们将 broadcastMessage 初始化为一个匿名函数,该函数使用传入的 sourcePath 调用 setImageSource()。这就是来自文本框的值。

您可以看到,当 broadcastMessage() 函数运行(当用户点击 [广播] 按钮时),它会在本地调用 setImageSource(),然后通过调用 send() 方法广播消息。

真的就这么简单。

关注点

我以前从未有过一个有趣的观点,但现在有了。您可以看到我并没有将这个特定的项目作为实时应用程序发布到网络上供大家尝试。通常我喜欢这样做。然而,那将意味着任何导航到该 URL 并添加图片(任何图片——恐怖!!!)的人都能够将其广播给所有正在查看我的生产 URL 的其他人。我对此根本不感兴趣。:) 坦率地说,这让我感到害怕。

历史

文章和代码的首次发布:2017 年 5 月 24 日

© . All rights reserved.