配合使用 ASP.NET Core SignalR 和 TypeScript 以及 WebpackUse ASP.NET Core SignalR with TypeScript and Webpack

本文内容

作者:Sébastien SougnezScott Addie

开发人员可以通过 Webpack 捆绑和生成 Web 应用的客户端资源。本教程介绍在 ASP.NET Core SignalR Web 应用中使用 Webpack,该应用的客户端是使用 TypeScript 编写的。

在本教程中,你将了解:

  • 为入门 ASP.NET Core SignalR 应用搭建基架
  • 配置 SignalR TypeScript 客户端
  • 使用 Webpack 配置生成管道
  • 配置 SignalR 服务器
  • 启用客户端和服务器之间的通信

查看或下载示例代码如何下载

先决条件Prerequisites

创建 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 配置

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 编译 。

在“集成终端”中运行以下命令 :

  1. dotnet new web -o SignalRWebPack
  2. code -r SignalRWebPack
  • dotnet new 命令会在 SignalRWebPack 目录中创建一个空的 ASP.NET Core Web 应用 。
  • code 命令会在 Visual Studio Code 的当前实例中打开 SignalRWebPack 文件夹 。

在“集成终端”中运行以下 .NET Core CLI 命令 :

  1. dotnet add package Microsoft.TypeScript.MSBuild

上述命令将添加 Microsoft.TypeScript.MSBuild 包,从而在项目中启用 TypeScript 编译。

配置 Webpack 和 TypeScriptConfigure Webpack and TypeScript

以下步骤配置 TypeScript 到 JavaScript 的转换和客户端资源的捆绑。

  • 在项目根目录中运行以下命令,创建 package.json 文件 :
  1. npm init -y
  • 将突出显示的属性添加到 package.json 文件并保存文件更改 :
  1. {
  2. "name": "SignalRWebPack",
  3. "version": "1.0.0",
  4. "private": true,
  5. "description": "",
  6. "main": "index.js",
  7. "scripts": {
  8. "test": "echo \"Error: no test specified\" && exit 1"
  9. },
  10. "keywords": [],
  11. "author": "",
  12. "license": "ISC"
  13. }

private 属性设置为 true,防止下一步出现包安装警告。

  • 安装所需的 npm 包。从项目根目录运行以下命令:
  1. 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 属性替换为以下代码 :
  1. "scripts": {
  2. "build": "webpack --mode=development --watch",
  3. "release": "webpack --mode=production",
  4. "publish": "npm run release && dotnet publish -c Release"
  5. },

脚本的一些解释:

  • build:在开发模式下捆绑客户端资源并观察文件更改。文件观察程序使捆绑在每次项目文件发生更改时重新生成。mode 选项可禁用生产优化,例如摇树优化和缩小优化。仅在开发中使用 build
  • release:在生产模式下捆绑客户端资源。
  • publish:运行 release 脚本,在生产模式下捆绑客户端资源。它调用 .NET Core CLI 的 publish 命令发布应用。
    • 在项目根目录中创建名为 webpack.config.js 的文件,使其包含以下代码 :
  1. const path = require("path");
  2. const HtmlWebpackPlugin = require("html-webpack-plugin");
  3. const { CleanWebpackPlugin } = require("clean-webpack-plugin");
  4. const MiniCssExtractPlugin = require("mini-css-extract-plugin");
  5. module.exports = {
  6. entry: "./src/index.ts",
  7. output: {
  8. path: path.resolve(__dirname, "wwwroot"),
  9. filename: "[name].[chunkhash].js",
  10. publicPath: "/"
  11. },
  12. resolve: {
  13. extensions: [".js", ".ts"]
  14. },
  15. module: {
  16. rules: [
  17. {
  18. test: /\.ts$/,
  19. use: "ts-loader"
  20. },
  21. {
  22. test: /\.css$/,
  23. use: [MiniCssExtractPlugin.loader, "css-loader"]
  24. }
  25. ]
  26. },
  27. plugins: [
  28. new CleanWebpackPlugin(),
  29. new HtmlWebpackPlugin({
  30. template: "./src/index.html"
  31. }),
  32. new MiniCssExtractPlugin({
  33. filename: "css/[name].[chunkhash].css"
  34. })
  35. ]
  36. };

