開發(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í)行操作:
導(dǎo)航到“工具” > “選項(xiàng)” > “項(xiàng)目和解決方案” > “Web 包管理” > “外部 Web 工具”。
在列表中選擇 $(PATH) 項(xiàng)。 單擊向上鍵將項(xiàng)移動列表第二個位置。
已完成 Visual Studio 配置。 可以開始創(chuàng)建項(xiàng)目了。
- 使用“文件” > “新建” > “項(xiàng)目”菜單選項(xiàng),然后選擇“ASP.NET Core Web 應(yīng)用程序”模板。
- 將項(xiàng)目命名為 SignalRWebPack 并選擇“確定”。
- 從目標(biāo)框架下拉列表選擇 .NET Core 并從框架選擇器下拉列表選擇 ASP.NET Core 2.2。 選擇“空白”模板并選擇“確定”。
配置 Webpack 和 TypeScript
以下步驟配置 TypeScript 到 JavaScript 的轉(zhuǎn)換和客戶端資源的捆綁。
- 在項(xiàng)目根目錄中執(zhí)行以下命令,創(chuàng)建 package.json 文件:console復(fù)制npm init -y
- 將突出顯示的屬性添加到 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)包安裝警告。
- 安裝所需的 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 文檔。
- 將 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)用。
- 在項(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。
- 在項(xiàng)目根中創(chuàng)建新的 src 目錄。 目的是存儲項(xiàng)目的客戶端資產(chǎn)。
- 創(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)記。
- 創(chuàng)建新的 src/css 目錄。 目的是存儲項(xiàng)目的 .css 文件。
- 創(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)用樣式。
- 創(chuàng)建包含以下內(nèi)容的 src/tsconfig.json:JSON復(fù)制{
"compilerOptions": {
"target": "es5"
}
}
前面的代碼配置 TypeScript 編譯器,生成與 ECMAScript 5 兼容的 JavaScript。
- 創(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)用
- 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。
- 在 Startup.ConfigureServices 方法中調(diào)用 AddSignalR。 此操作會將 SignalR 服務(wù)添加到項(xiàng)目。C#復(fù)制services.AddSignalR();
- 將 /hub 路由映射到 ChatHub 中心。 在 Startup.Configure 方法的末尾添加以下行:C#復(fù)制app.UseSignalR(options =>
{
options.MapHub<ChatHub>("/hub");
});
- 在項(xiàng)目根中創(chuàng)建名為 Hubs 的新目錄。 目的是存儲 SignalR 中心(在下一步中創(chuàng)建)。
- 創(chuàng)建包含以下代碼的中心 Hubs/ChatHub.cs:C#復(fù)制using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SignalRWebPack.Hubs
{
public class ChatHub : Hub
{
}
}
- 在 Startup.cs 文件頂部添加以下代碼,解析 ChatHub 引用:C#復(fù)制using SignalRWebPack.Hubs;
啟用客戶端和服務(wù)器通信
應(yīng)用當(dāng)前顯示一個發(fā)送消息的簡單窗體。 嘗試執(zhí)行此操作時沒有任何反應(yīng)。 服務(wù)器正在偵聽特定的路由,但是不涉及發(fā)送消息。
- 在項(xiàng)目根執(zhí)行以下命令:console復(fù)制npm install @aspnet/signalr
前面的命令安裝 SignalR TypeScript 客戶端,它允許客戶端向服務(wù)器發(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 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 元素。
- 客戶端可以接收消息后,將它配置為發(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ā)送成功,會清空文本框。
- 將突出顯示的方法添加到 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)用遵循以下步驟。
在 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)記之前。
選擇“調(diào)試” > “開始執(zhí)行(不調(diào)試)”,在不附加調(diào)試器的情況下在瀏覽器中啟動應(yīng)用。 在 http://localhost:<port_number>
上提供 wwwroot/index.html 文件。
打開另一個瀏覽器實(shí)例(任意瀏覽器)。 在地址欄中粘貼 URL。
選擇一個瀏覽器,在“消息”文本框中鍵入任意內(nèi)容,然后單擊“發(fā)送”按鈕。 兩個頁面上立即顯示唯一的用戶名和消息。
更多建議: