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

ASP.NET SignalR 入门指南

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (13投票s)

2017年5月22日

CPOL

19分钟阅读

viewsIcon

72869

downloadIcon

2851

学习使用 SignalR 通过 SignalR 广播异步更新所有 Web 客户端。

引言

我一直在想使用 SignalR 会有多么容易,所以我决定完成微软的一个快速入门教程。在做的过程中,我遇到了一些挑战,所以我想尝试自己解释 SignalR 的工作原理。

主要重点:更新远程客户端

使用 SignalR 这样的技术,其根本目的就是更新远程客户端。我想做一个简单的例子,让读者可以看到它是如何工作的。很久以前,我使用 Firebase(稍后会详细介绍)创建了一个简单的例子,该例子

  1. 允许用户在 HTML5 Canvas 上抓取和移动棋子
  2. 更新所有客户端,以便远程用户可以在他们的浏览器中看到棋子移动

这是一个简单的例子。左边的浏览器是 Google Chrome,右边的是 Microsoft Edge。它们都指向同一个 URL,也就是我托管在 SmarterASP.net - Unlimited ASP.NET Web Hosting[^] 上的网站。我在那里获得了 60 天的免费试用,您也可以获得,无需信用卡。稍后将详细介绍我为什么使用 SmarterASP.net。

main signal example

您可以立即尝试

只需打开两个不同的浏览器窗口,并将它们指向我的 SmarterASP.net 网站:http://http://raddevus-001-site1.btempurl.com/Pawns/[^]

接下来,在任何一个浏览器窗口中移动一个棋子,它都会在另一个窗口中移动。

Firebase 对比

我已经将本文中向您展示的应用作为 Firebase[^] 应用编写过了。您可以在以下位置看到该示例:

http://uncoveryourlife.com/html5/pawns/pawns.htm[^] 这个运行在我的 GoDaddy.com 网站上。稍后将详细介绍我尝试让 GoDaddy 托管 SignalR 应用时遇到的问题。

我曾广泛使用 Firebase,因此我非常有兴趣了解 SignalR 与之相比如何

  1. 开发便捷性/速度
  2. 工作效果 - 更新远程客户端和性能(更新远程客户端的速度)

Firebase(响应更灵敏)

如果您同时尝试了两者,我相信您会发现 Firebase 的响应速度要快得多。您可以看到,即使在上方的示例 GIF 中,也存在相当大的延迟,该 GIF 是从为本文编写的 SignalR 版本中录制的。

既然您已经看到了它的效果,让我们在 Visual Studio 中设置我们的 SignalR 项目并开始吧。

Visual Studio ASP.NET 项目

正如我之前所说,我是通过完成微软的一个教程来学习 SignalR 的。您可以在以下位置找到该教程:教程:SignalR 2 入门 | Microsoft Docs[^]。然而,那篇文章存在一些挑战。

  1. 这是一篇较旧的文章,使用 Visual Studio 2013
  2. 它会让您通过 Nuget 添加 Owin 包,然后单独添加 SignalR 包。但现在在 Visual Studio 2015(或本文中我使用的 2017)中,情况略有不同。我将向您展示。

启动 Visual Studio 并创建一个新项目。

在左侧选择“Web”项目类型,然后在右侧选择“ASP.NET Web 应用程序 (.NET Framework)”。

new project

将项目命名为“pawns”以保持简单,然后单击“[OK]”按钮。

将出现一个对话框。此项目不需要太多内容,并且我们将使用 Nuget 来添加 SignalR 库,因此请选择“Empty Project”并单击“[OK]”按钮。

empty project

创建模板项目后,选择 Visual Studio 顶部的 **工具**… 菜单项。

向下滚动到 **Nuget 包管理器** 菜单项。将弹出另一个菜单。**选择“为解决方案管理 Nuget 包”**… 菜单项。

start nuget

Visual Studio 中将打开一个非常丑陋的窗口。在我的电脑上,窗口非常小,几乎看不清任何东西。微软在 UI/UX 方面算不上特别出色,对吧?

nuget ugly

默认情况下,顶部的“已安装”项将被选中。请继续

  1. 单击“浏览”项
  2. 单击搜索编辑框内部
  3. 键入“signalr”以搜索 SignalR 库。
  4. 您应该会在 Nuget 窗口的底部看到一些 SignalR 选项。
  5. (单击)第一个显示的选项 — 应该是 `Microsoft.AspNet.SignalR`

