教程:使用 TypeScript 和 Webpack 开始使用 ASP.NET Core SignalR
注意
此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本。
本教程演示如何在 ASP.NET Core SignalR Web 应用中使用 Webpack 来捆绑和构建用 TypeScript 编写的客户端。 开发人员可以通过 Webpack 捆绑和生成 Web 应用的客户端资源。
在本教程中,你将了解如何执行以下操作:
- 创建 ASP.NET Core SignalR 应用
- 配置 SignalR 服务器
- 使用 Webpack 配置生成管道
- 配置 SignalR TypeScript 客户端
- 启用客户端和服务器之间的通信
先决条件
带有 ASP.NET 和 Web 开发工作负载的 Visual Studio 2022。

创建 ASP.NET Core Web 应用
默认情况下,Visual Studio 使用在安装目录中找到的 npm 版本。 配置 Visual Studio,在 PATH 环境变量中查找 npm:
启动 Visual Studio。 在“启动”窗口中,选择“继续但无需代码”。
导航到“工具”>“选项”>“项目和解决方案”>“Web 包管理”>“外部 Web 工具”。
在列表中选择
$(PATH)项。 选择向上键将项移动列表中的第二个位置,然后选择“确定”:
。
新建 ASP.NET Core Web 应用:
- 使用“文件”>“新建”>“项目”菜单选项,然后选择“ASP.NET Core 空”模板。 选择“下一页”。
- 将项目命名为
SignalRWebpack并选择“创建”。 - 从“框架”下拉列表中选择“.NET 8.0(长期支持)”。 选择创建。
将 Microsoft.TypeScript.MSBuild NuGet 包添加到项目:
- 在“解决方案资源管理器”中,右键单击项目节点,然后选择“管理 NuGet 包”。 在“浏览”选项卡中,搜索
Microsoft.TypeScript.MSBuild,然后选择右侧的“安装”来安装包。
Visual Studio 会将 NuGet 包添加到解决方案资源管理器中的“依赖项”节点下,从而在项目中启用 TypeScript 编译 。
配置服务器
在本部分,配置 ASP.NET Core Web 应用以发送和接收 SignalR 消息。
在
Program.cs中,调用 AddSignalR:var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR();同样,在
Program.cs中,调用 UseDefaultFiles 和 UseStaticFiles:var app = builder.Build(); app.UseDefaultFiles(); app.UseStaticFiles();上述代码允许服务器查找和处理
index.html文件。 无论用户输入完整 URL 还是 Web 应用的根 URL,都会提供该文件。在项目根目录
SignalRWebpack/中,为 SignalR类创建名为Hubs的新目录。创建包含以下代码的新文件
Hubs/ChatHub.cs:using Microsoft.AspNetCore.SignalR; 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 进行调用。
- 接收的消息会发送到所有连接到中心的客户端。
- TypeScript 客户端发送一条标识为
在
Program.cs顶部添加以下using语句来解析ChatHub引用:using SignalRWebpack.Hubs;在
Program.cs中,将/hub路由映射到ChatHub中心。 将显示Hello World!的代码替换为以下代码:app.MapHub<ChatHub>("/hub");
配置客户端
在本部分,创建一个 Node.js 项目,以使用 Webpack 将 TypeScript 转换为 JavaScript 并捆绑客户端资源,包括 HTML 和 CSS。
在项目根目录中运行以下命令,创建
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 css-loader html-webpack-plugin mini-css-extract-plugin ts-loader typescript webpack webpack-cli-E选项禁用 npm 将语义化版本控制范围运算符写到package.json的默认行为。 例如,使用"webpack": "5.76.1"而不是"webpack": "^5.76.1"。 此选项防止意外升级到新的包版本。有关详细信息,请参阅 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 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。
在项目根目录
SignalRWebpack/中,为客户端代码创建名为src的新目录。将
src目录及其内容从示例项目复制到项目根目录中。src包含以下文件:index.html,用于定义主页的样板标记:<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>ASP.NET Core SignalR with TypeScript and Webpack</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>css/main.css,用于为主页提供 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; }tsconfig.json,用于配置 TypeScript 编译器以生成与 ECMAScript 5 兼容的 JavaScript:{ "compilerOptions": { "target": "es5" } }index.ts:import * as signalR from "@microsoft/signalr"; 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(); const connection = new signalR.HubConnectionBuilder() .withUrl("/hub") .build(); connection.on("messageReceived", (username: string, message: string) => { const 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() { connection.send("newMessage", username, tbMessage.value) .then(() => (tbMessage.value = "")); }前面的代码检索对 DOM 元素的引用并附加两个事件处理程序:
keyup:当用户在tbMessage文本框中键入并在用户按“Enter”键调用send函数时触发。click:当用户选择“发送”按钮并调用send函数时触发。
HubConnectionBuilder类创建新的生成器,用于配置服务器连接。withUrl函数配置中心 URL。SignalR 启用客户端和服务器之间的消息交换。 每个消息都有特定的名称。 例如,名为
messageReceived的消息可以运行负责在消息区域显示新消息的逻辑。 可以通过on函数完成对特定消息的侦听。 可以侦听任意数量的消息名称。 还可以将参数传递到消息,例如所接收消息的作者姓名和内容。 客户端收到一条消息后,会创建一个新的div元素并在其innerHTML属性中显示作者姓名和消息内容。 它添加到显示消息的主要div元素。通过 WebSockets 连接发送消息需要调用
send方法。 该方法的第一个参数是消息名称。 消息数据包含其他参数。 在此示例中,一条标识为newMessage的消息已发送到服务器。 该消息包含用户名和文本框中的用户输入。 如果发送成功,会清空文本框。
在项目根目录运行以下命令:
npm i @microsoft/signalr @types/node上述的代码会安装:
- SignalR TypeScript 客户端,它允许客户端向服务器发送消息。
- 用于 node.js 的 TypeScript 类型定义,支持 Node.js 类型的编译时检查。
测试应用程序
确认应用遵循以下步骤:
在
release模式下运行 Webpack。 使用“包管理器控制台”窗口,在项目根目录中运行以下命令。npm run release此命令在运行应用时生成要提供的客户端资产。 资产位于
wwwroot文件夹。Webpack 完成了以下任务:
- 清除了
wwwroot目录的内容。 - 将 TypeScript 转换为 JavaScript,该过程称为“转译” 。
- 破坏生成的 JavaScript 以降低文件大小,该过程称为“缩小” 。
- 将已处理的 JavaScript、CSS 和 HTML 文件从
src复制到wwwroot目录。 - 将以下元素注入
wwwroot/index.html文件:- 一个引用
wwwroot/main.<hash>.css文件的<link>标记。 此标记紧挨着</head>结束标记之前。 - 一个引用缩小后的
wwwroot/main.<hash>.js文件的<script>标记。 此标记紧挨着</title>结束标记之后。
- 一个引用
- 清除了
选择“调试”>“开始执行(不调试)”,在不附加调试器的情况下在浏览器中启动应用 。 在
https://localhost:<port>上提供wwwroot/index.html文件。如果存在编译错误,请尝试关闭并重新打开解决方案。
打开另一个浏览器实例(任何浏览器),然后在地址栏中粘贴该 URL。
选择一个浏览器,在“消息”文本框中键入任意内容,然后选择“发送”按钮。 两个页面上立即显示唯一的用户名和消息。

后续步骤
其他资源
本教程演示如何在 ASP.NET Core SignalR Web 应用中使用 Webpack 来捆绑和构建用 TypeScript 编写的客户端。 开发人员可以通过 Webpack 捆绑和生成 Web 应用的客户端资源。
在本教程中,你将了解如何执行以下操作:
- 创建 ASP.NET Core SignalR 应用
- 配置 SignalR 服务器
- 使用 Webpack 配置生成管道
- 配置 SignalR TypeScript 客户端
- 启用客户端和服务器之间的通信
先决条件
带有 ASP.NET 和 Web 开发工作负载的 Visual Studio 2022。

创建 ASP.NET Core Web 应用
默认情况下,Visual Studio 使用在安装目录中找到的 npm 版本。 配置 Visual Studio,在 PATH 环境变量中查找 npm:
启动 Visual Studio。 在“启动”窗口中,选择“继续但无需代码”。
导航到“工具”>“选项”>“项目和解决方案”>“Web 包管理”>“外部 Web 工具”。
在列表中选择
$(PATH)项。 选择向上键将项移动列表中的第二个位置,然后选择“确定”:
。
新建 ASP.NET Core Web 应用:
- 使用“文件”>“新建”>“项目”菜单选项,然后选择“ASP.NET Core 空”模板。 选择“下一页”。
- 将项目命名为
SignalRWebpack并选择“创建”。 - 从“框架”下拉列表中,选择
.NET 7.0 (Standard Term Support)。 选择“创建”。
将 Microsoft.TypeScript.MSBuild NuGet 包添加到项目:
- 在“解决方案资源管理器”中,右键单击项目节点,然后选择“管理 NuGet 包”。 在“浏览”选项卡中,搜索
Microsoft.TypeScript.MSBuild,然后选择右侧的“安装”来安装包。
Visual Studio 会将 NuGet 包添加到解决方案资源管理器中的“依赖项”节点下,从而在项目中启用 TypeScript 编译 。
配置服务器
在本部分,配置 ASP.NET Core Web 应用以发送和接收 SignalR 消息。
在
Program.cs中,调用 AddSignalR:var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR();同样,在
Program.cs中,调用 UseDefaultFiles 和 UseStaticFiles:var app = builder.Build(); app.UseDefaultFiles(); app.UseStaticFiles();上述代码允许服务器查找和处理
index.html文件。 无论用户输入完整 URL 还是 Web 应用的根 URL,都会提供该文件。在项目根目录
SignalRWebpack/中,为 SignalR类创建名为Hubs的新目录。创建包含以下代码的新文件
Hubs/ChatHub.cs:using Microsoft.AspNetCore.SignalR; 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 进行调用。
- 接收的消息会发送到所有连接到中心的客户端。
- TypeScript 客户端发送一条标识为
在
Program.cs顶部添加以下using语句来解析ChatHub引用:using SignalRWebpack.Hubs;在
Program.cs中,将/hub路由映射到ChatHub中心。 将显示Hello World!的代码替换为以下代码:app.MapHub<ChatHub>("/hub");
配置客户端
在本部分,创建一个 Node.js 项目,以使用 Webpack 将 TypeScript 转换为 JavaScript 并捆绑客户端资源,包括 HTML 和 CSS。
在项目根目录中运行以下命令,创建
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 css-loader html-webpack-plugin mini-css-extract-plugin ts-loader typescript webpack webpack-cli-E选项禁用 npm 将语义化版本控制范围运算符写到package.json的默认行为。 例如,使用"webpack": "5.76.1"而不是"webpack": "^5.76.1"。 此选项防止意外升级到新的包版本。有关详细信息,请参阅 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 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 lang="en"> <head> <meta charset="utf-8" /> <title>ASP.NET Core SignalR with TypeScript and Webpack</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>css/main.css,用于为主页提供 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; }tsconfig.json,用于配置 TypeScript 编译器以生成与 ECMAScript 5 兼容的 JavaScript:{ "compilerOptions": { "target": "es5" } }index.ts:import * as signalR from "@microsoft/signalr"; 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(); const connection = new signalR.HubConnectionBuilder() .withUrl("/hub") .build(); connection.on("messageReceived", (username: string, message: string) => { const 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() { connection.send("newMessage", username, tbMessage.value) .then(() => (tbMessage.value = "")); }前面的代码检索对 DOM 元素的引用并附加两个事件处理程序:
keyup:当用户在tbMessage文本框中键入并在用户按“Enter”键调用send函数时触发。click:当用户选择“发送”按钮并调用send函数时触发。
HubConnectionBuilder类创建新的生成器,用于配置服务器连接。withUrl函数配置中心 URL。SignalR 启用客户端和服务器之间的消息交换。 每个消息都有特定的名称。 例如,名为
messageReceived的消息可以运行负责在消息区域显示新消息的逻辑。 可以通过on函数完成对特定消息的侦听。 可以侦听任意数量的消息名称。 还可以将参数传递到消息,例如所接收消息的作者姓名和内容。 客户端收到一条消息后,会创建一个新的div元素并在其innerHTML属性中显示作者姓名和消息内容。 它添加到显示消息的主要div元素。通过 WebSockets 连接发送消息需要调用
send方法。 该方法的第一个参数是消息名称。 消息数据包含其他参数。 在此示例中,一条标识为newMessage的消息已发送到服务器。 该消息包含用户名和文本框中的用户输入。 如果发送成功,会清空文本框。
在项目根目录运行以下命令:
npm i @microsoft/signalr @types/node上述的代码会安装:
- SignalR TypeScript 客户端,它允许客户端向服务器发送消息。
- 用于 node.js 的 TypeScript 类型定义,支持 Node.js 类型的编译时检查。
测试应用程序
确认应用遵循以下步骤:
在
release模式下运行 Webpack。 使用“包管理器控制台”窗口,在项目根目录中运行以下命令。npm run release此命令在运行应用时生成要提供的客户端资产。 资产位于
wwwroot文件夹。Webpack 完成了以下任务:
- 清除了
wwwroot目录的内容。 - 将 TypeScript 转换为 JavaScript,该过程称为“转译” 。
- 破坏生成的 JavaScript 以降低文件大小,该过程称为“缩小” 。
- 将已处理的 JavaScript、CSS 和 HTML 文件从
src复制到wwwroot目录。 - 将以下元素注入
wwwroot/index.html文件:- 一个引用
wwwroot/main.<hash>.css文件的<link>标记。 此标记紧挨着</head>结束标记之前。 - 一个引用缩小后的
wwwroot/main.<hash>.js文件的<script>标记。 此标记紧挨着</title>结束标记之后。
- 一个引用
- 清除了
选择“调试”>“开始执行(不调试)”,在不附加调试器的情况下在浏览器中启动应用 。 在
https://localhost:<port>上提供wwwroot/index.html文件。如果存在编译错误,请尝试关闭并重新打开解决方案。
打开另一个浏览器实例(任何浏览器),然后在地址栏中粘贴该 URL。
选择一个浏览器,在“消息”文本框中键入任意内容,然后选择“发送”按钮。 两个页面上立即显示唯一的用户名和消息。

后续步骤
其他资源
本教程演示如何在 ASP.NET Core SignalR Web 应用中使用 Webpack 来捆绑和构建用 TypeScript 编写的客户端。 开发人员可以通过 Webpack 捆绑和生成 Web 应用的客户端资源。
在本教程中,你将了解如何执行以下操作:
- 创建 ASP.NET Core SignalR 应用
- 配置 SignalR 服务器
- 使用 Webpack 配置生成管道
- 配置 SignalR TypeScript 客户端
- 启用客户端和服务器之间的通信
先决条件
- 带有 ASP.NET 和 Web 开发工作负载的 Visual Studio 2022。
- .NET 6.0 SDK
创建 ASP.NET Core Web 应用
默认情况下,Visual Studio 使用在安装目录中找到的 npm 版本。 配置 Visual Studio,在 PATH 环境变量中查找 npm:
启动 Visual Studio。 在“启动”窗口中,选择“继续但无需代码”。
导航到“工具”>“选项”>“项目和解决方案”>“Web 包管理”>“外部 Web 工具”。
在列表中选择
$(PATH)项。 选择向上键将项移动列表中的第二个位置,然后选择“确定”:
。
新建 ASP.NET Core Web 应用:
- 使用“文件”>“新建”>“项目”菜单选项,然后选择“ASP.NET Core 空”模板。 选择“下一页”。
- 将项目命名为
SignalRWebpack并选择“创建”。 - 从“框架”下拉列表中,选择
.NET 6.0 (Long Term Support)。 选择“创建”。
将 Microsoft.TypeScript.MSBuild NuGet 包添加到项目:
- 在“解决方案资源管理器”中,右键单击项目节点,然后选择“管理 NuGet 包”。 在“浏览”选项卡中,搜索
Microsoft.TypeScript.MSBuild,然后选择右侧的“安装”来安装包。
Visual Studio 会将 NuGet 包添加到解决方案资源管理器中的“依赖项”节点下,从而在项目中启用 TypeScript 编译 。
配置服务器
在本部分,配置 ASP.NET Core Web 应用以发送和接收 SignalR 消息。
在
Program.cs中,调用 AddSignalR:var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR();同样,在
Program.cs中,调用 UseDefaultFiles 和 UseStaticFiles:var app = builder.Build(); app.UseDefaultFiles(); app.UseStaticFiles();上述代码允许服务器查找和处理
index.html文件。 无论用户输入完整 URL 还是 Web 应用的根 URL,都会提供该文件。在项目根目录
SignalRWebpack/中,为 SignalR类创建名为Hubs的新目录。创建包含以下代码的新文件
Hubs/ChatHub.cs:using Microsoft.AspNetCore.SignalR; 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 进行调用。 接收的消息会发送到所有连接到中心的客户端。在
Program.cs顶部添加以下using语句来解析ChatHub引用:using SignalRWebpack.Hubs;在
Program.cs中,将/hub路由映射到ChatHub中心。 将显示Hello World!的代码替换为以下代码:app.MapHub<ChatHub>("/hub");
配置客户端
在本部分,创建一个 Node.js 项目,以使用 Webpack 将 TypeScript 转换为 JavaScript 并捆绑客户端资源,包括 HTML 和 CSS。
在项目根目录中运行以下命令,创建
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 css-loader html-webpack-plugin mini-css-extract-plugin ts-loader typescript webpack webpack-cli-E选项禁用 npm 将语义化版本控制范围运算符写到package.json的默认行为。 例如,使用"webpack": "5.70.0"而不是"webpack": "^5.70.0"。 此选项防止意外升级到新的包版本。有关详细信息,请参阅 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 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 lang="en"> <head> <meta charset="utf-8" /> <title>ASP.NET Core SignalR with TypeScript and Webpack</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>css/main.css,用于为主页提供 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; }tsconfig.json,用于配置 TypeScript 编译器以生成与 ECMAScript 5 兼容的 JavaScript:{ "compilerOptions": { "target": "es5" } }index.ts:import * as signalR from "@microsoft/signalr"; 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(); const connection = new signalR.HubConnectionBuilder() .withUrl("/hub") .build(); connection.on("messageReceived", (username: string, message: string) => { const 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() { connection.send("newMessage", username, tbMessage.value) .then(() => (tbMessage.value = "")); }
前面的代码检索对 DOM 元素的引用并附加两个事件处理程序:
keyup:当用户在tbMessage文本框中键入并在用户按“Enter”键调用send函数时触发。click:当用户选择“发送”按钮并调用send函数时触发。
HubConnectionBuilder类创建新的生成器,用于配置服务器连接。withUrl函数配置中心 URL。SignalR 启用客户端和服务器之间的消息交换。 每个消息都有特定的名称。 例如,名为
messageReceived的消息可以运行负责在消息区域显示新消息的逻辑。 可以通过on函数完成对特定消息的侦听。 可以侦听任意数量的消息名称。 还可以将参数传递到消息,例如所接收消息的作者姓名和内容。 客户端收到一条消息后,会创建一个新的div元素并在其innerHTML属性中显示作者姓名和消息内容。 它添加到显示消息的主要div元素。通过 WebSockets 连接发送消息需要调用
send方法。 该方法的第一个参数是消息名称。 消息数据包含其他参数。 在此示例中,一条标识为newMessage的消息已发送到服务器。 该消息包含用户名和文本框中的用户输入。 如果发送成功,会清空文本框。在项目根目录运行以下命令:
npm i @microsoft/signalr @types/node上述的代码会安装:
- SignalR TypeScript 客户端,它允许客户端向服务器发送消息。
- 用于 node.js 的 TypeScript 类型定义,支持 Node.js 类型的编译时检查。
测试应用程序
确认应用遵循以下步骤:
在
release模式下运行 Webpack。 使用“包管理器控制台”窗口,在项目根目录中运行以下命令。 如果不在项目根中,请在输入该命令之前输入cd SignalRWebpack。npm run release此命令在运行应用时生成要提供的客户端资产。 资产位于
wwwroot文件夹。Webpack 完成了以下任务:
- 清除了
wwwroot目录的内容。 - 将 TypeScript 转换为 JavaScript,该过程称为“转译” 。
- 破坏生成的 JavaScript 以降低文件大小,该过程称为“缩小” 。
- 将已处理的 JavaScript、CSS 和 HTML 文件从
src复制到wwwroot目录。 - 将以下元素注入
wwwroot/index.html文件:- 一个引用
wwwroot/main.<hash>.css文件的<link>标记。 此标记紧挨着</head>结束标记之前。 - 一个引用缩小后的
wwwroot/main.<hash>.js文件的<script>标记。 此标记紧挨着</title>结束标记之后。
- 一个引用
- 清除了
选择“调试”>“开始执行(不调试)”,在不附加调试器的情况下在浏览器中启动应用 。 在
https://localhost:<port>上提供wwwroot/index.html文件。如果遇到编译错误,请尝试关闭并重新打开解决方案。
打开另一个浏览器实例(任何浏览器),然后在地址栏中粘贴该 URL。
选择一个浏览器,在“消息”文本框中键入任意内容,然后选择“发送”按钮。 两个页面上立即显示唯一的用户名和消息。

后续步骤
其他资源
本教程演示如何在 ASP.NET Core SignalR Web 应用中使用 Webpack 来捆绑和构建用 TypeScript 编写的客户端。 开发人员可以通过 Webpack 捆绑和生成 Web 应用的客户端资源。
在本教程中,你将了解:
- 为入门 ASP.NET Core SignalR 应用搭建基架
- 配置 SignalR TypeScript 客户端
- 使用 Webpack 配置生成管道
- 配置 SignalR 服务器
- 启用客户端和服务器之间的通信
先决条件
- Visual Studio 2019 与 ASP.NET 和 Web 开发工作负载
- .NET Core SDK 3.0 或更高版本
- 带 npm 的 Node.js
创建 ASP.NET Core Web 应用
配置 Visual Studio,在 PATH 环境变量中查找 npm。 默认情况下,Visual Studio 使用在安装目录中找到的 npm 版本。 在 Visual Studio 中按照以下说明执行操作:
启动 Visual Studio。 在“启动”窗口中,选择“继续但无需代码”。
导航到“工具”>“选项”>“项目和解决方案”>“Web 包管理”>“外部 Web 工具”。
在列表中选择 $(PATH) 项。 选择向上键将项移动列表中的第二个位置,然后选择“确定”。
。
Visual Studio 配置完成。
- 使用“文件”>“新建”>“项目”菜单选项,然后选择“ASP.NET Core Web 应用程序”模板 。 选择“下一步”。
- 将项目命名为 *SignalRWebPac`` 并选择“创建”。
- 从目标框架下拉列表选择 .NET Core 并从框架选择器下拉列表选择 ASP.NET Core 3.1 。 选择“空白”模板并选择“创建” 。
将 Microsoft.TypeScript.MSBuild 包添加到项目:
- 在“解决方案资源管理器”(右侧窗格)中,右键单击项目节点,然后选择“管理 NuGet 包” 。 在“浏览”选项卡中,搜索
Microsoft.TypeScript.MSBuild,然后单击右侧的“安装”来安装包 。
Visual Studio 会将 NuGet 包添加到解决方案资源管理器中的“依赖项”节点下,从而在项目中启用 TypeScript 编译 。
配置 Webpack 和 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 将语义化版本控制范围运算符写到 *packagejson的默认行为。 例如,使用"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 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函数。
配置应用
在
Startup.Configure中,添加对 UseDefaultFiles(IApplicationBuilder) 和 UseStaticFiles(IApplicationBuilder) 的调用。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;
启用客户端和服务器通信
应用目前显示用于发送消息的基本窗体,但尚不能正常工作。 服务器正在侦听特定的路由,但是不涉及发送消息。
在项目根目录运行以下命令:
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 进行调用。 接收的消息会发送到所有连接到中心的客户端。
测试应用
确认应用遵循以下步骤。
在 release 模式下运行 Webpack。 使用“包管理器控制台”窗口,在项目根目录中运行以下命令。 如果不在项目根中,请在输入该命令之前输入
cd SignalRWebPack。npm run release此命令在运行应用时生成要提供的客户端资产。 资产位于
wwwroot文件夹。Webpack 完成了以下任务:
- 清除了
wwwroot目录的内容。 - 将 TypeScript 转换为 JavaScript,该过程称为“转译” 。
- 破坏生成的 JavaScript 以降低文件大小,该过程称为“缩小” 。
- 将已处理的 JavaScript、CSS 和 HTML 文件从
src复制到wwwroot目录。 - 将以下元素注入
wwwroot/index.html文件:- 一个引用
wwwroot/main.<hash>.css文件的<link>标记。 此标记紧挨着</head>结束标记之前。 - 一个引用缩小后的
wwwroot/main.<hash>.js文件的<script>标记。 此标记紧挨着</title>结束标记之后。
- 一个引用
- 清除了
选择“调试”>“开始执行(不调试)”,在不附加调试器的情况下在浏览器中启动应用 。 在
http://localhost:<port_number>上提供wwwroot/index.html文件。如果遇到编译错误,请尝试关闭并重新打开解决方案。
打开另一个浏览器实例(任意浏览器)。 在地址栏中粘贴 URL。
选择一个浏览器,在“消息”文本框中键入任意内容,然后选择“发送”按钮。 两个页面上立即显示唯一的用户名和消息。

其他资源
本教程演示如何在 ASP.NET Core SignalR Web 应用中使用 Webpack 来捆绑和构建用 TypeScript 编写的客户端。 开发人员可以通过 Webpack 捆绑和生成 Web 应用的客户端资源。
在本教程中,你将了解:
- 为入门 ASP.NET Core SignalR 应用搭建基架
- 配置 SignalR TypeScript 客户端
- 使用 Webpack 配置生成管道
- 配置 SignalR 服务器
- 启用客户端和服务器之间的通信
先决条件
- Visual Studio 2019 与 ASP.NET 和 Web 开发工作负载
- .NET Core SDK 2.2 或更高版本
- 带 npm 的 Node.js
创建 ASP.NET Core Web 应用
配置 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 。 选择“空白”模板并选择“创建” 。
配置 Webpack 和 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 将语义化版本控制范围运算符写到 *packagejson的默认行为。 例如,使用"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 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 应用
Startup.Configure方法中提供的代码显示 Hello World!。 将app.Run方法调用替换为对 UseDefaultFiles(IApplicationBuilder) 和 UseStaticFiles(IApplicationBuilder) 的调用。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;
启用客户端和服务器通信
应用当前显示一个发送消息的简单窗体。 尝试执行此操作时没有任何反应。 服务器正在侦听特定的路由,但是不涉及发送消息。
在项目根目录运行以下命令:
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 进行调用。 接收的消息会发送到所有连接到中心的客户端。
测试应用
确认应用遵循以下步骤。
在 release 模式下运行 Webpack。 使用“包管理器控制台”窗口,在项目根目录中运行以下命令。 如果不在项目根中,请在输入该命令之前输入
cd SignalRWebPack。npm run release此命令在运行应用时生成要提供的客户端资产。 资产位于
wwwroot文件夹。Webpack 完成了以下任务:
- 清除了
wwwroot目录的内容。 - 将 TypeScript 转换为 JavaScript,该过程称为“转译” 。
- 破坏生成的 JavaScript 以降低文件大小,该过程称为“缩小” 。
- 将已处理的 JavaScript、CSS 和 HTML 文件从
src复制到wwwroot目录。 - 将以下元素注入
wwwroot/index.html文件:- 一个引用
wwwroot/main.<hash>.css文件的<link>标记。 此标记紧挨着</head>结束标记之前。 - 一个引用缩小后的
wwwroot/main.<hash>.js文件的<script>标记。 此标记紧挨着</title>结束标记之后。
- 一个引用
- 清除了
选择“调试”>“开始执行(不调试)”,在不附加调试器的情况下在浏览器中启动应用 。 在
http://localhost:<port_number>上提供wwwroot/index.html文件。打开另一个浏览器实例(任意浏览器)。 在地址栏中粘贴 URL。
选择一个浏览器,在“消息”文本框中键入任意内容,然后选择“发送”按钮。 两个页面上立即显示唯一的用户名和消息。

其他资源
反馈
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:https://aka.ms/ContentUserFeedback。
提交和查看相关反馈