教學課程:使用 TypeScript 和 Webpack 處理 ASP.NET Core SignalR 的入門

注意

這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

如需目前版本,請參閱本文的 .NET 8 版本

作者:Sébastien Sougnez

本教學課程示範如何在 ASP.NET Core SignalR Web 應用程式中使用 Webpack,以統合及建置以 TypeScript 撰寫的用戶端。 Webpack 可讓開發人員組合並建置 Web 應用程式的用戶端資源。

在本教學課程中,您會了解如何:

  • 建立 ASP.NET Core SignalR 應用程式
  • 設定 SignalR 伺服器
  • 使用 Webpack 設定組建管線
  • 設定 SignalR TypeScript 用戶端
  • 啟用用戶端與伺服器之間的通訊

檢視或下載範例程式碼 \(英文\) (如何下載)

必要條件

建立 ASP.NET Core Web 應用程式

根據預設,Visual Studio 會使用在其安裝目錄中找到的 npm 版本。 若要設定 Visual Studio 以在 PATH 環境變數中尋找 npm:

啟動 Visual Studio。 在 [開始] 視窗中,選取 [不使用程式碼繼續]

  1. 巡覽至 [工具]>[選項]>[專案和方案]>[網頁套件管理]>[外部 Web 工具]

  2. 從清單中選取 $(PATH) 項目。 選取向上箭號,將此項目移至清單中的第二個位置,然後選取 [確定]

    Visual Studio 組態.

若要建立新的 ASP.NET Core Web 應用程式:

  1. 使用 [檔案]>[新增]>[專案] 功能表選項,然後選擇 [空的 ASP.NET Core] 範本。 選取 [下一步] 。
  2. 將專案命名為 SignalRWebpack,然後選取 [建立]
  3. 從 [架構] 下拉式清單中選取 [.NET 8.0 (長期支援)]。 選取 建立

Microsoft.TypeScript.MSBuild NuGet 套件新增至專案:

  1. 方案總管中,以滑鼠右鍵按一下專案節點,然後選取 [管理 NuGet 套件]。 在 [瀏覽] 索引標籤中搜尋 Microsoft.TypeScript.MSBuild,然後選取右側的 [安裝] 以安裝套件。

Visual Studio 會在方案總管的 [相依性] 節點底下新增 NuGet 套件,而在專案中啟用 TypeScript 編譯。

設定伺服器

在本節中,您會設定 ASP.NET Core Web 應用程式以傳送和接收 SignalR 訊息。

  1. Program.cs 中,呼叫 AddSignalR

    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddSignalR();
    
  2. 同樣地,在 Program.cs 中呼叫 UseDefaultFilesUseStaticFiles

    var app = builder.Build();
    
    app.UseDefaultFiles();
    app.UseStaticFiles();
    

    上述程式碼可讓伺服器找出並提供 index.html 檔案。 無論使用者輸入檔案的完整 URL 還是 Web 應用程式的根 URL,檔案都會提供。

  3. 在專案根目錄 SignalRWebpack/ 中,為 SignalR 中樞類別建立名為 Hubs 的新目錄。

  4. 使用下列程式碼建立新檔案 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
    • 接收的訊息就會傳送到連線至中樞的所有用戶端。
  5. Program.cs 頂端新增下列 using 陳述式,以解析 ChatHub 參考:

    using SignalRWebpack.Hubs;
    
  6. Program.cs 中,將 /hub 路由對應至 ChatHub 中樞。 將顯示 Hello World! 的程式碼取代為下列程式碼:

    app.MapHub<ChatHub>("/hub");
    

設定用戶端