前面的文件配置 Webpack 编译。需要注意的一些配置细节:

  • output 属性替代 dist 的默认值 。捆绑反而在 wwwroot 目录中发出 。
  • resolve.extensions 数组包含 .js,以便导入 SignalR 客户端 JavaScript 。
    • 在项目根目录中创建新的 src 目录,以存储项目的客户端资产 。
  • 创建包含以下标记的 src/index.html 。
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8" />
  5. <title>ASP.NET Core SignalR</title>
  6. </head>
  7. <body>
  8. <div id="divMessages" class="messages">
  9. </div>
  10. <div class="input-zone">
  11. <label id="lblMessage" for="tbMessage">Message:</label>
  12. <input id="tbMessage" class="input-zone-input" type="text" />
  13. <button id="btnSend">Send</button>
  14. </div>
  15. </body>
  16. </html>

前面的 HTML 定义主页的样板标记。

  • 创建新的 src/css 目录 。目的是存储项目的 .css 文件 。

  • 创建包含以下 CSS 的 src/css/main.css :

  1. *, *::before, *::after {
  2. box-sizing: border-box;
  3. }
  4. html, body {
  5. margin: 0;
  6. padding: 0;
  7. }
  8. .input-zone {
  9. align-items: center;
  10. display: flex;
  11. flex-direction: row;
  12. margin: 10px;
  13. }
  14. .input-zone-input {
  15. flex: 1;
  16. margin-right: 10px;
  17. }
  18. .message-author {
  19. font-weight: bold;
  20. }
  21. .messages {
  22. border: 1px solid #000;
  23. margin: 10px;
  24. max-height: 300px;
  25. min-height: 300px;
  26. overflow-y: auto;
  27. padding: 5px;
  28. }

前面的 main.css 文件设计应用样式 。

  • 创建包含以下 JSON 的 src/tsconfig.json :
  1. {
  2. "compilerOptions": {
  3. "target": "es5"
  4. }
  5. }

前面的代码配置 TypeScript 编译器,生成与 ECMAScript 5 兼容的 JavaScript。

  • 创建包含以下代码的 src/index.ts :
  1. import "./css/main.css";
  2. const divMessages: HTMLDivElement = document.querySelector("#divMessages");
  3. const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
  4. const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
  5. const username = new Date().getTime();
  6. tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
  7. if (e.key === "Enter") {
  8. send();
  9. }
  10. });
  11. btnSend.addEventListener("click", send);
  12. function send() {
  13. }

前面的 TypeScript 检索对 DOM 元素的引用并附加两个事件处理程序:

  • keyup:用户在 tbMessage 文本框中键入时触发此事件。用户按 Enter 时调用 send 函数 。
  • click:用户单击“发送”按钮时触发此事件 。调用 send 函数。

配置应用Configure the app

  1. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  2. {
  3. if (env.IsDevelopment())
  4. {
  5. app.UseDeveloperExceptionPage();
  6. }
  7. app.UseRouting();
  8. app.UseDefaultFiles();
  9. app.UseStaticFiles();
  10. app.UseEndpoints(endpoints =>
  11. {
  12. endpoints.MapHub<ChatHub>("/hub");
  13. });
  14. }

上述代码允许服务器查找和处理 index.html 文件 。无论用户输入完整 URL 还是 Web 应用的根 URL,都会提供该文件。

  • Startup.Configure 的末尾,将 /hub 路由映射到 ChatHub 中心 。将显示 Hello World! 的代码替换为以下行:
  1. app.UseEndpoints(endpoints =>
  2. {
  3. endpoints.MapHub<ChatHub>("/hub");
  4. });
  • Startup.ConfigureServices中,调用 AddSignalR
  1. services.AddSignalR();
  • 在项目根目录 SignalRWebPack/ 中创建名为 Hubs 的新目录,以存储 SignalR 中心 。

  • 创建包含以下代码的中心 Hubs/ChatHub.cs :

  1. using Microsoft.AspNetCore.SignalR;
  2. using System.Threading.Tasks;
  3. namespace SignalRWebPack.Hubs
  4. {
  5. public class ChatHub : Hub
  6. {
  7. }
  8. }
  • 在 Startup.cs 文件顶部添加以下 using 语句来解析 ChatHub 引用 :
  1. using SignalRWebPack.Hubs;

启用客户端和服务器通信Enable client and server communication