nuget needs to be larger

此时,我不得不移动滑块条,以便真正看到屏幕上需要的内容。如果您需要,请继续操作,因为在右侧,我们需要单击一个我们现在甚至看不到的按钮,以便添加 SignalR 库。

  1. 选中“Pawns”项目旁边的复选框。(这将启用“[Install]”按钮,以便您可以单击它。)
  2. 单击“[Install]”按钮将 SignalR 库添加到项目中。

select pawns project - nuget

将弹出一个对话框,显示将添加到您项目的所有库的预览。

preview libraries

单击“[OK]”按钮,所有库都将被添加。**注意**:还会弹出一个许可证接受对话框,以确保您接受使用许可证。

Nuget 处理依赖项

在微软的教程中,他们会让您单独添加 OWIN 库,但现在您实际上不需要这样做,因为 Nuget 会为您添加所有依赖项。

获取源代码 - v001

如果您在项目创建过程中遇到任何问题,可以下载本文顶部的 v001 zip 文件并解压缩,您就可以全部准备好了。当然,我已经删除了下载的 nuget 包,所以 zip 文件只是源代码,但您所要做的就是从 Visual Studio 恢复包,然后就可以继续阅读本文了。

完成以上所有步骤后,您就拥有了在项目中使用的 **SignalR** 的所有必需条件。

现在,让我们添加一些代码。

添加新的 HTML 页面

在微软的教程中,作者告诉您的第一件事是添加一个实现 SignalR 行为的新类。但是,我喜欢边做边构建,以便了解它们是如何协同工作的。所以,首先,我们将设置将用作我们应用的 UI 的 HTML 页面。

继续向您的项目中添加一个新的 Web 页面。您只需在解决方案资源管理器中右键单击您的解决方案。接下来,向下滚动到**添加**菜单项。最后选择 **HTML 页面** 菜单项。

add new html page

将弹出一个对话框。在其中键入文件名 `index.htm` 并单击“[OK]”按钮。是的,我使用了 3 个字母的扩展名,而不是完整的 4 个字母的 HTML。

index.htm

当您添加文件时,Visual Studio 将添加一个基本的 HTML 文件框架,该文件只会在用户面前显示一个空白页面。当然,对我们来说,我们希望在蓝色网格背景上显示三个不同颜色的棋子。

让我们通过用以下代码替换 Visual Studio 为我们提供的框架来修改它。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>pawns</title>

</head>
<style>
    body, html {
        margin: 0;
        padding: 0;
    }
</style>
<body>
    <img style="visibility:hidden;display:none;" src="assets/redBlueGreenPawn.png" id="allPawns" />
    <canvas id="gamescreen">You're browser does not support HTML5.</canvas>
    
</body>
</html>

通常,我会将 CSS(层叠样式表)数据保存在一个单独的文件中,但我正在尝试简化本教程,而且实际上只有一个样式可以删除任何边距或填充,所以我已将其添加到我们的 index.htm 中。

图像资源

接下来,您会看到我引用了一个您没有的图像。您可以在此处看到它,如果喜欢,可以下载它。

red green blue pawn

您可以看到,即使有三个独立的可移动棋子,图像本身实际上是一个 PNG 文件。这是因为您可以轻松地将图像的一部分作为单独的 HTML5 Canvas 对象通过 Canvas API 进行引用。

添加资产文件夹

我还会向项目中添加一个名为 `assets` 的新文件夹,并将 `redGreenBluePawn.png` 文件放在该文件夹中,使其成为项目的一部分。您将在下一个(v002)下载中获得该文件。

最后,我们设置了一个 `Canvas` 元素,我们的网格和棋子将在此绘制。

真正的功劳归于 JavaScript

然而,真正的工作是由 JavaScript 完成的。我知道你们很多人对此的感受,但这就是 Web 的运作方式,所以请习惯它。 :)

我们需要添加一些对 Visual Studio 项目提供的脚本的引用。Visual Studio 允许您拖放项目以引用它们。我们现在需要添加一些对 jQuery 库的引用。以下是您如何在 Visual Studio 中做到这一点。

添加 JavaScript 引用

在解决方案资源管理器中展开 `\Scripts` 文件夹,您将看到 Visual Studio 为您添加的 JavaScript 文件列表。实际上,我认为 `Nuget` 添加了 `jQuery` 文件,因为它知道您需要它们来与 `SignalR` 一起使用。

要添加对其中任何一个的引用,只需单击它并将其拖到您的源文件中。当您松开鼠标时,它将按照下一个图像所示添加正确的 `script` 标签。

add scripts

继续添加对 `jquery.signalR-2.2.2.min.js` 的引用。这些是我们运行此程序所需的 JavaScript 库的最小化版本。

我喜欢将自定义 JavaScript 分开,所以我将添加一个文件夹并创建一个名为 `pawns.js` 的新 Javascript 文件。我将向您展示它,并让您自己找出如何做同样的事情。仔细查看我在 HTM 文件中添加 `pawns.js` 引用的位置。这有点重要,因为该代码引用了 Canvas,并且我们确保 Canvas 元素已经加载。

奇怪的 SignalR 引用

另外,请注意我在下面的 HTML 示例中加粗显示的那一行。这是一个有些奇怪的对不存在文件的引用。这是微软在教程中向您展示的引用 SignalR 的一部分。将要引用的代码将在运行时生成。这是因为代码是由 SignalR C# 库创建的。我们将在文章后面看到更多内容。现在,请确保添加该引用。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>pawns</title>
    <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>
<style>
    body, html {
        margin: 0;
        padding: 0;
    }
</style>
<body>
    <img style="visibility:hidden;display:none;" src="assets/redBlueGreenPawn.png" id="allPawns" />
    <canvas id="gamescreen">You're browser does not support HTML5.</canvas>
    <script src="js/pawns.js"></script>
</body>
</html>

获取代码 v002

您也可以下载本文顶部的 **v002** 代码,这样您就可以跟上进度,并准备编写绘制棋子和网格的代码了。

代码可以编译并运行,但显示的页面当然是完全空白的。让我们来看看如何绘制棋子和网格。

绘制棋子和网格

我将快速浏览大部分内容,因为它只是我想要讨论的主题的间接部分。

首先,我需要设置一些将要使用的变量,并设置 `load` 事件,该事件将在浏览器加载目标页面(index.htm)及其所有关联资源(图像和 JavaScript 文件)后触发。

// pawns.js

var ctx = null;
var theCanvas = null;
var firebaseTokenRef = null;

window.addEventListener("load", initApp);
var mouseIsCaptured = false;
var LINES = 20;
var lineInterval = 0;

var allTokens = [];

// hoverToken -- token being hovered over with mouse
var hoverToken = null;
var pawnR = null;

//$.on("mousemove", mouseMove

function token(userToken){

    this.size = userToken.size;
    this.imgSourceX = userToken.imgSourceX;
    this.imgSourceY = userToken.imgSourceY;
    this.imgSourceSize = userToken.imgSourceSize;
    this.imgIdTag = userToken.imgIdTag;
    this.gridLocation = userToken.gridLocation;
}

function gridlocation(value){
    this.x = value.x;
    this.y = value.y
}

我还添加了两个我创建的类型(`token` 和 `gridLocation`)以方便跟踪。稍后您将看到它们的用法。

当浏览器加载所有内容时,将运行 `initApp()` 函数。让我们来看看它。

function initApp()
{
    theCanvas = document.getElementById("gamescreen");
    ctx = theCanvas.getContext("2d");
    
    ctx.canvas.height  = 650;
    ctx.canvas.width = ctx.canvas.height;

    initBoard();
}

我们开始设置将在其中绘制网格的 Canvas。

接下来,我们调用 `initBoard()` 中的自定义代码。

function initBoard(){
    lineInterval = Math.floor(ctx.canvas.width / LINES);
    console.log(lineInterval);
    initTokens();
}

我使用 Canvas 宽度和线之间的距离来计算线间隔。之后,我调用 `initTokens()` 来准备绘制棋子(token)。

function initTokens(){
        
    if (allTokens.length == 0)
    {
        allTokens = [];

        var currentToken =null;
        // add 3 pawns
            for (var i = 0; i < 3;i++)
            {
                currentToken = new token({
                        size:lineInterval,
                        imgSourceX:i*128,
                        imgSourceY:0*128,
                        imgSourceSize:128,
                        imgIdTag:'allPawns',
                        gridLocation: new gridlocation({x:i*lineInterval,y:3*lineInterval})
                    });
                    allTokens.push(currentToken);    
            }
        console.log(allTokens);
    }
    draw();
}

在这里,我们只需确保 `allTokens` 数组被初始化为空。接下来,我们将所有 token 添加到数组中,同时引用 PNG 图像的正确部分。由于每个 token 的宽度为 128 像素,因此使用一些数学计算很容易实现。

最后,我们调用 `draw()` 函数,该函数会将所有图形绘制到我们的 Canvas 元素上。

function draw() {

    ctx.globalAlpha = 1;

    // fill the canvas background with white
    ctx.fillStyle = "white";
    ctx.fillRect(0, 0, ctx.canvas.height, ctx.canvas.width);

    // draw the blue grid background
    for (var lineCount = 0; lineCount < LINES; lineCount++) {
        ctx.fillStyle = "blue";
        ctx.fillRect(0, lineInterval * (lineCount + 1), ctx.canvas.width, 2);
        ctx.fillRect(lineInterval * (lineCount + 1), 0, 2, ctx.canvas.width);
    }
    // draw each token it its current location
    for (var tokenCount = 0; tokenCount < allTokens.length; tokenCount++) {

        drawClippedAsset(
            allTokens[tokenCount].imgSourceX,
            allTokens[tokenCount].imgSourceY,
            allTokens[tokenCount].imgSourceSize,
            allTokens[tokenCount].imgSourceSize,
            allTokens[tokenCount].gridLocation.x,
            allTokens[tokenCount].gridLocation.y,
            allTokens[tokenCount].size,
            allTokens[tokenCount].size,
            allTokens[tokenCount].imgIdTag
        );
    }
    // if the mouse is hovering over the location of a token, show yellow highlight
    if (hoverToken !== null) {
        ctx.fillStyle = "yellow";
        ctx.globalAlpha = .5
        ctx.fillRect(hoverToken.gridLocation.x, hoverToken.gridLocation.y, 
                  hoverToken.size, hoverToken.size);
        ctx.globalAlpha = 1;

        drawClippedAsset(
            hoverToken.imgSourceX,
            hoverToken.imgSourceY,
            hoverToken.imgSourceSize,
            hoverToken.imgSourceSize,
            hoverToken.gridLocation.x,
            hoverToken.gridLocation.y,
            hoverToken.size,
            hoverToken.size,
            hoverToken.imgIdTag
        );
    }
}

基本上,我们所做的就是

  1. 遍历 `allTokens` 数组
  2. 根据其 `gridLocation` 值在当前位置绘制 token
  3. 如果 token 处于鼠标悬停状态,则在其周围绘制浅黄色阴影,以便用户知道可以抓取该 token。

draw() 函数确实使用了另一个名为 `drawClippedAsset()` 的辅助方法,该方法允许我轻松地引用我们图像中的 token。它看起来如下:

function drawClippedAsset(sx,sy,swidth,sheight,x,y,w,h,imageId)
{
    var img = document.getElementById(imageId);
    if (img != null)
    {
        ctx.drawImage(img,sx,sy,swidth,sheight,x,y,w,h);
    }
    else
    {
        console.log("couldn't get element");
    }
}

添加所有这些代码后,您最终将绘制出背景网格,并在初始位置绘制出棋子。

initial view

获取代码:v003

如果您从本文顶部获取 v003 代码,您将保持最新状态,以便继续阅读本文。

**注意**:此时,我必须在 `web.config` 文件中添加一个特殊部分,因为出于某种原因,我遇到了一个奇怪的错误,即 OWIN 尝试自动运行。我确定这与 SignalR 的 Nuget 添加的库有关。我添加的行如下:

<appSettings>
    <add key="owin:AutomaticAppStartup" value="false" />
  </appSettings>

这似乎可以使应用程序保持运行,否则 OWIN 会尝试加载并导致应用程序崩溃。

我们现在在哪里?

我们现在离成功使用 SignalR 非常近了,但首先,我们必须添加本地代码,以便能够抓取棋子并移动它。完成之后,我们将允许将更新的值广播到其他客户端。

让我们现在添加代码来完成这项工作。是的,仍然是更多的 JavaScript。

我们需要一些事件处理程序,它们将在鼠标单击(mousedown)和鼠标移动时执行一些工作。

回到我们的 `initApp()` 函数,我们需要添加这两个事件的事件监听器。现在 `initApp()` 将如下所示:(我添加了加粗的行)

function initApp()
{
    theCanvas = document.getElementById("gamescreen");
    ctx = theCanvas.getContext("2d");
    
    ctx.canvas.height  = 650;
    ctx.canvas.width = ctx.canvas.height;

    window.addEventListener("mousemove", handleMouseMove);
    window.addEventListener("mousedown", mouseDownHandler);

    initBoard();
}

