ASP.NET SignalR 入门指南
学习使用 SignalR 通过 SignalR 广播异步更新所有 Web 客户端。
- 下载 PawnsCP_SignalR_v005.zip - 231.5 KB
- 下载 PawnsCP_SignalR_v004.zip - 230.6 KB
- 下载 PawnsCP_SignalR_v003.zip - 229.9 KB
- 下载 PawnsCP_SignalR_v002.zip - 227.7 KB
- 下载 PawnsCP_SignalR_v001.zip - 204.8 KB
引言
我一直在想使用 SignalR 会有多么容易,所以我决定完成微软的一个快速入门教程。在做的过程中,我遇到了一些挑战,所以我想尝试自己解释 SignalR 的工作原理。
主要重点:更新远程客户端
使用 SignalR 这样的技术,其根本目的就是更新远程客户端。我想做一个简单的例子,让读者可以看到它是如何工作的。很久以前,我使用 Firebase(稍后会详细介绍)创建了一个简单的例子,该例子
- 允许用户在 HTML5 Canvas 上抓取和移动棋子
- 更新所有客户端,以便远程用户可以在他们的浏览器中看到棋子移动
这是一个简单的例子。左边的浏览器是 Google Chrome,右边的是 Microsoft Edge。它们都指向同一个 URL,也就是我托管在 SmarterASP.net - Unlimited ASP.NET Web Hosting[^] 上的网站。我在那里获得了 60 天的免费试用,您也可以获得,无需信用卡。稍后将详细介绍我为什么使用 SmarterASP.net。
您可以立即尝试
只需打开两个不同的浏览器窗口,并将它们指向我的 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 与之相比如何
- 开发便捷性/速度
- 工作效果 - 更新远程客户端和性能(更新远程客户端的速度)
Firebase(响应更灵敏)
如果您同时尝试了两者,我相信您会发现 Firebase 的响应速度要快得多。您可以看到,即使在上方的示例 GIF 中,也存在相当大的延迟,该 GIF 是从为本文编写的 SignalR 版本中录制的。
既然您已经看到了它的效果,让我们在 Visual Studio 中设置我们的 SignalR 项目并开始吧。
Visual Studio ASP.NET 项目
正如我之前所说,我是通过完成微软的一个教程来学习 SignalR 的。您可以在以下位置找到该教程:教程:SignalR 2 入门 | Microsoft Docs[^]。然而,那篇文章存在一些挑战。
- 这是一篇较旧的文章,使用 Visual Studio 2013
- 它会让您通过 Nuget 添加 Owin 包,然后单独添加 SignalR 包。但现在在 Visual Studio 2015(或本文中我使用的 2017)中,情况略有不同。我将向您展示。
启动 Visual Studio 并创建一个新项目。
在左侧选择“Web”项目类型,然后在右侧选择“ASP.NET Web 应用程序 (.NET Framework)”。
将项目命名为“pawns”以保持简单,然后单击“[OK]”按钮。
将出现一个对话框。此项目不需要太多内容,并且我们将使用 Nuget 来添加 SignalR 库,因此请选择“Empty Project”并单击“[OK]”按钮。
创建模板项目后,选择 Visual Studio 顶部的 **工具**… 菜单项。
向下滚动到 **Nuget 包管理器** 菜单项。将弹出另一个菜单。**选择“为解决方案管理 Nuget 包”**… 菜单项。
Visual Studio 中将打开一个非常丑陋的窗口。在我的电脑上,窗口非常小,几乎看不清任何东西。微软在 UI/UX 方面算不上特别出色,对吧?
默认情况下,顶部的“已安装”项将被选中。请继续
- 单击“浏览”项
- 单击搜索编辑框内部
- 键入“signalr”以搜索 SignalR 库。
- 您应该会在 Nuget 窗口的底部看到一些 SignalR 选项。
- (单击)第一个显示的选项 — 应该是 `Microsoft.AspNet.SignalR`
此时,我不得不移动滑块条,以便真正看到屏幕上需要的内容。如果您需要,请继续操作,因为在右侧,我们需要单击一个我们现在甚至看不到的按钮,以便添加 SignalR 库。
- 选中“Pawns”项目旁边的复选框。(这将启用“[Install]”按钮,以便您可以单击它。)
- 单击“[Install]”按钮将 SignalR 库添加到项目中。
将弹出一个对话框,显示将添加到您项目的所有库的预览。
单击“[OK]”按钮,所有库都将被添加。**注意**:还会弹出一个许可证接受对话框,以确保您接受使用许可证。
Nuget 处理依赖项
在微软的教程中,他们会让您单独添加 OWIN 库,但现在您实际上不需要这样做,因为 Nuget 会为您添加所有依赖项。
获取源代码 - v001
如果您在项目创建过程中遇到任何问题,可以下载本文顶部的 v001 zip 文件并解压缩,您就可以全部准备好了。当然,我已经删除了下载的 nuget 包,所以 zip 文件只是源代码,但您所要做的就是从 Visual Studio 恢复包,然后就可以继续阅读本文了。
完成以上所有步骤后,您就拥有了在项目中使用的 **SignalR** 的所有必需条件。
现在,让我们添加一些代码。
添加新的 HTML 页面
在微软的教程中,作者告诉您的第一件事是添加一个实现 SignalR 行为的新类。但是,我喜欢边做边构建,以便了解它们是如何协同工作的。所以,首先,我们将设置将用作我们应用的 UI 的 HTML 页面。
继续向您的项目中添加一个新的 Web 页面。您只需在解决方案资源管理器中右键单击您的解决方案。接下来,向下滚动到**添加**菜单项。最后选择 **HTML 页面** 菜单项。
将弹出一个对话框。在其中键入文件名 `index.htm` 并单击“[OK]”按钮。是的,我使用了 3 个字母的扩展名,而不是完整的 4 个字母的 HTML。
当您添加文件时,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 中。
图像资源
接下来,您会看到我引用了一个您没有的图像。您可以在此处看到它,如果喜欢,可以下载它。
您可以看到,即使有三个独立的可移动棋子,图像本身实际上是一个 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` 标签。
继续添加对 `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
);
}
}
基本上,我们所做的就是
- 遍历 `allTokens` 数组
- 根据其 `gridLocation` 值在当前位置绘制 token
- 如果 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");
}
}
添加所有这些代码后,您最终将绘制出背景网格,并在初始位置绘制出棋子。
获取代码: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 文件,您可以尝试拖动棋子。
最后,我们面临主要挑战
我们终于准备好尝试解决主要挑战了。我们想做的是
主要挑战
更新所有正在查看我们网页的客户端,以便当棋子移动时,所有其他客户端都能看到棋子移动。
要实现这一点,我们首先要做的是初始化 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 展示一下本地运行时的快速更新。
让这个工作确实遇到了挑战。
刷新页面会重置棋子位置
另外,如果您刷新页面,棋子总是会回到其原始位置。这是因为我没有将这些值持久化到数据存储中。如果您将此 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