在本節中,您會建立 Node.js 專案,以使用 Webpack 將 TypeScript 轉換為 JavaScript 並統合用戶端資源,包括 HTML 和 CSS。

  1. 在專案根目錄中執行下列命令,以建立 package.json 檔案:

    npm init -y
    
  2. 將醒目提示的屬性新增至 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 可避免在下一個步驟中出現套件安裝警告。

  3. 安裝必要的 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 文件。

  4. 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 命令以發佈應用程式。
  5. 使用下列程式碼,在專案根目錄中建立名為 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。
  6. 在專案根目錄 SignalRWebpack/ 中,為用戶端程式碼建立名為 src 的新目錄。

  7. 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 的訊息會傳送到伺服器。 訊息是由使用者名稱和文字方塊中的使用者輸入所組成。 如果傳送可正常運作,則會清除文字方塊的值。

  8. 在專案根目錄下執行下列命令:

    npm i @microsoft/signalr @types/node
    

    上述命令會安裝:

    • SignalR TypeScript 用戶端,可讓用戶端將訊息傳送至伺服器。
    • Node.js 的 TypeScript 類型定義,可啟用 Node.js 類型的編譯時間檢查。

測試應用程式

使用下列步驟確認應用程式可正常運作:

  1. release 模式執行 Webpack。 使用 [套件管理員主控台] 視窗,在專案根目錄中執行下列命令。

    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 檔案。 此標籤緊接在 </title> 結尾標籤後面。
  2. 選取 [偵錯]>[啟動但不偵錯],啟動瀏覽器中的應用程式,但不附加偵錯工具。 wwwroot/index.html 檔案會提供於 https://localhost:<port>

    如果發生編譯錯誤,請嘗試將解決方案關閉再重新開啟。

  3. 開啟另一個瀏覽器執行個體 (任何瀏覽器),並在網址列中貼上 URL。

  4. 選擇其中一個瀏覽器,在 [訊息] 文字方塊中鍵入某些內容,然後選取 [傳送] 按鈕。 唯一使用者名稱和訊息會立即顯示在兩個頁面上。

兩個瀏覽器視窗都顯示的訊息

下一步

其他資源

本教學課程示範如何在 ASP.NET Core SignalR Web 應用程式中使用 Webpack,以統合及建置以 TypeScript 撰寫的用戶端。 Webpack 可讓開發人員組合並建置 Web 應用程式的用戶端資源。

在本教學課程中,您會了解如何:

  • 建立 ASP.NET Core SignalR 應用程式
  • 設定 SignalR 伺服器
  • 使用 Webpack 設定組建管線
  • 設定 SignalR TypeScript 用戶端
  • 啟用用戶端與伺服器之間的通訊

檢視或下載範例程式碼 \(英文\) (如何下載)

必要條件

建立 ASP.NET Core Web 應用程式

根據預設,Visual Studio 會使用在其安裝目錄中找到的 npm 版本。 若要設定 Visual Studio 以在 PATH 環境變數中尋找 npm:

啟動 Visual Studio。 在 [開始] 視窗中,選取 [不使用程式碼繼續]

  1. 巡覽至 [工具]>[選項]>[專案和方案]>[網頁套件管理]>[外部 Web 工具]

  2. 從清單中選取 $(PATH) 項目。 選取向上箭號,將此項目移至清單中的第二個位置,然後選取 [確定]

    Visual Studio 組態.

若要建立新的 ASP.NET Core Web 應用程式:

  1. 使用 [檔案]>[新增]>[專案] 功能表選項,然後選擇 [空的 ASP.NET Core] 範本。 選取 [下一步] 。
  2. 將專案命名為 SignalRWebpack,然後選取 [建立]
  3. 從 [架構] 下拉式清單中選取 .NET 7.0 (Standard Term Support)。 選取建立

Microsoft.TypeScript.MSBuild NuGet 套件新增至專案:

  1. 方案總管中,以滑鼠右鍵按一下專案節點,然後選取 [管理 NuGet 套件]。 在 [瀏覽] 索引標籤中搜尋 Microsoft.TypeScript.MSBuild,然後選取右側的 [安裝] 以安裝套件。

Visual Studio 會在方案總管的 [相依性] 節點底下新增 NuGet 套件,而在專案中啟用 TypeScript 編譯。

設定伺服器