这是添加这些监听器的纯 JavaScript 方式。您也可以使用 jQuery 来完成,但这样操作也很简单。

现在,我们已经注册了事件监听器,我们需要实现 `handleMouseMove()` 和 `mousedownHandler()` 方法。

function handleMouseMove(e)
{
    if (mouseIsCaptured)
    {    
        if (hoverItem.isMoving)
        {
        var tempx = e.clientX - hoverItem.offSetX;
        var tempy = e.clientY - hoverItem.offSetY;
        hoverItem.gridLocation.x = tempx;
        hoverItem.gridLocation.y = tempy;
         if (tempx < 0)
          {
            hoverItem.gridLocation.x = 0;
          }
          if (tempx + lineInterval > 650)
          {
            hoverItem.gridLocation.x = 650 - lineInterval;
          }
          if (tempy < 0)
          { 
            hoverItem.gridLocation.y = 0;
          }
          if (lineInterval + tempy > 650)
          {
            hoverItem.gridLocation.y = 650 - lineInterval;
          }
      
        allTokens[hoverItem.idx]=hoverItem;
        pawnR.server.send(hoverItem.gridLocation.x, hoverItem.gridLocation.y,hoverItem.idx);
        }
        draw();
    }
    // otherwise user is just moving mouse / highlight tokens
    else
    {
        hoverToken = hitTestHoverItem({x:e.clientX,y:e.clientY}, allTokens);
        draw();
    }
}

只要鼠标移动,此函数就会运行。我可以更具体地说,只在鼠标移动且鼠标位于 `Canvas` 元素上时运行,但这样也可以。

我首先做的是检查 `mouseIsCaptured` 是否为 true。该值在 `mouseDownHandler` 触发(用户单击)并且方法确定用户位于三个棋子之一上方时设置。这项工作由 `mouseDownHandler` 完成,让我们来看看。

function mouseDownHandler(event) {

    var currentPoint = getMousePos(event);
    
    for (var tokenCount = allTokens.length - 1; tokenCount >= 0; tokenCount--) {
        if (hitTest(currentPoint, allTokens[tokenCount])) {
            currentToken = allTokens[tokenCount];
            // the offset value is the diff. between the place inside the token
            // where the user clicked and the token's xy origin.
            currentToken.offSetX = currentPoint.x - currentToken.gridLocation.x;
            currentToken.offSetY = currentPoint.y - currentToken.gridLocation.y;
            currentToken.isMoving = true;
            currentToken.idx = tokenCount;
            hoverItem = currentToken;
            console.log("b.x : " + currentToken.gridLocation.x + "  b.y : "
                  + currentToken.gridLocation.y);
            mouseIsCaptured = true;
            window.addEventListener("mouseup", mouseUpHandler);
            break;
        }
    }
}

在 `mouseDownHandler` 中,我们只需遍历 `allTokens` 数组并检查它们的 `gridLocation`。如果确定鼠标指针位于该区域内,我们就将 `mouseIsCaptured` 布尔值设置为 true。

我将检查鼠标位置是否在任何 token 位置内的代码分解出来,并将其放入一个名为 `hitTest()` 的方法中。

function hitTest(mouseLocation, hitTestObject)
{
  var testObjXmax = hitTestObject.gridLocation.x + hitTestObject.size;
  var testObjYmax = hitTestObject.gridLocation.y + hitTestObject.size;
  if ( ((mouseLocation.x >= hitTestObject.gridLocation.x) && (mouseLocation.x <= testObjXmax)) && 
    ((mouseLocation.y >= hitTestObject.gridLocation.y) && (mouseLocation.y <= testObjYmax)))
  {
    return true;
  }
  return false;
}

您可以看到,我们只需传入 `mouseLocation` 和要测试的对象,该函数就会进行迭代并确定是否命中,然后返回 true 或 false。这使得一切都非常易于使用。

其中还有一些其他辅助方法,它们将决定正在抓取哪个棋子,并将不断调用 `draw()` 方法,以便在鼠标移动时绘制棋子。此时,用户可以抓取任何一个棋子并在屏幕上移动它。

获取代码:用户可以抓取和拖动任何棋子 v004

下载本文顶部的 v004 zip 文件,您可以尝试拖动棋子。

drag pawns

最后,我们面临主要挑战

