> ## Content Index
> Fetch the complete content index at: https://blog.anthea.day/llms.txt
> Use this file to discover other available public pages before exploring further.

# SignalR 的學習與應用（1）：簡單網頁聊天室
- URL: https://blog.anthea.day/series-dotnet-signalr-1/
- Published: 2025-02-15T18:55:11.000Z
- Updated: 2026-11-07T19:51:50.000Z
- Author: Anthea / 芫荽
- Tags: #Migrated-1792241843742, #Import 2026-10-17 12:57, Coding, .NET, SignalR 的學習與應用

### SignalR 簡介

如果你是第一次接觸 SignalR，可以想像它是一個幫你**處理即時通訊的工具**。它會自動幫你搞定誰在線上、誰下線了，還能讓你輕鬆把訊息送給所有人或指定的人，而且可以應付很多人同時使用。簡單來說，不用煩惱那些複雜的底層細節，SignalR 幫你包好了！

#### 傳輸協議

你可能會想問，SignalR 到底怎麼做到讓訊息這麼即時的呢？ 其實它會自動幫你選擇最適合的傳輸方式：

- **WebSocket**：就像一直開著的專線，速度最快，也是首選。
- Server-Sent Events (SSE)：伺服器主動推消息給你，像訂閱新聞一樣。
- Long Polling：不斷問伺服器「有新消息嗎？」來達成即時效果。 不用擔心怎麼挑，SignalR 會幫你搞定！

---

### 實作步驟

#### 1\. 建立專案並添加 SignalR

在 .NET Core/.NET 5+ 中，SignalR 已經被整合到 ASP.NET Core 框架中了，所以我們不需要額外安裝 SignalR 的服務器端套件，只需要在 `Program.cs` 中配置 SignalR：

```csharp
using WebApiDemo.Hubs; 
 
var builder = WebApplication.CreateBuilder(args); 
 
// 添加 SignalR 服務到 DI 容器 
builder.Services.AddSignalR(); 
 
var app = builder.Build(); 
 
app.UseStaticFiles(); 
app.UseRouting();     // 啟用路由 
 
// 配置 SignalR Hub 路由 
app.MapHub<ChatHub>("/chatHub"); 
 
// 設置默認頁面 
app.MapGet("/", () => Results.File("wwwroot/index.html", "text/html")); 
 
app.Run();
```

#### 2\. 創建 Hub

`Hub` 是 SignalR 的核心角色，可以想成「訊息小幫手」，負責在用戶的瀏覽器和伺服器之間傳遞訊息。基本的 Hub 需要做的事很簡單：收到使用者的訊息並轉送給其他人，處理使用者連線和斷線的通知。

```csharp
using Microsoft.AspNetCore.SignalR; 
 
namespace WebApiDemo.Hubs; 
 
public class ChatHub : Hub 
{ 
    // 這個方法用來接收使用者的訊息並廣播給所有人 
    public async Task SendMessage(string user, string message) 
    { 
        await Clients.All.SendAsync("ReceiveMessage", user, message); 
    } 
 
    // 使用者連線時會觸發這裡，把新加入的使用者通知給大家 
    public override async Task OnConnectedAsync() 
    { 
        await Clients.All.SendAsync("UserConnected", Context.ConnectionId); 
        await base.OnConnectedAsync(); 
    } 
 
    // 使用者斷線時會執行這裡，把誰離開的通知發給所有人 
    public override async Task OnDisconnectedAsync(Exception? exception) 
    { 
        await Clients.All.SendAsync("UserDisconnected", Context.ConnectionId); 
        await base.OnDisconnectedAsync(exception); 
    } 
}
```

**Hub 重要屬性**：`Clients`：負責把訊息發送給誰，例如：

- `Clients.All`：送給所有人，不管是誰
- `Clients.Caller`：只回傳給發送訊息的人
- `Clients.Others`：送給除了發訊息的人以外的所有人

`Context`：提供關於這次連線的資訊，例如：