在本節中,您會設定 ASP.NET Core Web 應用程式以傳送和接收 SignalR 訊息。

  1. Program.cs 中,呼叫 AddSignalR

    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddSignalR();
    
  2. 同樣地,在 Program.cs 中呼叫 UseDefaultFilesUseStaticFiles

    var app = builder.Build();
    
    app.UseDefaultFiles();
    app.UseStaticFiles();
    

    上述程式碼可讓伺服器找出並提供 index.html 檔案。 無論使用者輸入檔案的完整 URL 還是 Web 應用程式的根 URL,檔案都會提供。

  3. 在專案根目錄 SignalRWebpack/ 中,為 SignalR 中樞類別建立名為 Hubs 的新目錄。

  4. 使用下列程式碼建立新檔案 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
    • 接收的訊息就會傳送到連線至中樞的所有用戶端。
  5. Program.cs 頂端新增下列 using 陳述式,以解析 ChatHub 參考:

    using SignalRWebpack.Hubs;
    
  6. Program.cs 中,將 /hub 路由對應至 ChatHub 中樞。 將顯示 Hello World! 的程式碼取代為下列程式碼:

    app.MapHub<ChatHub>("/hub");
    

設定用戶端

在本節中,您會建立 Node.js 專案,以使用 Webpack 將 TypeScript 轉換為 JavaScript 並統合用戶端資源,包括 HTML 和 CSS。

  1. 在專案根目錄中執行下列命令,以建立 package.json 檔案:

    npm init -y
    
  2. 將醒目提示的屬性新增至 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 可避免在下一個步驟中出現套件安裝警告。

  3. 安裝必要的 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 文件。

  4. 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 命令以發佈應用程式。
  5. 使用下列程式碼,在專案根目錄中建立名為 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。
  6. 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 的訊息會傳送到伺服器。 訊息是由使用者名稱和文字方塊中的使用者輸入所組成。 如果傳送可正常運作,則會清除文字方塊的值。

  7. 在專案根目錄下執行下列命令:

    npm i @microsoft/signalr @types/node
    

    上述命令會安裝:

    • SignalR TypeScript 用戶端,可讓用戶端將訊息傳送至伺服器。
    • Node.js 的 TypeScript 類型定義,可啟用 Node.js 類型的編譯時間檢查。

測試應用程式

使用下列步驟確認應用程式可正常運作:

  1. release 模式執行 Webpack。 使用 [套件管理員主控台] 視窗,在專案根目錄中執行下列命令。

    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 檔案。 此標籤緊接在 </title> 結尾標籤後面。
  2. 選取 [偵錯]>[啟動但不偵錯],啟動瀏覽器中的應用程式,但不附加偵錯工具。 wwwroot/index.html 檔案會提供於 https://localhost:<port>

    如果發生編譯錯誤,請嘗試將解決方案關閉再重新開啟。

  3. 開啟另一個瀏覽器執行個體 (任何瀏覽器),並在網址列中貼上 URL。

  4. 選擇其中一個瀏覽器,在 [訊息] 文字方塊中鍵入某些內容,然後選取 [傳送] 按鈕。 唯一使用者名稱和訊息會立即顯示在兩個頁面上。

兩個瀏覽器視窗都顯示的訊息

下一步

其他資源

本教學課程示範如何在 ASP.NET Core SignalR Web 應用程式中使用 Webpack,以統合及建置以 TypeScript 撰寫的用戶端。 Webpack 可讓開發人員組合並建置 Web 應用程式的用戶端資源。

在本教學課程中,您會了解如何:

  • 建立 ASP.NET Core SignalR 應用程式
  • 設定 SignalR 伺服器
  • 使用 Webpack 設定組建管線
  • 設定 SignalR TypeScript 用戶端
  • 啟用用戶端與伺服器之間的通訊

檢視或下載範例程式碼 \(英文\) (如何下載)

必要條件

建立 ASP.NET Core Web 應用程式

根據預設,Visual Studio 會使用在其安裝目錄中找到的 npm 版本。 若要設定 Visual Studio 以在 PATH 環境變數中尋找 npm:

  1. 啟動 Visual Studio。 在 [開始] 視窗中,選取 [不使用程式碼繼續]

  2. 巡覽至 [工具]>[選項]>[專案和方案]>[網頁套件管理]>[外部 Web 工具]

  3. 從清單中選取 $(PATH) 項目。 選取向上箭號,將此項目移至清單中的第二個位置,然後選取 [確定]

    Visual Studio 組態.