应用目前显示用于发送消息的基本窗体,但尚不能正常工作。服务器正在侦听特定的路由,但是不涉及发送消息。

  • 在项目根目录运行以下命令:
  1. npm i @microsoft/signalr @types/node

上述的代码会安装:

  • SignalR TypeScript 客户端,它允许客户端向服务器发送消息。
  • 用于 node.js 的 TypeScript 类型定义,支持 Node.js 类型的编译时检查。
    • 将突出显示的代码添加到 src/index.ts 文件 :
  1. import "./css/main.css";
  2. import * as signalR from "@microsoft/signalr";
  3. const divMessages: HTMLDivElement = document.querySelector("#divMessages");
  4. const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
  5. const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
  6. const username = new Date().getTime();
  7. const connection = new signalR.HubConnectionBuilder()
  8. .withUrl("/hub")
  9. .build();
  10. connection.on("messageReceived", (username: string, message: string) => {
  11. let m = document.createElement("div");
  12. m.innerHTML =
  13. `<div class="message-author">${username}</div><div>${message}</div>`;
  14. divMessages.appendChild(m);
  15. divMessages.scrollTop = divMessages.scrollHeight;
  16. });
  17. connection.start().catch(err => document.write(err));
  18. tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
  19. if (e.key === "Enter") {
  20. send();
  21. }
  22. });
  23. btnSend.addEventListener("click", send);
  24. function send() {
  25. }

前面的代码支持从服务器接收消息。HubConnectionBuilder 类创建新的生成器,用于配置服务器连接。withUrl 函数配置中心 URL。

SignalR 启用客户端和服务器之间的消息交换。每个消息都有特定的名称。例如,名为 messageReceived 的消息可以运行负责在消息区域显示新消息的逻辑。可以通过 on 函数完成对特定消息的侦听。可以侦听任意数量的消息名称。还可以将参数传递到消息,例如所接收消息的作者姓名和内容。客户端收到一条消息后,会创建一个新的 div 元素并在其 innerHTML 属性中显示作者姓名和消息内容。它添加到显示消息的主要 div 元素。

  • 客户端可以接收消息后,将它配置为发送消息。将突出显示的代码添加到 src/index.ts 文件 :
  1. import "./css/main.css";
  2. import * as signalR from "@microsoft/signalr";
  3. const divMessages: HTMLDivElement = document.querySelector("#divMessages");
  4. const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
  5. const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
  6. const username = new Date().getTime();
  7. const connection = new signalR.HubConnectionBuilder()
  8. .withUrl("/hub")
  9. .build();
  10. connection.on("messageReceived", (username: string, message: string) => {
  11. let messages = document.createElement("div");
  12. messages.innerHTML =
  13. `<div class="message-author">${username}</div><div>${message}</div>`;
  14. divMessages.appendChild(messages);
  15. divMessages.scrollTop = divMessages.scrollHeight;
  16. });
  17. connection.start().catch(err => document.write(err));
  18. tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
  19. if (e.key === "Enter") {
  20. send();
  21. }
  22. });
  23. btnSend.addEventListener("click", send);
  24. function send() {
  25. connection.send("newMessage", username, tbMessage.value)
  26. .then(() => tbMessage.value = "");
  27. }

通过 WebSockets 连接发送消息需要调用 send 方法。该方法的第一个参数是消息名称。消息数据包含其他参数。在此示例中,一条标识为 newMessage 的消息已发送到服务器。该消息包含用户名和文本框中的用户输入。如果发送成功,会清空文本框。

  • NewMessage 方法添加到 ChatHub 类:
  1. using Microsoft.AspNetCore.SignalR;
  2. using System.Threading.Tasks;
  3. namespace SignalRWebPack.Hubs
  4. {
  5. public class ChatHub : Hub
  6. {
  7. public async Task NewMessage(long username, string message)
  8. {
  9. await Clients.All.SendAsync("messageReceived", username, message);
  10. }
  11. }
  12. }

服务器收到消息后,前面的代码会将这些消息播发到所有连接的用户。没有必要使用泛型 on 方法接收所有消息。使用以消息名称命名的方法就可以了。

在此示例中,TypeScript 客户端发送一条标识为 newMessage 的消息。C# NewMessage 方法需要客户端发送的数据。在 Clients.All 上对 SendAsync 进行调用。接收的消息会发送到所有连接到中心的客户端。

测试应用Test the app

