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

2019-04-17 08:57 更新

開發(fā)人員可以通過 Webpack 捆綁和生成 Web 應(yīng)用的客戶端資源。 本教程介紹在 ASP.NET Core SignalR Web 應(yīng)用中使用 Webpack,該應(yīng)用的客戶端是使用 TypeScript 編寫的。

在本教程中,你將了解:

  • 為入門 ASP.NET Core SignalR 應(yīng)用搭建基架
  • 配置 SignalR TypeScript 客戶端
  • 使用 Webpack 配置生成管道
  • 配置 SignalR 服務(wù)器
  • 啟用客戶端和服務(wù)器之間的通信

查看或下載示例代碼如何下載

系統(tǒng)必備

創(chuàng)建 ASP.NET Core Web 應(yīng)用

配置 Visual Studio,在 PATH 環(huán)境變量中查找 npm。 默認(rèn)情況下,Visual Studio 使用在安裝目錄中找到的 npm 版本。 在 Visual Studio 中按照以下說明執(zhí)行操作:

  1. 導(dǎo)航到“工具” > “選項(xiàng)” > “項(xiàng)目和解決方案” > “Web 包管理” > “外部 Web 工具”。

  2. 在列表中選擇 $(PATH) 項(xiàng)。 單擊向上鍵將項(xiàng)移動列表第二個位置。

    Visual Studio 配置

已完成 Visual Studio 配置。 可以開始創(chuàng)建項(xiàng)目了。

  1. 使用“文件” > “新建” > “項(xiàng)目”菜單選項(xiàng),然后選擇“ASP.NET Core Web 應(yīng)用程序”模板。
  2. 將項(xiàng)目命名為 SignalRWebPack 并選擇“確定”。
  3. 從目標(biāo)框架下拉列表選擇 .NET Core 并從框架選擇器下拉列表選擇 ASP.NET Core 2.2。 選擇“空白”模板并選擇“確定”。

配置 Webpack 和 TypeScript