若要建立新的 ASP.NET Core Web 應用程式:

  1. 使用 [檔案]>[新增]>[專案] 功能表選項,然後選擇 [空的 ASP.NET Core] 範本。 選取 [下一步] 。
  2. 將專案命名為 SignalRWebpack,然後選取 [建立]
  3. 從 [架構] 下拉式清單中選取 .NET 6.0 (Long Term Support)。 選取建立

Microsoft.TypeScript.MSBuild NuGet 套件新增至專案:

  1. 方案總管中,以滑鼠右鍵按一下專案節點,然後選取 [管理 NuGet 套件]。 在 [瀏覽] 索引標籤中搜尋 Microsoft.TypeScript.MSBuild,然後選取右側的 [安裝] 以安裝套件。

Visual Studio 會在方案總管的 [相依性] 節點底下新增 NuGet 套件,而在專案中啟用 TypeScript 編譯。

設定伺服器

在本節中,您會設定 ASP.NET Core Web 應用程式以傳送和接收 SignalR 訊息。

  1. Program.cs 中,呼叫 AddSignalR

    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddSignalR();
    
  2. 同樣地,在 Program.cs 中呼叫 UseDefaultFilesUseStaticFiles

    var app = builder.Build();
    
    app.UseDefaultFiles();
    app.UseStaticFiles();
    

    上述程式碼可讓伺服器找出並提供 index.html 檔案。 無論使用者輸入檔案的完整 URL 還是 Web 應用程式的根 URL,檔案都會提供。

  3. 在專案根目錄 SignalRWebpack/ 中,為 SignalR 中樞類別建立名為 Hubs 的新目錄。

  4. 使用下列程式碼建立新檔案 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。 接收的訊息就會傳送到連線至中樞的所有用戶端。

  5. Program.cs 頂端新增下列 using 陳述式,以解析 ChatHub 參考:

    using SignalRWebpack.Hubs;
    
  6. Program.cs 中,將 /hub 路由對應至 ChatHub 中樞。 將顯示 Hello World! 的程式碼取代為下列程式碼:

    app.MapHub<ChatHub>("/hub");
    

設定用戶端

在本節中,您會建立 Node.js 專案,以使用 Webpack 將 TypeScript 轉換為 JavaScript 並統合用戶端資源,包括 HTML 和 CSS。

  1. 在專案根目錄中執行下列命令,以建立 package.json 檔案:

    npm init -y
    
  2. 將醒目提示的屬性新增至 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 可避免在下一個步驟中出現套件安裝警告。

  3. 安裝必要的 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 文件。

  4. 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 命令以發佈應用程式。
  5. 使用下列程式碼,在專案根目錄中建立名為 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。
  6. 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 的訊息會傳送到伺服器。 訊息是由使用者名稱和文字方塊中的使用者輸入所組成。 如果傳送可正常運作,則會清除文字方塊的值。

  7. 在專案根目錄下執行下列命令:

    npm i @microsoft/signalr @types/node
    

    上述命令會安裝:

    • SignalR TypeScript 用戶端,可讓用戶端將訊息傳送至伺服器。
    • Node.js 的 TypeScript 類型定義,可啟用 Node.js 類型的編譯時間檢查。

測試應用程式

使用下列步驟確認應用程式可正常運作:

  1. 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 檔案。 此標籤緊接在 </title> 結尾標籤後面。
  2. 選取 [偵錯]>[啟動但不偵錯],啟動瀏覽器中的應用程式,但不附加偵錯工具。 wwwroot/index.html 檔案會提供於 https://localhost:<port>

    如果發生編譯錯誤,請嘗試將解決方案關閉再重新開啟。

  3. 開啟另一個瀏覽器執行個體 (任何瀏覽器),並在網址列中貼上 URL。

  4. 選擇其中一個瀏覽器,在 [訊息] 文字方塊中鍵入某些內容,然後選取 [傳送] 按鈕。 唯一使用者名稱和訊息會立即顯示在兩個頁面上。