确认应用遵循以下步骤。

  • 在 release 模式下运行 Webpack 。使用“包管理器控制台”窗口,在项目根目录中运行以下命令 。如果不在项目根中,请在输入该命令之前输入 cd SignalRWebPack
  1. 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&gt; 上提供 wwwroot/index.html 文件。

如果遇到编译错误,请尝试关闭并重新打开解决方案。

  • 打开另一个浏览器实例(任意浏览器)。在地址栏中粘贴 URL。

  • 选择一个浏览器,在“消息”文本框中键入任意内容,然后单击“发送”按钮 。两个页面上立即显示唯一的用户名和消息。

  • 通过在项目根中执行以下命令以 release 模式运行 Webpack :
  1. 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> 结束标记之前。
    • 通过在项目根中执行以下命令生成和运行应用:
  1. dotnet run

Web 服务器启动应用并在 localhost 上提供。

  • 打开浏览器,转到 http://localhost:<port_number&gt;。提供 wwwroot/index.html 文件 。从地址栏复制 URL。

  • 打开另一个浏览器实例(任意浏览器)。在地址栏中粘贴 URL。

  • 选择一个浏览器,在“消息”文本框中键入任意内容,然后单击“发送”按钮 。两个页面上立即显示唯一的用户名和消息。

两个浏览器窗口都显示的消息

先决条件Prerequisites

创建 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 配置

已完成 Visual Studio 配置。可以开始创建项目了。

  • 使用“文件”>“新建”>“项目”菜单选项,然后选择“ASP.NET Core Web 应用程序”模板 。
  • 将项目命名为 SignalRWebPack 并选择“创建” 。
  • 从目标框架下拉列表选择 .NET Core 并从框架选择器下拉列表选择 ASP.NET Core 2.2 。选择“空白”模板并选择“创建” 。

在“集成终端”中运行以下命令 :

  1. dotnet new web -o SignalRWebPack

SignalRWebPack 目录中创建了一个面向 .NET Core 的空 ASP.NET Core Web 应用 。

配置 Webpack 和 TypeScriptConfigure Webpack and TypeScript

以下步骤配置 TypeScript 到 JavaScript 的转换和客户端资源的捆绑。

  • 在项目根目录中运行以下命令,创建 package.json 文件 :
  1. npm init -y
  • 将突出显示的属性添加到 package.json 文件 :
  1. {
  2. "name": "SignalRWebPack",
  3. "version": "1.0.0",
  4. "private": true,
  5. "description": "",
  6. "main": "index.js",
  7. "scripts": {
  8. "test": "echo \"Error: no test specified\" && exit 1"
  9. },
  10. "keywords": [],
  11. "author": "",
  12. "license": "ISC"
  13. }

private 属性设置为 true,防止下一步出现包安装警告。

  • 安装所需的 npm 包。从项目根目录运行以下命令:
  1. 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 属性替换为以下代码 :
  1. "scripts": {
  2. "build": "webpack --mode=development --watch",
  3. "release": "webpack --mode=production",
  4. "publish": "npm run release && dotnet publish -c Release"
  5. },

脚本的一些解释:

  • build:在开发模式下捆绑客户端资源并观察文件更改。文件观察程序使捆绑在每次项目文件发生更改时重新生成。mode 选项可禁用生产优化,例如摇树优化和缩小优化。仅在开发中使用 build
  • release:在生产模式下捆绑客户端资源。
  • publish:运行 release 脚本,在生产模式下捆绑客户端资源。它调用 .NET Core CLI 的 publish 命令发布应用。
    • 在项目根目录中创建名为 webpack.config.js 的文件,使其包含以下代码 :
  1. const path = require("path");
  2. const HtmlWebpackPlugin = require("html-webpack-plugin");
  3. const CleanWebpackPlugin = require("clean-webpack-plugin");
  4. const MiniCssExtractPlugin = require("mini-css-extract-plugin");
  5. module.exports = {
  6. entry: "./src/index.ts",
  7. output: {
  8. path: path.resolve(__dirname, "wwwroot"),
  9. filename: "[name].[chunkhash].js",
  10. publicPath: "/"
  11. },
  12. resolve: {
  13. extensions: [".js", ".ts"]
  14. },
  15. module: {
  16. rules: [
  17. {
  18. test: /\.ts$/,
  19. use: "ts-loader"
  20. },
  21. {
  22. test: /\.css$/,
  23. use: [MiniCssExtractPlugin.loader, "css-loader"]
  24. }
  25. ]
  26. },
  27. plugins: [
  28. new CleanWebpackPlugin(["wwwroot/*"]),
  29. new HtmlWebpackPlugin({
  30. template: "./src/index.html"
  31. }),
  32. new MiniCssExtractPlugin({
  33. filename: "css/[name].[chunkhash].css"
  34. })
  35. ]
  36. };