以下步驟配置 TypeScript 到 JavaScript 的轉(zhuǎn)換和客戶端資源的捆綁。

  1. 在項(xiàng)目根目錄中執(zhí)行以下命令,創(chuàng)建 package.json 文件:console復(fù)制npm init -y
  2. 將突出顯示的屬性添加到 package.json 文件:JSON復(fù)制{ "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 屬性設(shè)置為 true,防止下一步出現(xiàn)包安裝警告。
  3. 安裝所需的 npm 包。 從項(xiàng)目根執(zhí)行以下命令:console復(fù)制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 需要注意的一些命令細(xì)節(jié):每個包名稱中 @ 符號后是版本號。 npm 安裝這些特定的包版本。-E 選項(xiàng)禁用 npm 將語義化版本控制范圍運(yùn)算符寫到 package.json 的默認(rèn)行為。 例如,使用 "webpack": "4.29.3" 而不是 "webpack": "^4.29.3"。 此選項(xiàng)防止意外升級到新的包版本。有關(guān)詳細(xì)信息,請參閱官方 npm-install 文檔。
  4. 將 package.json 文件的 scripts 屬性替換為以下代碼片段:JSON復(fù)制"scripts": { "build": "webpack --mode=development --watch", "release": "webpack --mode=production", "publish": "npm run release && dotnet publish -c Release" }, 腳本的一些解釋:build:在開發(fā)模式下捆綁客戶端資源并觀察文件更改。 文件觀察程序使捆綁在每次項(xiàng)目文件發(fā)生更改時重新生成。 mode 選項(xiàng)可禁用生產(chǎn)優(yōu)化,例如搖樹優(yōu)化和縮小優(yōu)化。僅在開發(fā)中使用 build。release:在生產(chǎn)模式下捆綁客戶端資源。publish:運(yùn)行 release 腳本,在生產(chǎn)模式下捆綁客戶端資源。 它調(diào)用 .NET Core CLI 的 publish 命令發(fā)布應(yīng)用。
  5. 在項(xiàng)目根中創(chuàng)建名為 webpack.config.js 的文件,包含以下內(nèi)容:JavaScript復(fù)制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 編譯。 需要注意的一些配置細(xì)節(jié):output 屬性替代 dist 的默認(rèn)值。 捆綁反而在 wwwroot 目錄中發(fā)出。resolve.extensions 數(shù)組包含 .js,以便導(dǎo)入 SignalR 客戶端 JavaScript。
  6. 在項(xiàng)目根中創(chuàng)建新的 src 目錄。 目的是存儲項(xiàng)目的客戶端資產(chǎn)。
  7. 創(chuàng)建包含以下內(nèi)容的 src/index.html。HTML復(fù)制<!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 定義主頁的樣板標(biāo)記。
  8. 創(chuàng)建新的 src/css 目錄。 目的是存儲項(xiàng)目的 .css 文件。
  9. 創(chuàng)建包含以下內(nèi)容的 src/css/main.css:css復(fù)制*, *::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 文件設(shè)計(jì)應(yīng)用樣式。
  10. 創(chuàng)建包含以下內(nèi)容的 src/tsconfig.json:JSON復(fù)制{ "compilerOptions": { "target": "es5" } } 前面的代碼配置 TypeScript 編譯器,生成與 ECMAScript 5 兼容的 JavaScript。
  11. 創(chuàng)建包含以下內(nèi)容的 src/index.ts:TypeScript復(fù)制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:用戶在文本框中鍵入標(biāo)識為 tbMessage 的內(nèi)容時觸發(fā)此事件。 用戶按 Enter 時調(diào)用 send 函數(shù)。click:用戶單擊“發(fā)送”按鈕時觸發(fā)此事件。 調(diào)用 send 函數(shù)。

配置 ASP.NET Core 應(yīng)用

  1. Startup.Configure 方法中提供的代碼顯示 Hello World!。 將 app.Run 方法調(diào)用替換為對 UseDefaultFiles 和 UseStaticFiles 的調(diào)用。C#復(fù)制app.UseDefaultFiles(); app.UseStaticFiles(); 前面的代碼允許服務(wù)器定位并提供 index.html 文件,無論用戶輸入完整 URL 還是 Web 應(yīng)用的根 URL。
  2. 在 Startup.ConfigureServices 方法中調(diào)用 AddSignalR。 此操作會將 SignalR 服務(wù)添加到項(xiàng)目。C#復(fù)制services.AddSignalR();
  3. 將 /hub 路由映射到 ChatHub 中心。 在 Startup.Configure 方法的末尾添加以下行:C#復(fù)制app.UseSignalR(options => { options.MapHub<ChatHub>("/hub"); });
  4. 在項(xiàng)目根中創(chuàng)建名為 Hubs 的新目錄。 目的是存儲 SignalR 中心(在下一步中創(chuàng)建)。
  5. 創(chuàng)建包含以下代碼的中心 Hubs/ChatHub.cs:C#復(fù)制using Microsoft.AspNetCore.SignalR; using System.Threading.Tasks; namespace SignalRWebPack.Hubs { public class ChatHub : Hub { } }
  6. 在 Startup.cs 文件頂部添加以下代碼,解析 ChatHub 引用:C#復(fù)制using SignalRWebPack.Hubs;

啟用客戶端和服務(wù)器通信

應(yīng)用當(dāng)前顯示一個發(fā)送消息的簡單窗體。 嘗試執(zhí)行此操作時沒有任何反應(yīng)。 服務(wù)器正在偵聽特定的路由,但是不涉及發(fā)送消息。

  1. 在項(xiàng)目根執(zhí)行以下命令:console復(fù)制npm install @aspnet/signalr 前面的命令安裝 SignalR TypeScript 客戶端,它允許客戶端向服務(wù)器發(fā)送消息。
  2. 將突出顯示的代碼添加到 src/index.ts 文件:TypeScript復(fù)制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.start().catch(err => document.write(err)); 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; }); tbMessage.addEventListener("keyup", (e: KeyboardEvent) => { if (e.keyCode === 13) { send(); } }); btnSend.addEventListener("click", send); function send() { } 前面的代碼支持從服務(wù)器接收消息。 HubConnectionBuilder 類創(chuàng)建新的生成器,用于配置服務(wù)器連接。 withUrl 函數(shù)配置中心 URL。SignalR 啟用客戶端和服務(wù)器之間的消息交換。 每個消息都有特定的名稱。 例如,名為 messageReceived 的消息可以執(zhí)行負(fù)責(zé)在消息區(qū)域顯示新消息的邏輯。 可以通過 on 函數(shù)完成對特定消息的偵聽。 可以偵聽任意數(shù)量的消息名稱。 還可以將參數(shù)傳遞到消息,例如所接收消息的作者姓名和內(nèi)容。 客戶端收到一條消息后,會創(chuàng)建一個新的 div 元素并在其 innerHTML屬性中顯示作者姓名和消息內(nèi)容。 它添加到顯示消息的主要 div 元素。
  3. 客戶端可以接收消息后,將它配置為發(fā)送消息。 將突出顯示的代碼添加到 src/index.ts 文件:TypeScript復(fù)制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.start().catch(err => document.write(err)); 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; }); 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 連接發(fā)送消息需要調(diào)用 send 方法。 該方法的第一個參數(shù)是消息名稱。 消息數(shù)據(jù)包含其他參數(shù)。 在此示例中,一條標(biāo)識為 newMessage 的消息已發(fā)送到服務(wù)器。 該消息包含用戶名和文本框中的用戶輸入。 如果發(fā)送成功,會清空文本框。
  4. 將突出顯示的方法添加到 ChatHub 類:C#復(fù)制using Microsoft.AspNetCore.SignalR; using System.Threading.Tasks; namespace SignalRWebPack.Hubs { public class ChatHub : Hub { public async Task NewMessage(string username, string message) { await Clients.All.SendAsync("messageReceived", username, message); } } } 服務(wù)器收到消息后,前面的代碼會將這些消息播發(fā)到所有連接的用戶。 沒有必要使用泛型 on方法接收所有消息。 使用以消息名稱命名的方法就可以了。在此示例中,TypeScript 客戶端發(fā)送一條標(biāo)識為 newMessage 的消息。 C# NewMessage 方法需要客戶端發(fā)送的數(shù)據(jù)。 在 Clients.All 調(diào)用 SendAsync 方法。 接收的消息會發(fā)送到所有連接到中心的客戶端。