兩個瀏覽器視窗都顯示的訊息

下一步

其他資源

本教學課程示範如何在 ASP.NET Core SignalR Web 應用程式中使用 Webpack,以統合及建置以 TypeScript 撰寫的用戶端。 Webpack 可讓開發人員組合並建置 Web 應用程式的用戶端資源。

在本教學課程中,您會了解如何:

  • 建構入門 ASP.NET Core SignalR 應用程式
  • 設定 SignalR TypeScript 用戶端
  • 使用 Webpack 設定組建管線
  • 設定 SignalR 伺服器
  • 啟用用戶端與伺服器之間的通訊

檢視或下載範例程式碼 \(英文\) (如何下載)

必要條件

建立 ASP.NET Core Web 應用程式

設定 Visual Studio 以在 PATH 環境變數中尋找 npm。 根據預設,Visual Studio 會使用在其安裝目錄中找到的 npm 版本。 請遵循 Visual Studio 中的下列指示:

  1. 啟動 Visual Studio。 在 [開始] 視窗中,選取 [不使用程式碼繼續]

  2. 巡覽至 [工具]>[選項]>[專案和方案]>[網頁套件管理]>[外部 Web 工具]

  3. 從清單中選取 $(PATH) 項目。 選取向上箭號,將此項目移至清單中的第二個位置,然後選取 [確定]

    Visual Studio 組態.

Visual Studio 設定完成。

  1. 使用 [檔案]>[新增]>[專案] 功能表選項,然後選擇 [ASP.NET Core Web 應用程式] 範本。 選取 [下一步] 。
  2. 將專案命名為 *SignalRWebPac``,然後選取 [建立]
  3. 從目標 Framework 下拉式清單中選取 [.NET Core],然後從 Framework 選取器下拉式清單中選取 [ASP.NET Core 3.1]。 選取 [空白] 範本,然後選取 [建立]

Microsoft.TypeScript.MSBuild 套件新增至專案:

  1. 方案總管 (右窗格) 中,以滑鼠右鍵按一下專案節點,然後選取 [管理 NuGet 套件]。 在 [瀏覽] 索引標籤中搜尋 Microsoft.TypeScript.MSBuild,然後按一下右側的 [安裝] 以安裝套件。

Visual Studio 會在方案總管的 [相依性] 節點底下新增 NuGet 套件,而在專案中啟用 TypeScript 編譯。

設定 Webpack 和 TypeScript

下列步驟可設定 TypeScript 至 JavaScript 的轉換和用戶端資源的組合。

  1. 在專案根目錄中執行下列命令,以建立 package.json 檔案:

    npm init -y
    
  2. 將醒目提示的屬性新增至 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 可避免在下一個步驟中出現套件安裝警告。

  3. 安裝必要的 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 文件。

  4. 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 Core CLI 的 publish 命令來發行應用程式。
  5. 使用下列程式碼,在專案根目錄中建立名為 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。
  6. 在專案根目錄中建立新的 src 目錄,用以儲存專案的用戶端資產。

  7. 使用下列標記建立 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 會定義首頁的樣板標記。

  8. 建立新的 src/css 目錄。 其目的是要儲存專案的 .css 檔案。

  9. 使用下列 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 檔案會設定應用程式的樣式。

  10. 使用下列 JSON 建立 src/tsconfig.json

    {
      "compilerOptions": {
        "target": "es5"
      }
    }
    

    上述程式碼會設定 TypeScript 編譯器來產生 ECMAScript 5 相容的 JavaScript。

  11. 使用下列程式碼建立 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 函式。

設定應用程式

  1. 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,檔案都會提供。

  2. Startup.Configure 的結尾,將 /hub 路由對應至 ChatHub 中樞。 將顯示 Hello World! 的程式碼取代為以下這一行:

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHub<ChatHub>("/hub");
    });
    
  3. Startup.ConfigureServices 中,呼叫 AddSignalR

    services.AddSignalR();
    
  4. 在專案根目錄 SignalRWebPack/ 中建立名為 Hubs 的新目錄,用以儲存 SignalR 中樞。

  5. 使用下列程式碼建立中樞 Hubs/ChatHub.cs

    using Microsoft.AspNetCore.SignalR;
    using System.Threading.Tasks;
    
    namespace SignalRWebPack.Hubs
    {
        public class ChatHub : Hub
        {
        }
    }
    
  6. Startup.cs 檔案的頂端新增下列 using 陳述式,以解析 ChatHub 參考:

    using SignalRWebPack.Hubs;
    