我们终于准备好尝试解决主要挑战了。我们想做的是

主要挑战

更新所有正在查看我们网页的客户端,以便当棋子移动时,所有其他客户端都能看到棋子移动。

要实现这一点,我们首先要做的是初始化 SignalR 的启动。原始的微软文章告诉我们必须添加一个 `Startup.cs` 文件来完成这项工作。

当然,它不一定命名为 Startup,但它确实需要是项目中的一个新类。请继续在解决方案资源管理器中右键单击 `Pawns` 项目,然后选择出现的 **添加** 菜单项,然后选择 **Class**… 将会弹出一个对话框,以便您可以输入要创建的类的名称。请将其命名为 `Startup.cs` 并单击“[OK]”按钮。

文件将被添加到您的项目中,并添加一些模板代码。请继续用以下代码替换该文件中的代码:

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(Pawns.Startup))]
namespace Pawns
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // Any connection or hub wire up and configuration should go here
            app.MapSignalR();
        }
    }
}

此代码初始化 OwinStartup 并映射 SignalR 代码,以便它可以构建您需要的 JavaScript。这是用于准备所有内容的简单样板代码。现在我们可以编写一些代码来做一些事情了。

我们的应用程序想要做什么

我们希望尽可能少地将数据发送到其他浏览器。这意味着我们只想将移动棋子的 `gridLocation` 信息发送到其他浏览器。这样,当本地棋子移动并更新其位置时,其他客户端中的棋子也会被更新。

SignalR Hub 类:C# 生成 JavaScript

为了完成这项工作,我们需要生成一个 SignalR `Hub` 类,它将数据发送到其他浏览器。

现在,我们向项目中添加另一个类,这次将其命名为 `PawnHub`。请注意,在下一个代码片段中,我们将 `PawnHub` 派生自 `Hub` 类型,这是一个特殊的 SignalR 库类型,提供了一些特殊功能。

添加该类后,用以下代码替换其所有代码:

using Microsoft.AspNet.SignalR;

namespace Pawns
{
    [Microsoft.AspNet.SignalR.Hubs.HubName("pawnHub")]
    public class PawnHub : Hub
    {
        public void Send(int x, int y, int idx)
        {
            // Call the broadcastMessage method to update clients.
            Clients.Others.broadcastMessage(x, y, idx);
        }
    }
}

这是将 C# 与 JavaScript 绑定的部分。当您编译并运行此代码时,它将查找一个名为 `pawnHub` 的特殊 JavaScript 对象。当它找到 `pawnHub` 对象并且调用 `Send()` 方法时,它将调用 `broadcastMessage()` 方法,并传递我们提供给它的值。

三个参数是可自定义的

这三个参数是可自定义的。我选择传入对象的 `x` 和 `y` 位置以及我们 `allTokens` 数组中对象的索引。

这是一个少量要广播的数据(只有三个整数),并且很容易在 JavaScript 中处理。让我们添加将要更新所有其他客户端浏览器的 JavaScript,这样当您在一个浏览器中移动棋子时,所有其他浏览器都会更新。

使用 JavaScript 进行最后的润色

回到我们的 `initApp()`,我们需要初始化我们的

现在 `initApp()` 将如下所示(我添加了加粗的代码):

function initApp() {
    theCanvas = document.getElementById("gamescreen");
    ctx = theCanvas.getContext("2d");

    ctx.canvas.height = 650;
    ctx.canvas.width = ctx.canvas.height;
    window.addEventListener("mousemove", handleMouseMove);
    window.addEventListener("mousedown", mouseDownHandler);

    pawnR = $.connection.pawnHub;
    pawnR.client.broadcastMessage = function (x, y, idx) {
        allTokens[idx].gridLocation.x = x;
        allTokens[idx].gridLocation.y = y;
        draw();
    };
    
    $.connection.hub.start().done(function () {
        console.log("Hub is started.");
    });

    initBoard();
}

 

我们只需设置我们的 `pawnR`,它就是保存我们的 `SignalR` 对象的变量。如您所见,我们使用 `pawnHub` 对象的副本对其进行了初始化。这个 `$.connection.pawnHub` 是编译器为我们生成的代码。这就是访问该对象的简单语法。

设置完方法后,您必须通过调用 `start()` 方法来启动 `hub`。完成后,它将运行一个函数,我在这里只是添加了代码以在 `console.log()` 中显示一条消息。

这是您在微软教程中看到的相同示例。