- `Context.ConnectionId`：每個使用者連上來後都有一個獨一無二的編號
- `Context.User`：使用者的身份資訊，適用在需要登入或認證的應用程式

#### 3\. 客戶端實現

在 root 目錄底下建立 `wwwroot` 的資料夾，並且建立 `index.html` 輸入以下內容：

```xml
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="UTF-8"> 
    <title>SignalR 聊天室</title> 
    <style> 
        #messagesList { border: 1px solid #ccc; height: 300px; overflow-y: auto; padding: 10px; margin-bottom: 10px; } 
        .form-group { margin: 10px 0; } 
        .form-group label { display: inline-block; width: 70px; } 
        .form-group input { padding: 5px; width: 200px; } 
        #sendButton { padding: 5px 15px; margin-top: 10px; } 
    </style> 
</head> 
<body> 
    <div class="container"> 
        <h2>SignalR 聊天室</h2> 
        <div id="messagesList"></div> 
         
        <div class="form-group"> 
            <label for="userInput">用戶名:</label> 
            <input type="text" id="userInput" /> 
        </div> 
        <div class="form-group"> 
            <label for="messageInput">消息:</label> 
            <input type="text" id="messageInput" /> 
        </div> 
        <button id="sendButton">發送</button> 
    </div> 
 
    <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js"></script> 
    <script> 
        // 建立連接 
        const connection = new signalR.HubConnectionBuilder() 
            .withUrl("/chatHub")  // Hub 的路由 
            .build(); 
 
        document.getElementById("sendButton").disabled = true; 
 
        // 註冊接收消息的處理函數 
        connection.on("ReceiveMessage", function (user, message) { 
            const msg = `${user}: ${message}`; 
            const li = document.createElement("div"); 
            li.textContent = msg; 
            document.getElementById("messagesList").appendChild(li); 
        }); 
 
        // 處理用戶連接事件 
        connection.on("UserConnected", function (connectionId) { 
            const msg = `用戶已連接 (${connectionId})`; 
            const li = document.createElement("div"); 
            li.textContent = msg; 
            li.style.color = "green"; 
            document.getElementById("messagesList").appendChild(li); 
        }); 
 
        connection.on("UserDisconnected", function (connectionId) { 
            const msg = `用戶已斷開連接 (${connectionId})`; 
            const li = document.createElement("div"); 
            li.textContent = msg; 
            li.style.color = "red"; 
            document.getElementById("messagesList").appendChild(li); 
        }); 
 
        // 啟動連接 
        connection.start().then(function () { 
            document.getElementById("sendButton").disabled = false; 
        }).catch(function (err) { 
            return console.error(err.toString()); 
        }); 
 
        // 發送消息 
        document.getElementById("sendButton").addEventListener("click", function (event) { 
            const user = document.getElementById("userInput").value; 
            const message = document.getElementById("messageInput").value; 
            // 調用服務器端的 SendMessage 方法 
            connection.invoke("SendMessage", user, message).catch(function (err) { 
                return console.error(err.toString()); 
            }); 
            document.getElementById("messageInput").value = ""; 
            event.preventDefault(); 
        }); 
    </script> 
</body> 
</html>
```

這時候運行你的專案（記得設定 `launchSettings.json`），輸入 `localhost:5000/index.html` ，就可以看到以下的畫面：

![](https://blog.anthea.day/content/images/2026/10/1-_9fwi0qzxadj9dvxsiuqmw.png)

如果你再多開一個新的 tab，就會看到第一個 tab 多了一行連接的用戶：

![](https://blog.anthea.day/content/images/2026/10/1-jfzfqoy1yhceuc8op8ytmq.png)

![](https://blog.anthea.day/content/images/2026/10/1-qtzlieqvaoudcvtx1hzgvw.png)

兩個不同的人可以輸入訊息，按下發送後，另外一邊也會收到

![](https://blog.anthea.day/content/images/2026/10/1-tup1mdsuxgtmcwqea_6mmg.png)

有人離開的話就可以送出通知

到這裡，一個簡單的聊天室就建立完成了。