啟用用戶端與伺服器的通訊

應用程式目前顯示用來傳送訊息的基本表單,但尚未正常運作。 伺服器正在接聽特定的路由,但對於已傳送的訊息不會進行任何處理。

  1. 在專案根目錄下執行下列命令:

    npm i @microsoft/signalr @types/node
    

    上述命令會安裝:

    • SignalR TypeScript 用戶端,可讓用戶端將訊息傳送至伺服器。
    • Node.js 的 TypeScript 類型定義,可啟用 Node.js 類型的編譯時間檢查。
  2. 將醒目提示的程式碼新增至 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 元素。

  3. 現在用戶端可以接收訊息,請設定它來傳送訊息。 將醒目提示的程式碼新增至 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 的訊息會傳送到伺服器。 訊息是由使用者名稱和文字方塊中的使用者輸入所組成。 如果傳送可正常運作,則會清除文字方塊的值。

  4. 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。 接收的訊息就會傳送到連線至中樞的所有用戶端。

測試應用程式

使用下列步驟確認應用程式可正常運作。

  1. 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 檔案。 此標籤緊接在 </title> 結尾標籤後面。
  2. 選取 [偵錯]>[啟動但不偵錯],啟動瀏覽器中的應用程式,但不附加偵錯工具。 wwwroot/index.html 檔案會提供於 http://localhost:<port_number>

    如果發生編譯錯誤,請嘗試將解決方案關閉再重新開啟。

  3. 開啟另一個瀏覽器執行個體 (任何瀏覽器)。 在網址列中貼上 URL。

  4. 選擇其中一個瀏覽器,在 [訊息] 文字方塊中鍵入某些內容,然後選取 [傳送] 按鈕。 唯一使用者名稱和訊息會立即顯示在兩個頁面上。

兩個瀏覽器視窗都顯示的訊息

其他資源

本教學課程示範如何在 ASP.NET Core SignalR Web 應用程式中使用 Webpack,以統合及建置以 TypeScript 撰寫的用戶端。 Webpack 可讓開發人員組合並建置 Web 應用程式的用戶端資源。

在本教學課程中,您會了解如何:

  • 建構入門 ASP.NET Core SignalR 應用程式
  • 設定 SignalR TypeScript 用戶端
  • 使用 Webpack 設定組建管線
  • 設定 SignalR 伺服器
  • 啟用用戶端與伺服器之間的通訊

檢視或下載範例程式碼 \(英文\) (如何下載)

必要條件

建立 ASP.NET Core Web 應用程式

設定 Visual Studio 以在 PATH 環境變數中尋找 npm。 根據預設,Visual Studio 會使用在其安裝目錄中找到的 npm 版本。 請遵循 Visual Studio 中的下列指示:

  1. 巡覽至 [工具]>[選項]>[專案和方案]>[網頁套件管理]>[外部 Web 工具]

  2. 從清單中選取 $(PATH) 項目。 選取向上箭號,將此項目移至清單中的第二個位置。

    Visual Studio 組態