定义广播消息方法

完成此操作后,我们必须告诉 `pawnHub` 对象在客户端发送广播消息时调用哪个 JavaScript 函数。您可以看到我们在下一行中使用匿名函数完成了此操作,该函数只需获取发送的值(还记得 C# `PawnHub` 的 `Send()` 方法吗?)— 在我们的例子中是 `x`、`y` 和 `idx`。它使用这些值更新适当的 token(来自 `allTokens`),然后调用 `draw()`,以便在客户端,当远程用户移动棋子时,棋子会随之移动(重新绘制)。就是这么简单。

现在,我们只需要添加一行代码,以便在用户拖动棋子时发送数据。由于该代码应该在鼠标移动时运行,因此我们将更新 `onMouseMove` 处理程序。这是包含添加的一行代码的加粗代码:

您可以看到该方法名为 `Send()`,并接受三个参数。这与我们的 `PawnHub` 中的 `Send()` 方法的定义相匹配。我们将 `PawnHub` 方法映射到了一个 JavaScript 客户端方法,因此当它被调用并广播消息时,所有客户端都会更新。这就提供了完整的解决方案。

function handleMouseMove(e)
{
    if (mouseIsCaptured)
    {    
        if (hoverItem.isMoving)
        {
        var tempx = e.clientX - hoverItem.offSetX;
        var tempy = e.clientY - hoverItem.offSetY;
        hoverItem.gridLocation.x = tempx;
        hoverItem.gridLocation.y = tempy;
         if (tempx < 0)
          {
            hoverItem.gridLocation.x = 0;
          }
          if (tempx + lineInterval > 650)
          {
            hoverItem.gridLocation.x = 650 - lineInterval;
          }
          if (tempy < 0)
          { 
            hoverItem.gridLocation.y = 0;
          }
          if (lineInterval + tempy > 650)
          {
            hoverItem.gridLocation.y = 650 - lineInterval;
          }
      
        allTokens[hoverItem.idx]=hoverItem;
        pawnR.server.send(hoverItem.gridLocation.x, hoverItem.gridLocation.y,hoverItem.idx);
        }
        draw();
    }
    // otherwise user is just moving mouse / highlight tokens
    else
    {
        hoverToken = hitTestHoverItem({x:e.clientX,y:e.clientY}, allTokens);
        draw();
    }
}

完整解决方案:v005

在本文顶部的 v005 下载中获取完整的解决方案。

构建并运行,然后打开两个指向相同 URL 的独立浏览器窗口并移动一个棋子。它也会在另一个浏览器窗口中移动。

请注意

我必须从 `web.config` 文件中删除以下代码,以便 hub 可以启动。如果您将其保留,您将收到错误。

  <appSettings>
    <add key="owin:AutomaticAppStartup" value="false" />
  </appSettings>

看看本地运行有多快

再用一个动画 GIF 展示一下本地运行时的快速更新。

final app running local

让这个工作确实遇到了挑战。

刷新页面会重置棋子位置

另外,如果您刷新页面,棋子总是会回到其原始位置。这是因为我没有将这些值持久化到数据存储中。如果您将此 SignalR 解决方案与我的 Firebase 解决方案进行比较,您会发现 Firebase 解决方案始终保留棋子的位置。这是因为 Firebase 解决方案通过提供一个对象数据库来远程存储您的项目,从而本身就解决了这个问题。

GoDaddy 问题 / 部署

我从未能在我的 GoDaddy 托管站点上成功运行。这是因为 JavaScript 似乎是实时生成的,位于一个虚拟目录或使用 SignalR/Hubs 引用之类的东西中。我从未能弄清楚。但我为了让它在 SmarterASP.net 站点上运行,完全没有做任何特殊处理。如果您在部署 SignalR 应用时遇到问题,请务必了解您的 Web 托管服务是如何做的。

我希望您发现本文是一个有用的示例和 SignalR 使用入门。

开启我的科幻写作生涯 :)

我还宣布我将开始撰写科幻小说。我将通过博客发布我的书 **Robot Hunters: Divided Resistance**(三部曲的第一部)。您可以在我的网站/博客上阅读第一章: http://robothunters.us/post/2017/05/22/chapter-1-enter-the-robot-compound^

它还很粗糙,尚未完全完成,但也许您是一位对写作感兴趣的科幻迷,就像我一样。感谢您查看。

历史

第一版 : 05/22/2017

© . All rights reserved.