前面的文件配置 Webpack 编译。需要注意的一些配置细节:

  • output 属性替代 dist 的默认值 。捆绑反而在 wwwroot 目录中发出 。
  • resolve.extensions 数组包含 .js,以便导入 SignalR 客户端 JavaScript 。
    • 在项目根目录中创建新的 src 目录,以存储项目的客户端资产 。
  • 创建包含以下标记的 src/index.html 。
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8" />
  5. <title>ASP.NET Core SignalR</title>
  6. </head>
  7. <body>
  8. <div id="divMessages" class="messages">
  9. </div>
  10. <div class="input-zone">
  11. <label id="lblMessage" for="tbMessage">Message:</label>
  12. <input id="tbMessage" class="input-zone-input" type="text" />
  13. <button id="btnSend">Send</button>
  14. </div>
  15. </body>
  16. </html>

前面的 HTML 定义主页的样板标记。

  • 创建新的 src/css 目录 。目的是存储项目的 .css 文件 。

  • 创建包含以下标记的 src/css/main.css :

  1. *, *::before, *::after {
  2. box-sizing: border-box;
  3. }
  4. html, body {
  5. margin: 0;
  6. padding: 0;
  7. }
  8. .input-zone {
  9. align-items: center;
  10. display: flex;
  11. flex-direction: row;
  12. margin: 10px;
  13. }
  14. .input-zone-input {
  15. flex: 1;
  16. margin-right: 10px;
  17. }
  18. .message-author {
  19. font-weight: bold;
  20. }
  21. .messages {
  22. border: 1px solid #000;
  23. margin: 10px;
  24. max-height: 300px;
  25. min-height: 300px;
  26. overflow-y: auto;
  27. padding: 5px;
  28. }

前面的 main.css 文件设计应用样式 。

  • 创建包含以下 JSON 的 src/tsconfig.json :
  1. {
  2. "compilerOptions": {
  3. "target": "es5"
  4. }
  5. }

前面的代码配置 TypeScript 编译器,生成与 ECMAScript 5 兼容的 JavaScript。

  • 创建包含以下代码的 src/index.ts :
  1. import "./css/main.css";
  2. const divMessages: HTMLDivElement = document.querySelector("#divMessages");
  3. const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
  4. const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
  5. const username = new Date().getTime();
  6. tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
  7. if (e.keyCode === 13) {
  8. send();
  9. }
  10. });
  11. btnSend.addEventListener("click", send);
  12. function send() {
  13. }

前面的 TypeScript 检索对 DOM 元素的引用并附加两个事件处理程序:

  • keyup:用户在 tbMessage 文本框中键入时触发此事件。用户按 Enter 时调用 send 函数 。
  • click:用户单击“发送”按钮时触发此事件 。调用 send 函数。

配置 ASP.NET Core 应用Configure the ASP.NET Core app

  • Startup.Configure 方法中提供的代码显示 Hello World! 。将 app.Run 方法调用替换为对 UseDefaultFilesUseStaticFiles 的调用。
  1. app.UseDefaultFiles();
  2. app.UseStaticFiles();

前面的代码允许服务器定位并提供 index.html 文件,无论用户输入完整 URL 还是 Web 应用的根 URL 。

  • Startup.ConfigureServices 中,调用 AddSignalR。此操作会将 SignalR 服务添加到项目。
  1. services.AddSignalR();
  • 将 /hub 路由映射到 ChatHub 中心 。在 Startup.Configure 的末尾添加以下行:
  1. app.UseSignalR(options =>
  2. {
  3. options.MapHub<ChatHub>("/hub");
  4. });
  • 在项目根中创建名为 Hubs 的新目录 。目的是存储 SignalR 中心(在下一步中创建)。

  • 创建包含以下代码的中心 Hubs/ChatHub.cs :

  1. using Microsoft.AspNetCore.SignalR;
  2. using System.Threading.Tasks;
  3. namespace SignalRWebPack.Hubs
  4. {
  5. public class ChatHub : Hub
  6. {
  7. }
  8. }
  • 在 Startup.cs 文件顶部添加以下代码,解析 ChatHub 引用 :
  1. using SignalRWebPack.Hubs;