Visual Studio 組態已完成。 現在即可開始建立專案。

  1. 使用 [檔案]>[新增]>[專案] 功能表選項,然後選擇 [ASP.NET Core Web 應用程式] 範本。
  2. 將專案命名為 *SignalRWebPack`,然後選取 [建立]
  3. 從目標 Framework 下拉式清單中選取 [.NET Core],然後從 Framework 選取器下拉式清單中選取 [ASP.NET Core 2.2]。 選取 [空白] 範本,然後選取 [建立]

設定 Webpack 和 TypeScript

下列步驟可設定 TypeScript 至 JavaScript 的轉換和用戶端資源的組合。

  1. 在專案根目錄中執行下列命令,以建立 package.json 檔案:

    npm init -y
    
  2. 將醒目提示的屬性新增至 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 可避免在下一個步驟中出現套件安裝警告。

  3. 安裝必要的 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 文件。

  4. 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 Core CLI 的 publish 命令來發行應用程式。
  5. 使用下列程式碼,在專案根目錄中建立名為 *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。
  6. 在專案根目錄中建立新的 src 目錄,用以儲存專案的用戶端資產。

  7. 使用下列標記建立 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 會定義首頁的樣板標記。

  8. 建立新的 src/css 目錄。 其目的是要儲存專案的 .css 檔案。

  9. 使用下列標記建立 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 檔案會設定應用程式的樣式。

  10. 使用下列 JSON 建立 src/tsconfig.json

    {
      "compilerOptions": {
        "target": "es5"
      }
    }
    

    上述程式碼會設定 TypeScript 編譯器來產生 ECMAScript 5 相容的 JavaScript。

  11. 使用下列程式碼建立 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 應用程式

  1. Startup.Configure 方法中提供的程式碼會顯示 Hello World!。 將 app.Run 方法呼叫取代為對 UseDefaultFiles(IApplicationBuilder)UseStaticFiles(IApplicationBuilder) 的呼叫。

    app.UseDefaultFiles();
    app.UseStaticFiles();
    

    上述程式碼可讓伺服器找出並提供 index.html 檔案,無論使用者輸入的是其完整 URL 還是 Web 應用程式的根 URL。

  2. Startup.ConfigureServices 中呼叫 AddSignalR。 這會將 SignalR 服務新增至專案。

    services.AddSignalR();
    
  3. /hub 路由對應至 ChatHub 中樞。 在 Startup.Configure 的結尾新增以下幾行:

    app.UseSignalR(options =>
    {
        options.MapHub<ChatHub>("/hub");
    });
    
  4. 在專案根目錄中建立名為 Hubs 的新目錄。 其目的是要儲存下一個步驟所建立的 SignalR 中樞。

  5. 使用下列程式碼建立中樞 Hubs/ChatHub.cs

    using Microsoft.AspNetCore.SignalR;
    using System.Threading.Tasks;
    
    namespace SignalRWebPack.Hubs
    {
        public class ChatHub : Hub
        {
        }
    }
    
  6. Startup.cs 檔案的頂端新增下列程式碼,以解析 ChatHub 參考:

    using SignalRWebPack.Hubs;
    

啟用用戶端與伺服器的通訊

應用程式目前會顯示一個簡單的表單來傳送訊息。 當您嘗試這樣做時,不會執行任何動作。 伺服器正在接聽特定的路由,但對於已傳送的訊息不會進行任何處理。

  1. 在專案根目錄下執行下列命令:

    npm install @aspnet/signalr
    

    上述命令會安裝 SignalR TypeScript 用戶端,可讓用戶端將訊息傳送至伺服器。

  2. 將醒目提示的程式碼新增至 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 元素。

  3. 現在用戶端可以接收訊息,請設定它來傳送訊息。 將醒目提示的程式碼新增至 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 的訊息會傳送到伺服器。 訊息是由使用者名稱和文字方塊中的使用者輸入所組成。 如果傳送可正常運作,則會清除文字方塊的值。

  4. 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。 接收的訊息就會傳送到連線至中樞的所有用戶端。

測試應用程式

使用下列步驟確認應用程式可正常運作。

  1. 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 檔案。 此標籤緊接在 </title> 結尾標籤後面。
  2. 選取 [偵錯]>[啟動但不偵錯],啟動瀏覽器中的應用程式,但不附加偵錯工具。 wwwroot/index.html 檔案會提供於 http://localhost:<port_number>

  3. 開啟另一個瀏覽器執行個體 (任何瀏覽器)。 在網址列中貼上 URL。

  4. 選擇其中一個瀏覽器,在 [訊息] 文字方塊中鍵入某些內容,然後選取 [傳送] 按鈕。 唯一使用者名稱和訊息會立即顯示在兩個頁面上。

兩個瀏覽器視窗都顯示的訊息

其他資源