測試應(yīng)用

確認(rèn)應(yīng)用遵循以下步驟。

  1. 在 release 模式下運(yùn)行 Webpack。 使用“包管理器控制臺”窗口,在項(xiàng)目根中運(yùn)行以下命令。 如果不在項(xiàng)目根中,請?jiān)谳斎朐撁钪拜斎?nbsp;cd SignalRWebPack

    console
    npm run release
    

    此命令在運(yùn)行應(yīng)用時生成要提供的客戶端資產(chǎn)。 資產(chǎn)位于 wwwroot 文件夾。

    Webpack 完成了以下任務(wù):

    • 清除了 wwwroot 目錄的內(nèi)容。
    • 將 TypeScript 轉(zhuǎn)換為 JavaScript,該過程稱為“轉(zhuǎn)譯”.
    • 破壞生成的 JavaScript 以降低文件大小,該過程稱為“縮小”。
    • 將已處理的 JavaScript、CSS 和 HTML 文件從 src 復(fù)制到 wwwroot 目錄。
    • 將以下元素注入 wwwroot/index.html 文件:
      • 一個引用 wwwroot/main.<hash>.css 文件的 <link> 標(biāo)記。 此標(biāo)記緊挨著 </head> 結(jié)束標(biāo)記之前。
      • 一個引用縮小后的 wwwroot/main.<hash>.js 文件的 <script> 標(biāo)記。 此標(biāo)記緊挨著 </body> 結(jié)束標(biāo)記之前。
  2. 選擇“調(diào)試” > “開始執(zhí)行(不調(diào)試)”,在不附加調(diào)試器的情況下在瀏覽器中啟動應(yīng)用。 在 http://localhost:<port_number> 上提供 wwwroot/index.html 文件。

  3. 打開另一個瀏覽器實(shí)例(任意瀏覽器)。 在地址欄中粘貼 URL。

  4. 選擇一個瀏覽器,在“消息”文本框中鍵入任意內(nèi)容,然后單擊“發(fā)送”按鈕。 兩個頁面上立即顯示唯一的用戶名和消息。

兩個瀏覽器窗口都顯示的消息



以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號