启用客户端和服务器通信Enable client and server communication

应用当前显示一个发送消息的简单窗体。尝试执行此操作时没有任何反应。服务器正在侦听特定的路由,但是不涉及发送消息。

  • 在项目根目录运行以下命令:
  1. npm install @aspnet/signalr

前面的命令安装 SignalR TypeScript 客户端,它允许客户端向服务器发送消息。

  • 将突出显示的代码添加到 src/index.ts 文件 :
  1. import "./css/main.css";
  2. import * as signalR from "@aspnet/signalr";
  3. const divMessages: HTMLDivElement = document.querySelector("#divMessages");
  4. const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
  5. const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
  6. const username = new Date().getTime();
  7. const connection = new signalR.HubConnectionBuilder()
  8. .withUrl("/hub")
  9. .build();
  10. connection.on("messageReceived", (username: string, message: string) => {
  11. let m = document.createElement("div");
  12. m.innerHTML =
  13. `<div class="message-author">${username}</div><div>${message}</div>`;
  14. divMessages.appendChild(m);
  15. divMessages.scrollTop = divMessages.scrollHeight;
  16. });
  17. connection.start().catch(err => document.write(err));
  18. tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
  19. if (e.keyCode === 13) {
  20. send();
  21. }
  22. });
  23. btnSend.addEventListener("click", send);
  24. function send() {
  25. }

前面的代码支持从服务器接收消息。HubConnectionBuilder 类创建新的生成器,用于配置服务器连接。withUrl 函数配置中心 URL。

SignalR 启用客户端和服务器之间的消息交换。每个消息都有特定的名称。例如,名为 messageReceived 的消息可以运行负责在消息区域显示新消息的逻辑。可以通过 on 函数完成对特定消息的侦听。可以侦听任意数量的消息名称。还可以将参数传递到消息,例如所接收消息的作者姓名和内容。客户端收到一条消息后,会创建一个新的 div 元素并在其 innerHTML 属性中显示作者姓名和消息内容。新消息将添加到显示消息的主 div 元素中。

  • 客户端可以接收消息后,将它配置为发送消息。将突出显示的代码添加到 src/index.ts 文件 :
  1. import "./css/main.css";
  2. import * as signalR from "@aspnet/signalr";
  3. const divMessages: HTMLDivElement = document.querySelector("#divMessages");
  4. const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
  5. const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
  6. const username = new Date().getTime();
  7. const connection = new signalR.HubConnectionBuilder()
  8. .withUrl("/hub")
  9. .build();
  10. connection.on("messageReceived", (username: string, message: string) => {
  11. let messageContainer = document.createElement("div");
  12. messageContainer.innerHTML =
  13. `<div class="message-author">${username}</div><div>${message}</div>`;
  14. divMessages.appendChild(messageContainer);
  15. divMessages.scrollTop = divMessages.scrollHeight;
  16. });
  17. connection.start().catch(err => document.write(err));
  18. tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
  19. if (e.keyCode === 13) {
  20. send();
  21. }
  22. });
  23. btnSend.addEventListener("click", send);
  24. function send() {
  25. connection.send("newMessage", username, tbMessage.value)
  26. .then(() => tbMessage.value = "");
  27. }

通过 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&gt; 上提供 wwwroot/index.html 文件。
  • 打开另一个浏览器实例(任意浏览器)。在地址栏中粘贴 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> 结束标记之前。
    • 通过在项目根中执行以下命令生成和运行应用:
dotnet run

Web 服务器启动应用并在 localhost 上提供。

  • 打开浏览器,转到 http://localhost:<port_number&gt;。提供 wwwroot/index.html 文件 。从地址栏复制 URL。

  • 打开另一个浏览器实例(任意浏览器)。在地址栏中粘贴 URL。

  • 选择一个浏览器,在“消息”文本框中键入任意内容,然后单击“发送”按钮 。两个页面上立即显示唯一的用户名和消息。

两个浏览器窗口都显示的消息

其他资源Additional resources