教學課程:開始使用 TypeScript 和 Webpack ASP.NET Core SignalR
作者:Sébastien Sougnez 和 Scott Addie
本教學課程示範如何在 ASP.NET Core SignalR Web 應用程式中使用Webpack來組合及建置以 TypeScript撰寫的用戶端。 Webpack 可讓開發人員組合並建置 Web 應用程式的用戶端資源。
在本教學課程中,您會了解如何:
- 建立 ASP.NET Core SignalR 應用程式
- 設定 SignalR 伺服器
- 使用 Webpack 設定組建管線
- 設定 SignalR TypeScript 用戶端
- 啟用用戶端與伺服器之間的通訊
檢視或下載範例程式碼 \(英文\) (如何下載)
必要條件
- Visual Studio 2022 和 ASP.NET 與 Web 開發工作負載。
建立 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
會停用將 語意版本設定 範圍運算子package.json
寫入 至 的預設行為。 例如,會使用"webpack": "5.70.0"
,而不是"webpack": "^5.70.0"
。 此選項可防止意外升級至較新的套件版本。如需詳細資訊,請參閱 npm-install 檔。
scripts
以下列程式碼取代 檔案的package.json
屬性:"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
函式完成。 可以接聽任意數目的訊息名稱。 也可將參數傳遞給訊息,例如作者的名稱和已接收訊息的內容。 用戶端收到訊息之後,就會在其innerHTML
屬性中使用作者的名稱和訊息內容建立新的div
元素。 它會新增至顯示訊息的主要div
元素。透過 Websocket 連線傳送訊息需要呼叫
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
檔案中:<link>
標籤,參考wwwroot/main.<hash>.css
檔案。 此標記緊接在結尾</head>
標記之前。<script>
標籤,參考縮減wwwroot/main.<hash>.js
的檔案。 此標記緊接在結尾</body>
標記之前。
- 清除目錄的內容
>選取 [偵錯啟動但不偵錯] 以在瀏覽器中啟動應用程式,而不附加偵錯工具。 檔案
wwwroot/index.html
會在 提供https://localhost:<port>
。如果您收到編譯錯誤,請嘗試關閉並重新開啟解決方案。
開啟另一個瀏覽器執行個體 (任何瀏覽器),並在網址列中貼上 URL。
選擇任一瀏覽器,在 [ 訊息 ] 文字方塊中輸入某個專案,然後選取 [ 傳送 ] 按鈕。 唯一使用者名稱和訊息會立即顯示在這兩個頁面上。
其他資源
本教學課程示範如何在 ASP.NET Core SignalR Web 應用程式中使用Webpack來組合及建置以 TypeScript撰寫的用戶端。 Webpack 可讓開發人員組合並建置 Web 應用程式的用戶端資源。
在本教學課程中,您會了解如何:
- ASP.NET Core應用程式建構入門 SignalR
- 設定 SignalR TypeScript 用戶端
- 使用 Webpack 設定組建管線
- 設定 SignalR 伺服器
- 啟用用戶端與伺服器之間的通訊
檢視或下載範例程式碼 \(英文\) (如何下載)
必要條件
- 具有ASP.NET 和 Web 開發工作負載的Visual Studio 2019
- .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 應用程式] 範本。 選取 [下一步]。
- 將專案命名為 * SignalR WebPac'',然後選取 [ 建立]。
- 從目標 Framework 下拉式清單中選取.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 檔。
- 版本號碼接在每一個套件名稱的
以
scripts
下列程式碼取代 檔案的package.json
屬性:"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
檔案樣式應用程式。使用下列 JS ON 建立
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
,新增 對 和 UseStaticFiles(IApplicationBuilder) 的 UseDefaultFiles(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();
在專案根SignalR WebPack/中建立名為Hub 的新目錄,以儲存中 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
函式完成。 可以接聽任意數目的訊息名稱。 也可將參數傳遞給訊息,例如作者的名稱和已接收訊息的內容。 用戶端收到訊息之後,就會在其innerHTML
屬性中使用作者的名稱和訊息內容建立新的div
元素。 它會新增至顯示訊息的主要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 = ""); }
透過 Websocket 連線傳送訊息需要呼叫
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
檔案中:<link>
標籤,參考wwwroot/main.<hash>.css
檔案。 此標記緊接在結尾</head>
標記之前。<script>
標籤,參考縮減wwwroot/main.<hash>.js
的檔案。 此標記緊接在結尾</body>
標記之前。
- 清除目錄的內容
>選取 [偵錯啟動但不偵錯] 以在瀏覽器中啟動應用程式,而不附加偵錯工具。 檔案
wwwroot/index.html
會在 提供http://localhost:<port_number>
。如果您收到編譯錯誤,請嘗試關閉並重新開啟解決方案。
開啟另一個瀏覽器執行個體 (任何瀏覽器)。 在網址列中貼上 URL。
選擇任一瀏覽器,在 [ 訊息 ] 文字方塊中輸入某個專案,然後選取 [ 傳送 ] 按鈕。 唯一使用者名稱和訊息會立即顯示在這兩個頁面上。
其他資源
本教學課程示範如何在 ASP.NET Core SignalR Web 應用程式中使用Webpack來組合及建置以 TypeScript撰寫的用戶端。 Webpack 可讓開發人員組合並建置 Web 應用程式的用戶端資源。
在本教學課程中,您會了解如何:
- ASP.NET Core應用程式建構入門 SignalR
- 設定 SignalR TypeScript 用戶端
- 使用 Webpack 設定組建管線
- 設定 SignalR 伺服器
- 啟用用戶端與伺服器之間的通訊
檢視或下載範例程式碼 \(英文\) (如何下載)
必要條件
- 具有ASP.NET 和 Web 開發工作負載的Visual Studio 2019
- .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 應用程式] 範本。
- 將專案命名為 * SignalR WebPack',然後選取 [ 建立]。
- 從目標 Framework 下拉式清單中選取 [.NET Core],然後從 Framework 選取器下拉式清單中選取 [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 檔。
- 版本號碼接在每一個套件名稱的
以
scripts
下列程式碼取代 檔案的package.json
屬性:"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
檔案樣式應用程式。使用下列 JS ON 建立
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
方法呼叫取代為 對 和 UseStaticFiles(IApplicationBuilder) 的 UseDefaultFiles(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
函式完成。 您可以接聽任意數目的訊息名稱。 也可將參數傳遞給訊息,例如作者的名稱和已接收訊息的內容。 用戶端收到訊息之後,就會在其innerHTML
屬性中使用作者的名稱和訊息內容建立新的div
元素。 新訊息會新增至顯示訊息的主要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 = ""); }
透過 Websocket 連線傳送訊息需要呼叫
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
檔案中:<link>
標籤,參考wwwroot/main.<hash>.css
檔案。 此標記緊接在結尾</head>
標記之前。<script>
標籤,參考縮減wwwroot/main.<hash>.js
的檔案。 此標記緊接在結尾</body>
標記之前。
- 清除目錄的內容
>選取 [偵錯啟動但不偵錯] 以在瀏覽器中啟動應用程式,而不附加偵錯工具。 檔案
wwwroot/index.html
會在 提供http://localhost:<port_number>
。開啟另一個瀏覽器執行個體 (任何瀏覽器)。 在網址列中貼上 URL。
選擇任一瀏覽器,在 [ 訊息 ] 文字方塊中輸入某個專案,然後選取 [ 傳送 ] 按鈕。 唯一使用者名稱和訊息會立即顯示在這兩個頁面上。