教學課程:開始使用 TypeScript 和 Webpack ASP.NET Core SignalR

作者:Sébastien SougnezScott Addie

本教學課程示範如何在 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 套件管理>外部 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 會停用將 語意版本設定 範圍運算子 package.json 寫入 至 的預設行為。 例如,會使用 "webpack": "5.70.0",而不是 "webpack": "^5.70.0"。 此選項可防止意外升級至較新的套件版本。

    如需詳細資訊,請參閱 npm-install 檔。

  4. 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 命令來發佈應用程式。
  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 檔案複製到 srcwwwroot 目錄。
    • 將下列元素插入 wwwroot/index.html 檔案中:
      • <link>標籤,參考 wwwroot/main.<hash>.css 檔案。 此標記緊接在結尾 </head> 標記之前。
      • <script>標籤,參考縮減 wwwroot/main.<hash>.js 的檔案。 此標記緊接在結尾 </body> 標記之前。
  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 套件管理>外部 Web 工具]。

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

    Visual Studio 設定

Visual Studio 設定已完成。

  1. 使用 [檔案>新增>專案]功能表選項,然後選擇[ASP.NET Core Web 應用程式] 範本。 選取 [下一步]。
  2. 將專案命名為 * SignalR WebPac'',然後選取 [ 建立]。
  3. 從目標 Framework 下拉式清單中選取.NET Core,然後從 [架構選取器] 下拉式清單中選取[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 的預設行為,將語意版本設定範圍運算子寫入 *package json 。 例如,會使用 "webpack": "4.41.5",而不是 "webpack": "^4.41.5"。 此選項可防止意外升級至較新的套件版本。

    如需詳細資訊,請參閱 npm-install 檔。

  4. 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 命令來發行應用程式。
  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. 使用下列 JS ON 建立 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 ,新增 對 和 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,都提供檔案。

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

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

    services.AddSignalR();
    
  4. 在專案根SignalR WebPack/中建立名為Hub 的新目錄,以儲存中 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 檔案複製到 srcwwwroot 目錄。
    • 將下列元素插入 wwwroot/index.html 檔案中:
      • <link>標籤,參考 wwwroot/main.<hash>.css 檔案。 此標記緊接在結尾 </head> 標記之前。
      • <script>標籤,參考縮減 wwwroot/main.<hash>.js 的檔案。 此標記緊接在結尾 </body> 標記之前。
  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 套件管理>外部 Web 工具]。

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

    Visual Studio 設定

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

  1. 使用 [檔案>新增>專案]功能表選項,然後選擇[ASP.NET Core Web 應用程式] 範本。
  2. 將專案命名為 * SignalR WebPack',然後選取 [ 建立]。
  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 的預設行為,將語意版本設定範圍運算子寫入 *package json 。 例如,會使用 "webpack": "4.29.3",而不是 "webpack": "^4.29.3"。 此選項可防止意外升級至較新的套件版本。

    如需詳細資訊,請參閱 npm-install 檔。

  4. 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 命令來發行應用程式。
  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. 使用下列 JS ON 建立 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 方法呼叫取代為 對 和 UseStaticFiles(IApplicationBuilder)UseDefaultFiles(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 檔案複製到 srcwwwroot 目錄。
    • 將下列元素插入 wwwroot/index.html 檔案中:
      • <link>標籤,參考 wwwroot/main.<hash>.css 檔案。 此標記緊接在結尾 </head> 標記之前。
      • <script>標籤,參考縮減 wwwroot/main.<hash>.js 的檔案。 此標記緊接在結尾 </body> 標記之前。
  2. >選取 [偵錯啟動但不偵錯] 以在瀏覽器中啟動應用程式,而不附加偵錯工具。 檔案 wwwroot/index.html 會在 提供 http://localhost:<port_number>

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

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

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

其他資源