- 配合使用 ASP.NET Core SignalR 和 TypeScript 以及 WebpackUse ASP.NET Core SignalR with TypeScript and Webpack
- 先决条件Prerequisites
- 创建 ASP.NET Core Web 应用Create the ASP.NET Core web app
- 配置 Webpack 和 TypeScriptConfigure Webpack and TypeScript
- 配置应用Configure the app
- 启用客户端和服务器通信Enable client and server communication
- 测试应用Test the app
- 先决条件Prerequisites
- 创建 ASP.NET Core Web 应用Create the ASP.NET Core web app
- 配置 Webpack 和 TypeScriptConfigure Webpack and TypeScript
- 配置 ASP.NET Core 应用Configure the ASP.NET Core app
- 启用客户端和服务器通信Enable client and server communication
- 测试应用Test the app
- 其他资源Additional resources
配合使用 ASP.NET Core SignalR 和 TypeScript 以及 WebpackUse ASP.NET Core SignalR with TypeScript and Webpack
本文内容
作者:Sébastien Sougnez 和 Scott Addie
开发人员可以通过 Webpack 捆绑和生成 Web 应用的客户端资源。本教程介绍在 ASP.NET Core SignalR Web 应用中使用 Webpack,该应用的客户端是使用 TypeScript 编写的。
在本教程中,你将了解:
- 为入门 ASP.NET Core SignalR 应用搭建基架
- 配置 SignalR TypeScript 客户端
- 使用 Webpack 配置生成管道
- 配置 SignalR 服务器
- 启用客户端和服务器之间的通信
先决条件Prerequisites
- Visual Studio 2019 与 ASP.NET 和 Web 开发 工作负载
- .NET Core SDK 3.0 或更高版本
- 带 npm 的 Node.js
创建 ASP.NET Core Web 应用Create the ASP.NET Core web app
配置 Visual Studio,在 PATH 环境变量中查找 npm 。默认情况下,Visual Studio 使用在安装目录中找到的 npm 版本。在 Visual Studio 中按照以下说明执行操作:
启动 Visual Studio。在“启动”窗口中,选择“继续但无需代码” 。
导航到“工具” >“选项” >“项目和解决方案” >“Web 包管理” >“外部 Web 工具” 。
在列表中选择 $(PATH) 项 。单击向上键将项移动列表中的第二个位置,然后选择“确定” 。
Visual Studio 配置完成。
- 使用“文件” > “新建” > “项目”菜单选项,然后选择“ASP.NET Core Web 应用程序”模板 。选择“下一步” 。
- 将项目命名为 SignalRWebPack 并选择“创建” 。
从目标框架下拉列表选择 .NET Core 并从框架选择器下拉列表选择 ASP.NET Core 3.1 。选择“空白”模板并选择“创建” 。
将Microsoft.TypeScript.MSBuild
包添加到项目:在“解决方案资源管理器”(右侧窗格)中,右键单击项目节点,然后选择“管理 NuGet 包” 。在“浏览”选项卡中,搜索
Microsoft.TypeScript.MSBuild
,然后单击右侧的“安装”来安装包 。
Visual Studio 会将 NuGet 包添加到解决方案资源管理器中的“依赖项”节点下,从而在项目中启用 TypeScript 编译 。
在“集成终端”中运行以下命令 :
dotnet new web -o SignalRWebPack
code -r SignalRWebPack
dotnet new
命令会在 SignalRWebPack 目录中创建一个空的 ASP.NET Core Web 应用 。code
命令会在 Visual Studio Code 的当前实例中打开 SignalRWebPack 文件夹 。
在“集成终端”中运行以下 .NET Core CLI 命令 :
dotnet add package Microsoft.TypeScript.MSBuild
上述命令将添加 Microsoft.TypeScript.MSBuild 包,从而在项目中启用 TypeScript 编译。
配置 Webpack 和 TypeScriptConfigure Webpack and TypeScript
以下步骤配置 TypeScript 到 JavaScript 的转换和客户端资源的捆绑。
- 在项目根目录中运行以下命令,创建 package.json 文件 :
npm init -y
- 将突出显示的属性添加到 package.json 文件并保存文件更改 :
{
"name": "SignalRWebPack",
"version": "1.0.0",
"private": true,
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
将 private
属性设置为 true
,防止下一步出现包安装警告。
- 安装所需的 npm 包。从项目根目录运行以下命令:
npm i -D -E clean-webpack-plugin@3.0.0 css-loader@3.4.2 html-webpack-plugin@3.2.0 mini-css-extract-plugin@0.9.0 ts-loader@6.2.1 typescript@3.7.5 webpack@4.41.5 webpack-cli@3.3.10
需要注意的一些命令细节:
- 每个包名称中
@
符号后是版本号。npm 安装这些特定的包版本。 -E
选项禁用 npm 将语义化版本控制范围运算符写到 package.json 的默认行为 。例如,使用"webpack": "4.41.5"
而不是"webpack": "^4.41.5"
。此选项防止意外升级到新的包版本。
有关详细信息,请参阅 npm-install 文档。
- 将 package.json 文件的
scripts
属性替换为以下代码 :
"scripts": {
"build": "webpack --mode=development --watch",
"release": "webpack --mode=production",
"publish": "npm run release && dotnet publish -c Release"
},
脚本的一些解释:
build
:在开发模式下捆绑客户端资源并观察文件更改。文件观察程序使捆绑在每次项目文件发生更改时重新生成。mode
选项可禁用生产优化,例如摇树优化和缩小优化。仅在开发中使用build
。release
:在生产模式下捆绑客户端资源。publish
:运行release
脚本,在生产模式下捆绑客户端资源。它调用 .NET Core CLI 的 publish 命令发布应用。- 在项目根目录中创建名为 webpack.config.js 的文件,使其包含以下代码 :
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, "wwwroot"),
filename: "[name].[chunkhash].js",
publicPath: "/"
},
resolve: {
extensions: [".js", ".ts"]
},
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader"
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: "./src/index.html"
}),
new MiniCssExtractPlugin({
filename: "css/[name].[chunkhash].css"
})
]
};
前面的文件配置 Webpack 编译。需要注意的一些配置细节:
output
属性替代 dist 的默认值 。捆绑反而在 wwwroot 目录中发出 。resolve.extensions
数组包含 .js,以便导入 SignalR 客户端 JavaScript 。- 在项目根目录中创建新的 src 目录,以存储项目的客户端资产 。
- 创建包含以下标记的 src/index.html 。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>ASP.NET Core SignalR</title>
</head>
<body>
<div id="divMessages" class="messages">
</div>
<div class="input-zone">
<label id="lblMessage" for="tbMessage">Message:</label>
<input id="tbMessage" class="input-zone-input" type="text" />
<button id="btnSend">Send</button>
</div>
</body>
</html>
前面的 HTML 定义主页的样板标记。
创建新的 src/css 目录 。目的是存储项目的 .css 文件 。
创建包含以下 CSS 的 src/css/main.css :
*, *::before, *::after {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
}
.input-zone {
align-items: center;
display: flex;
flex-direction: row;
margin: 10px;
}
.input-zone-input {
flex: 1;
margin-right: 10px;
}
.message-author {
font-weight: bold;
}
.messages {
border: 1px solid #000;
margin: 10px;
max-height: 300px;
min-height: 300px;
overflow-y: auto;
padding: 5px;
}
前面的 main.css 文件设计应用样式 。
- 创建包含以下 JSON 的 src/tsconfig.json :
{
"compilerOptions": {
"target": "es5"
}
}
前面的代码配置 TypeScript 编译器,生成与 ECMAScript 5 兼容的 JavaScript。
- 创建包含以下代码的 src/index.ts :
import "./css/main.css";
const divMessages: HTMLDivElement = document.querySelector("#divMessages");
const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
const username = new Date().getTime();
tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
if (e.key === "Enter") {
send();
}
});
btnSend.addEventListener("click", send);
function send() {
}
前面的 TypeScript 检索对 DOM 元素的引用并附加两个事件处理程序:
keyup
:用户在tbMessage
文本框中键入时触发此事件。用户按 Enter 时调用send
函数 。click
:用户单击“发送”按钮时触发此事件 。调用send
函数。
配置应用Configure the app
- 在
Startup.Configure
中,添加对 UseDefaultFiles 和 UseStaticFiles 的调用。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ChatHub>("/hub");
});
}
上述代码允许服务器查找和处理 index.html 文件 。无论用户输入完整 URL 还是 Web 应用的根 URL,都会提供该文件。
- 在
Startup.Configure
的末尾,将 /hub 路由映射到ChatHub
中心 。将显示 Hello World! 的代码替换为以下行:
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ChatHub>("/hub");
});
- 在
Startup.ConfigureServices
中,调用 AddSignalR。
services.AddSignalR();
在项目根目录 SignalRWebPack/ 中创建名为 Hubs 的新目录,以存储 SignalR 中心 。
创建包含以下代码的中心 Hubs/ChatHub.cs :
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SignalRWebPack.Hubs
{
public class ChatHub : Hub
{
}
}
- 在 Startup.cs 文件顶部添加以下
using
语句来解析ChatHub
引用 :
using SignalRWebPack.Hubs;
启用客户端和服务器通信Enable client and server communication
应用目前显示用于发送消息的基本窗体,但尚不能正常工作。服务器正在侦听特定的路由,但是不涉及发送消息。
- 在项目根目录运行以下命令:
npm i @microsoft/signalr @types/node
上述的代码会安装:
- SignalR TypeScript 客户端,它允许客户端向服务器发送消息。
- 用于 node.js 的 TypeScript 类型定义,支持 Node.js 类型的编译时检查。
- 将突出显示的代码添加到 src/index.ts 文件 :
import "./css/main.css";
import * as signalR from "@microsoft/signalr";
const divMessages: HTMLDivElement = document.querySelector("#divMessages");
const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
const username = new Date().getTime();
const connection = new signalR.HubConnectionBuilder()
.withUrl("/hub")
.build();
connection.on("messageReceived", (username: string, message: string) => {
let m = document.createElement("div");
m.innerHTML =
`<div class="message-author">${username}</div><div>${message}</div>`;
divMessages.appendChild(m);
divMessages.scrollTop = divMessages.scrollHeight;
});
connection.start().catch(err => document.write(err));
tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
if (e.key === "Enter") {
send();
}
});
btnSend.addEventListener("click", send);
function send() {
}
前面的代码支持从服务器接收消息。HubConnectionBuilder
类创建新的生成器,用于配置服务器连接。withUrl
函数配置中心 URL。
SignalR 启用客户端和服务器之间的消息交换。每个消息都有特定的名称。例如,名为 messageReceived
的消息可以运行负责在消息区域显示新消息的逻辑。可以通过 on
函数完成对特定消息的侦听。可以侦听任意数量的消息名称。还可以将参数传递到消息,例如所接收消息的作者姓名和内容。客户端收到一条消息后,会创建一个新的 div
元素并在其 innerHTML
属性中显示作者姓名和消息内容。它添加到显示消息的主要 div
元素。
- 客户端可以接收消息后,将它配置为发送消息。将突出显示的代码添加到 src/index.ts 文件 :
import "./css/main.css";
import * as signalR from "@microsoft/signalr";
const divMessages: HTMLDivElement = document.querySelector("#divMessages");
const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
const username = new Date().getTime();
const connection = new signalR.HubConnectionBuilder()
.withUrl("/hub")
.build();
connection.on("messageReceived", (username: string, message: string) => {
let messages = document.createElement("div");
messages.innerHTML =
`<div class="message-author">${username}</div><div>${message}</div>`;
divMessages.appendChild(messages);
divMessages.scrollTop = divMessages.scrollHeight;
});
connection.start().catch(err => document.write(err));
tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
if (e.key === "Enter") {
send();
}
});
btnSend.addEventListener("click", send);
function send() {
connection.send("newMessage", username, tbMessage.value)
.then(() => tbMessage.value = "");
}
通过 WebSockets 连接发送消息需要调用 send
方法。该方法的第一个参数是消息名称。消息数据包含其他参数。在此示例中,一条标识为 newMessage
的消息已发送到服务器。该消息包含用户名和文本框中的用户输入。如果发送成功,会清空文本框。
- 将
NewMessage
方法添加到ChatHub
类:
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SignalRWebPack.Hubs
{
public class ChatHub : Hub
{
public async Task NewMessage(long username, string message)
{
await Clients.All.SendAsync("messageReceived", username, message);
}
}
}
服务器收到消息后,前面的代码会将这些消息播发到所有连接的用户。没有必要使用泛型 on
方法接收所有消息。使用以消息名称命名的方法就可以了。
在此示例中,TypeScript 客户端发送一条标识为 newMessage
的消息。C# NewMessage
方法需要客户端发送的数据。在 Clients.All 上对 SendAsync 进行调用。接收的消息会发送到所有连接到中心的客户端。
测试应用Test the app
确认应用遵循以下步骤。
- 在 release 模式下运行 Webpack 。使用“包管理器控制台”窗口,在项目根目录中运行以下命令 。如果不在项目根中,请在输入该命令之前输入
cd SignalRWebPack
。
npm run release
此命令在运行应用时生成要提供的客户端资产。资产位于 wwwroot 文件夹。
Webpack 完成了以下任务:
- 清除了 wwwroot 目录的内容。
- 将 TypeScript 转换为 JavaScript,该过程称为“转译”。
- 破坏生成的 JavaScript 以降低文件大小,该过程称为“缩小”。
- 将已处理的 JavaScript、CSS 和 HTML 文件从 src 复制到 wwwroot 目录。
- 将以下元素注入 wwwroot/index.html 文件:
- 一个引用 wwwroot/main.
.css 文件的 <link>
标记。此标记紧挨着</head>
结束标记之前。 - 一个引用缩小后的 wwwroot/main.
.js 文件的 <script>
标记。此标记紧挨着</body>
结束标记之前。 - 选择“调试” > “开始执行(不调试)”,在不附加调试器的情况下在浏览器中启动应用 。在
http://localhost:<port_number>
上提供 wwwroot/index.html 文件。
- 一个引用 wwwroot/main.
如果遇到编译错误,请尝试关闭并重新打开解决方案。
打开另一个浏览器实例(任意浏览器)。在地址栏中粘贴 URL。
选择一个浏览器,在“消息”文本框中键入任意内容,然后单击“发送”按钮 。两个页面上立即显示唯一的用户名和消息。
- 通过在项目根中执行以下命令以 release 模式运行 Webpack :
npm run release
此命令在运行应用时生成要提供的客户端资产。资产位于 wwwroot 文件夹。
Webpack 完成了以下任务:
- 清除了 wwwroot 目录的内容。
- 将 TypeScript 转换为 JavaScript,该过程称为“转译”。
- 破坏生成的 JavaScript 以降低文件大小,该过程称为“缩小”。
- 将已处理的 JavaScript、CSS 和 HTML 文件从 src 复制到 wwwroot 目录。
- 将以下元素注入 wwwroot/index.html 文件:
- 一个引用 wwwroot/main.
.css 文件的 <link>
标记。此标记紧挨着</head>
结束标记之前。 - 一个引用缩小后的 wwwroot/main.
.js 文件的 <script>
标记。此标记紧挨着</body>
结束标记之前。 - 通过在项目根中执行以下命令生成和运行应用:
- 一个引用 wwwroot/main.
dotnet run
Web 服务器启动应用并在 localhost 上提供。
打开浏览器,转到
http://localhost:<port_number>
。提供 wwwroot/index.html 文件 。从地址栏复制 URL。打开另一个浏览器实例(任意浏览器)。在地址栏中粘贴 URL。
选择一个浏览器,在“消息”文本框中键入任意内容,然后单击“发送”按钮 。两个页面上立即显示唯一的用户名和消息。
先决条件Prerequisites
- Visual Studio 2019 与 ASP.NET 和 Web 开发 工作负载
- .NET Core SDK 2.2 或更高版本
- 带 npm 的 Node.js
创建 ASP.NET Core Web 应用Create the ASP.NET Core web app
配置 Visual Studio,在 PATH 环境变量中查找 npm 。默认情况下,Visual Studio 使用在安装目录中找到的 npm 版本。在 Visual Studio 中按照以下说明执行操作:
导航到“工具” >“选项” >“项目和解决方案” >“Web 包管理” >“外部 Web 工具” 。
在列表中选择 $(PATH) 项 。单击向上键将项移动列表第二个位置。
已完成 Visual Studio 配置。可以开始创建项目了。
- 使用“文件”>“新建”>“项目”菜单选项,然后选择“ASP.NET Core Web 应用程序”模板 。
- 将项目命名为 SignalRWebPack 并选择“创建” 。
- 从目标框架下拉列表选择 .NET Core 并从框架选择器下拉列表选择 ASP.NET Core 2.2 。选择“空白”模板并选择“创建” 。
在“集成终端”中运行以下命令 :
dotnet new web -o SignalRWebPack
SignalRWebPack 目录中创建了一个面向 .NET Core 的空 ASP.NET Core Web 应用 。
配置 Webpack 和 TypeScriptConfigure Webpack and TypeScript
以下步骤配置 TypeScript 到 JavaScript 的转换和客户端资源的捆绑。
- 在项目根目录中运行以下命令,创建 package.json 文件 :
npm init -y
- 将突出显示的属性添加到 package.json 文件 :
{
"name": "SignalRWebPack",
"version": "1.0.0",
"private": true,
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
将 private
属性设置为 true
,防止下一步出现包安装警告。
- 安装所需的 npm 包。从项目根目录运行以下命令:
npm install -D -E clean-webpack-plugin@1.0.1 css-loader@2.1.0 html-webpack-plugin@4.0.0-beta.5 mini-css-extract-plugin@0.5.0 ts-loader@5.3.3 typescript@3.3.3 webpack@4.29.3 webpack-cli@3.2.3
需要注意的一些命令细节:
- 每个包名称中
@
符号后是版本号。npm 安装这些特定的包版本。 -E
选项禁用 npm 将语义化版本控制范围运算符写到 package.json 的默认行为 。例如,使用"webpack": "4.29.3"
而不是"webpack": "^4.29.3"
。此选项防止意外升级到新的包版本。
有关详细信息,请参阅 npm-install 文档。
- 将 package.json 文件的
scripts
属性替换为以下代码 :
"scripts": {
"build": "webpack --mode=development --watch",
"release": "webpack --mode=production",
"publish": "npm run release && dotnet publish -c Release"
},
脚本的一些解释:
build
:在开发模式下捆绑客户端资源并观察文件更改。文件观察程序使捆绑在每次项目文件发生更改时重新生成。mode
选项可禁用生产优化,例如摇树优化和缩小优化。仅在开发中使用build
。release
:在生产模式下捆绑客户端资源。publish
:运行release
脚本,在生产模式下捆绑客户端资源。它调用 .NET Core CLI 的 publish 命令发布应用。- 在项目根目录中创建名为 webpack.config.js 的文件,使其包含以下代码 :
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CleanWebpackPlugin = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, "wwwroot"),
filename: "[name].[chunkhash].js",
publicPath: "/"
},
resolve: {
extensions: [".js", ".ts"]
},
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader"
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"]
}
]
},
plugins: [
new CleanWebpackPlugin(["wwwroot/*"]),
new HtmlWebpackPlugin({
template: "./src/index.html"
}),
new MiniCssExtractPlugin({
filename: "css/[name].[chunkhash].css"
})
]
};
前面的文件配置 Webpack 编译。需要注意的一些配置细节:
output
属性替代 dist 的默认值 。捆绑反而在 wwwroot 目录中发出 。resolve.extensions
数组包含 .js,以便导入 SignalR 客户端 JavaScript 。- 在项目根目录中创建新的 src 目录,以存储项目的客户端资产 。
- 创建包含以下标记的 src/index.html 。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>ASP.NET Core SignalR</title>
</head>
<body>
<div id="divMessages" class="messages">
</div>
<div class="input-zone">
<label id="lblMessage" for="tbMessage">Message:</label>
<input id="tbMessage" class="input-zone-input" type="text" />
<button id="btnSend">Send</button>
</div>
</body>
</html>
前面的 HTML 定义主页的样板标记。
创建新的 src/css 目录 。目的是存储项目的 .css 文件 。
创建包含以下标记的 src/css/main.css :
*, *::before, *::after {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
}
.input-zone {
align-items: center;
display: flex;
flex-direction: row;
margin: 10px;
}
.input-zone-input {
flex: 1;
margin-right: 10px;
}
.message-author {
font-weight: bold;
}
.messages {
border: 1px solid #000;
margin: 10px;
max-height: 300px;
min-height: 300px;
overflow-y: auto;
padding: 5px;
}
前面的 main.css 文件设计应用样式 。
- 创建包含以下 JSON 的 src/tsconfig.json :
{
"compilerOptions": {
"target": "es5"
}
}
前面的代码配置 TypeScript 编译器,生成与 ECMAScript 5 兼容的 JavaScript。
- 创建包含以下代码的 src/index.ts :
import "./css/main.css";
const divMessages: HTMLDivElement = document.querySelector("#divMessages");
const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
const username = new Date().getTime();
tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
if (e.keyCode === 13) {
send();
}
});
btnSend.addEventListener("click", send);
function send() {
}
前面的 TypeScript 检索对 DOM 元素的引用并附加两个事件处理程序:
keyup
:用户在tbMessage
文本框中键入时触发此事件。用户按 Enter 时调用send
函数 。click
:用户单击“发送”按钮时触发此事件 。调用send
函数。
配置 ASP.NET Core 应用Configure the ASP.NET Core app
Startup.Configure
方法中提供的代码显示 Hello World! 。将app.Run
方法调用替换为对 UseDefaultFiles 和 UseStaticFiles 的调用。
app.UseDefaultFiles();
app.UseStaticFiles();
前面的代码允许服务器定位并提供 index.html 文件,无论用户输入完整 URL 还是 Web 应用的根 URL 。
- 在
Startup.ConfigureServices
中,调用 AddSignalR。此操作会将 SignalR 服务添加到项目。
services.AddSignalR();
- 将 /hub 路由映射到
ChatHub
中心 。在Startup.Configure
的末尾添加以下行:
app.UseSignalR(options =>
{
options.MapHub<ChatHub>("/hub");
});
在项目根中创建名为 Hubs 的新目录 。目的是存储 SignalR 中心(在下一步中创建)。
创建包含以下代码的中心 Hubs/ChatHub.cs :
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SignalRWebPack.Hubs
{
public class ChatHub : Hub
{
}
}
- 在 Startup.cs 文件顶部添加以下代码,解析
ChatHub
引用 :
using SignalRWebPack.Hubs;
启用客户端和服务器通信Enable client and server communication
应用当前显示一个发送消息的简单窗体。尝试执行此操作时没有任何反应。服务器正在侦听特定的路由,但是不涉及发送消息。
- 在项目根目录运行以下命令:
npm install @aspnet/signalr
前面的命令安装 SignalR TypeScript 客户端,它允许客户端向服务器发送消息。
- 将突出显示的代码添加到 src/index.ts 文件 :
import "./css/main.css";
import * as signalR from "@aspnet/signalr";
const divMessages: HTMLDivElement = document.querySelector("#divMessages");
const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
const username = new Date().getTime();
const connection = new signalR.HubConnectionBuilder()
.withUrl("/hub")
.build();
connection.on("messageReceived", (username: string, message: string) => {
let m = document.createElement("div");
m.innerHTML =
`<div class="message-author">${username}</div><div>${message}</div>`;
divMessages.appendChild(m);
divMessages.scrollTop = divMessages.scrollHeight;
});
connection.start().catch(err => document.write(err));
tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
if (e.keyCode === 13) {
send();
}
});
btnSend.addEventListener("click", send);
function send() {
}
前面的代码支持从服务器接收消息。HubConnectionBuilder
类创建新的生成器,用于配置服务器连接。withUrl
函数配置中心 URL。
SignalR 启用客户端和服务器之间的消息交换。每个消息都有特定的名称。例如,名为 messageReceived
的消息可以运行负责在消息区域显示新消息的逻辑。可以通过 on
函数完成对特定消息的侦听。可以侦听任意数量的消息名称。还可以将参数传递到消息,例如所接收消息的作者姓名和内容。客户端收到一条消息后,会创建一个新的 div
元素并在其 innerHTML
属性中显示作者姓名和消息内容。新消息将添加到显示消息的主 div
元素中。
- 客户端可以接收消息后,将它配置为发送消息。将突出显示的代码添加到 src/index.ts 文件 :
import "./css/main.css";
import * as signalR from "@aspnet/signalr";
const divMessages: HTMLDivElement = document.querySelector("#divMessages");
const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
const username = new Date().getTime();
const connection = new signalR.HubConnectionBuilder()
.withUrl("/hub")
.build();
connection.on("messageReceived", (username: string, message: string) => {
let messageContainer = document.createElement("div");
messageContainer.innerHTML =
`<div class="message-author">${username}</div><div>${message}</div>`;
divMessages.appendChild(messageContainer);
divMessages.scrollTop = divMessages.scrollHeight;
});
connection.start().catch(err => document.write(err));
tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
if (e.keyCode === 13) {
send();
}
});
btnSend.addEventListener("click", send);
function send() {
connection.send("newMessage", username, tbMessage.value)
.then(() => tbMessage.value = "");
}
通过 WebSockets 连接发送消息需要调用 send
方法。该方法的第一个参数是消息名称。消息数据包含其他参数。在此示例中,一条标识为 newMessage
的消息已发送到服务器。该消息包含用户名和文本框中的用户输入。如果发送成功,会清空文本框。
- 将
NewMessage
方法添加到ChatHub
类:
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SignalRWebPack.Hubs
{
public class ChatHub : Hub
{
public async Task NewMessage(long username, string message)
{
await Clients.All.SendAsync("messageReceived", username, message);
}
}
}
服务器收到消息后,前面的代码会将这些消息播发到所有连接的用户。没有必要使用泛型 on
方法接收所有消息。使用以消息名称命名的方法就可以了。
在此示例中,TypeScript 客户端发送一条标识为 newMessage
的消息。C# NewMessage
方法需要客户端发送的数据。在 Clients.All 上对 SendAsync 进行调用。接收的消息会发送到所有连接到中心的客户端。
测试应用Test the app
确认应用遵循以下步骤。
- 在 release 模式下运行 Webpack 。使用“包管理器控制台”窗口,在项目根目录中运行以下命令 。如果不在项目根中,请在输入该命令之前输入
cd SignalRWebPack
。
npm run release
此命令在运行应用时生成要提供的客户端资产。资产位于 wwwroot 文件夹。
Webpack 完成了以下任务:
- 清除了 wwwroot 目录的内容。
- 将 TypeScript 转换为 JavaScript,该过程称为“转译”。
- 破坏生成的 JavaScript 以降低文件大小,该过程称为“缩小”。
- 将已处理的 JavaScript、CSS 和 HTML 文件从 src 复制到 wwwroot 目录。
- 将以下元素注入 wwwroot/index.html 文件:
- 一个引用 wwwroot/main.
.css 文件的 <link>
标记。此标记紧挨着</head>
结束标记之前。 - 一个引用缩小后的 wwwroot/main.
.js 文件的 <script>
标记。此标记紧挨着</body>
结束标记之前。 - 选择“调试” > “开始执行(不调试)”,在不附加调试器的情况下在浏览器中启动应用 。在
http://localhost:<port_number>
上提供 wwwroot/index.html 文件。
- 一个引用 wwwroot/main.
打开另一个浏览器实例(任意浏览器)。在地址栏中粘贴 URL。
选择一个浏览器,在“消息”文本框中键入任意内容,然后单击“发送”按钮 。两个页面上立即显示唯一的用户名和消息。
- 通过在项目根中执行以下命令以 release 模式运行 Webpack :
npm run release
此命令在运行应用时生成要提供的客户端资产。资产位于 wwwroot 文件夹。
Webpack 完成了以下任务:
- 清除了 wwwroot 目录的内容。
- 将 TypeScript 转换为 JavaScript,该过程称为“转译”。
- 破坏生成的 JavaScript 以降低文件大小,该过程称为“缩小”。
- 将已处理的 JavaScript、CSS 和 HTML 文件从 src 复制到 wwwroot 目录。
- 将以下元素注入 wwwroot/index.html 文件:
- 一个引用 wwwroot/main.
.css 文件的 <link>
标记。此标记紧挨着</head>
结束标记之前。 - 一个引用缩小后的 wwwroot/main.
.js 文件的 <script>
标记。此标记紧挨着</body>
结束标记之前。 - 通过在项目根中执行以下命令生成和运行应用:
- 一个引用 wwwroot/main.
dotnet run
Web 服务器启动应用并在 localhost 上提供。
打开浏览器,转到
http://localhost:<port_number>
。提供 wwwroot/index.html 文件 。从地址栏复制 URL。打开另一个浏览器实例(任意浏览器)。在地址栏中粘贴 URL。
选择一个浏览器,在“消息”文本框中键入任意内容,然后单击“发送”按钮 。两个页面上立即显示唯一的用户名和消息。