快速入門:使用 SignalR Service 建立聊天室
Azure SignalR 服務是可以協助開發人員使用即時功能輕鬆地建置 Web 應用程式的 Azure 服務。
本文會示範如何開始使用 Azure SignalR 服務。 在此快速入門中,您將會使用 ASP.NET Core Web 應用程式建立聊天應用程式。 此應用程式會與您的 Azure SignalR 服務資源連線,以提供即時的內容更新。 您將會於本機裝載 Web 應用程式,並與多個瀏覽器用戶端連線。 每個用戶端都將能把內容更新推送至所有其他用戶端。
您可以使用任何程式碼編輯器來完成本快速入門中的步驟。 Windows、macOS 及 Linux 平台上都有提供的 Visual Studio Code \(英文\) 是一個選項。
本教學課程的程式碼可於 AzureSignalR-samples GitHub 存放庫下載。 您可以依照建立 SignalR 服務指令碼中的說明,建立此快速入門中使用的 Azure 資源。
如果您沒有 Azure 訂用帳戶,請在開始前建立免費帳戶。
重要
原始 連接字串 只會出現在本文中,以供示範之用。
連接字串包含應用程式存取 Azure Web PubSub 服務所需的授權資訊。 連接字串內的存取金鑰類似於服務的根密碼。 在生產環境中,請一律保護您的存取金鑰。 使用 Azure 金鑰保存庫,使用 Microsoft Entra 識別元安全地管理及輪替您的 連接字串,並使用 Microsoft Entra 標識符來授權存取權。
避免將存取金鑰散發給其他使用者、寫入程式碼,或將其以純文字儲存在他人可以存取的位置。 如果您認為金鑰可能已遭盜用,請輪替金鑰。
準備開始了嗎?
必要條件
- 安裝最新的 .NET Core SDK。
- 下載或複製 AzureSignalR-sample \(英文\) GitHub 存放庫。
建立 Azure SignalR 資源
在本節中,您會建立基本 Azure SignalR 執行個體,以用於您的應用程式。 下列步驟會使用 Azure 入口網站來建立新的執行個體,但您也可以使用 Azure CLI。 如需詳細資訊,請參閱 Azure SignalR Service CLI 參考中的 az signalr create 命令。
- 登入 Azure 入口網站。
- 在頁面的左側,選取 [+ 建立資源]。
- 在 [建立資源] 頁面上的 [搜尋服務和市集] 文字方塊中,輸入 signalr,然後從清單中選取 [SignalR Service]。
- 在 [SignalR Service] 頁面上,選取 [建立]。
- 在 [基本] 索引標籤上,您需要輸入新 SignalR Service 執行個體的重要資訊。 輸入下列值:
欄位 | 建議的值 | 描述 |
---|---|---|
訂用帳戶 | 選擇您的訂用帳戶 | 選取您要用來建立新 SignalR Service 執行個體的訂用帳戶。 |
資源群組 | 建立名為 SignalRTestResources 的新資源群組 | 選取或建立 SignalR 資源的資源群組。 針對本教學課程建立新的資源群組,而不是使用現有的資源群組,會很有用。 若要在完成本教學課程之後釋出資源,請刪除資源群組。 刪除資源群組同時會刪除群組所屬的所有資源。 此動作無法復原。 刪除資源群組之前,請確定其不包含您想要保留的資源。 如需詳細資訊,請參閱 使用資源群組來管理您的 Azure 資源。 |
資源名稱 | testsignalr | 輸入要對 SignalR 資源使用的唯一資源名稱。 如果您的區域中已採用 testsignalr,請新增數字或字元,直到成為唯一的名稱為止。 名稱必須是 1 到 63 個字元的字串,而且只能包含數字、字母和連字號 ( - ) 字元。 名稱的開頭或結尾不能是連字號字元,且連續連字號字元無效。 |
區域 | 選擇您的區域 | 選取新 SignalR Service 執行個體的適當區域。 Azure SignalR Service 目前不適用於所有區域。 如需詳細資訊,請參閱 Azure SignalR 區域可用性 |
定價層 | 選取 [變更],然後選擇 [免費 (僅限開發/測試)]。 選擇 [選取] 來確認您選擇的定價層。 | Azure SignalR Service 有三個定價層:免費、標準和進階。 除非必要條件中另有說明,否則教學課程會使用 [免費] 層。 如需有關階層與定價之間功能差異的詳細資訊,請參閱 Azure SignalR Service 定價 |
服務模式 | 選擇適當的服務模式 | 當您在 Web 應用程式中裝載 SignalR 中樞邏輯並使用 SignalR Service 做為 Proxy 時,請使用 [預設值]。 當您使用 Azure Functions 之類的無伺服器技術來裝載 SignalR 中樞邏輯時,請使用 [無伺服器]。 [傳統] 模式僅適用於回溯相容性,不建議使用。 如需詳細資訊,請參閱 Azure SignalR Service 中的服務模式。 |
您不需要針對 SignalR 教學課程變更 [網路] 和 [標籤] 索引標籤上的設定。
- 選取 [基本] 索引標籤底部的 [檢閱 + 建立] 按鈕。
- 在 [檢閱 + 建立] 索引標籤上,檢閱這些值,然後選取 [建立]。 需要數分鐘才能完成部署。
- 部署完成後,請選取 [移至資源] 按鈕。
- 在 SignalR 資源頁面上,從左側功能表的 [設定] 底下選取 [金鑰]。
- 複製主索引鍵的 [連接字串]。 稍後在本教學課程中,您需要此連接字串來設定您的應用程式。
建立 ASP.NET Core Web 應用程式
在本節中,您會使用 .NET Core 命令列介面 (CLI) \(英文\) 來建立 ASP.NET Core MVC Web 應用程式專案。 使用 .NET Core CLI 而非 Visual Studio 的好處,在於 .NET Core CLI 可同時於 Windows、macOS 及 Linux 平台上取得。
為您的專案建立資料夾。 本快速入門會使用 chattest 資料夾。
在新的資料夾中,執行以下命令以建立專案:
dotnet new web
將祕密管理員新增至專案
在本節中,您會將祕密管理員工具 \(機器翻譯\) 新增至您的專案。 祕密管理員工具能儲存專案樹狀結構外開發工作的敏感性資料。 此作法能協助避免於原始程式碼內意外共用應用程式祕密。
在資料夾中,執行下列命令以 init
UserSecretsId
:dotnet user-secrets init
將名為 Azure:SignalR:ConnectionString 的祕密新增至祕密管理員。
原始 連接字串 只針對示範目的出現在本文中。 在生產環境中,請一律保護您的存取金鑰。 使用 Azure 金鑰保存庫,使用 Microsoft Entra 識別碼安全地管理和輪替密鑰,連接字串 並使用 Microsoft Entra 識別符來授權存取權。
此祕密將包含存取您 SignalR 服務資源的連接字串。 Azure:SignalR:ConnectionString 為 SignalR 在建立連線時會尋找的預設組態金鑰。 請以您 SignalR 服務資源的連接字串取代下列命令中的值。
您必須在和
csproj
檔案相同的目錄中執行此命令。dotnet user-secrets set Azure:SignalR:ConnectionString "<Your connection string>"
祕密管理員將只會用來測試於本機裝載的 Web 應用程式。 在稍後的教學課程中,您會將聊天 Web 應用程式部署至 Azure。 在 Web 應用程式部署至 Azure 之後,您將會使用應用程式設定,而非搭配祕密管理員儲存連接字串。
此祕密可使用設定 API 來存取。 在所有支援的平台上,組態 API 的組態名稱中都適用冒號 (:)。 請參閱取決於環境的組態。
將 Azure SignalR 新增至 Web 應用程式
透過執行下列命令,將參考新增至
Microsoft.Azure.SignalR
NuGet 套件:dotnet add package Microsoft.Azure.SignalR
開啟 Program.cs 並將程式碼更新如下,它會呼叫
AddSignalR()
和AddAzureSignalR()
方法來使用 Azure SignalR Service:var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR().AddAzureSignalR(); var app = builder.Build(); app.UseDefaultFiles(); app.UseRouting(); app.UseStaticFiles(); app.MapHub<ChatSampleHub>("/chat"); app.Run();
不傳遞參數至
AddAzureSignalR()
,表示它會針對 SignalR Service 資源連接字串使用預設組態金鑰。 預設組態金鑰為 Azure: SignalR:ConnectionString。 它也會使用我們將在下一節中建立的ChatSampleHub
。
新增中樞類別
在 SignalR 中,「中樞」是一個核心元件,可公開由用戶端呼叫的一組方法。 在本節中,您會搭配兩個方法定義中樞類別:
BroadcastMessage
:此方法會將訊息廣播至所有用戶端。Echo
:此方法會將訊息傳送回呼叫端。
這兩個方法都會使用由 ASP.NET Core SignalR SDK 所提供的 Clients
介面。 此介面可讓您存取所有已連線的用戶端,以便將內容推送至用戶端。
在您的專案目錄中,新增名為 Hub 的資料夾。 將名為 ChatSampleHub.cs 的新中樞程式碼檔案新增至這個新資料夾。
將下列程式碼新增至 ChatSampleHub.cs 以定義中樞類別,然後儲存檔案。
using Microsoft.AspNetCore.SignalR; public class ChatSampleHub : Hub { public Task BroadcastMessage(string name, string message) => Clients.All.SendAsync("broadcastMessage", name, message); public Task Echo(string name, string message) => Clients.Client(Context.ConnectionId) .SendAsync("echo", name, $"{message} (echo from server)"); }
新增 Web 應用程式的用戶端介面
此聊天室應用程式的用戶端使用者介面,將會由位於 wwwroot 目錄中名為 index.html 檔案內的 HTML 和 JavaScript 所組成。
從範例存放庫的 wwwroot 資料夾複製 css/site.css 檔案。 將您專案的 css/site.css 取代為您所複製的檔案。
在名為 index.html 的 wwwroot 目錄中建立新檔案、複製下列 HTML,並將其貼入新建立的檔案中。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta name="viewport" content="width=device-width">
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="css/site.css" rel="stylesheet" />
<title>Azure SignalR Group Chat</title>
</head>
<body>
<h2 class="text-center" style="margin-top: 0; padding-top: 30px; padding-bottom: 30px;">Azure SignalR Group Chat</h2>
<div class="container" style="height: calc(100% - 110px);">
<div id="messages" style="background-color: whitesmoke; "></div>
<div style="width: 100%; border-left-style: ridge; border-right-style: ridge;">
<textarea id="message" style="width: 100%; padding: 5px 10px; border-style: hidden;"
placeholder="Type message and press Enter to send..."></textarea>
</div>
<div style="overflow: auto; border-style: ridge; border-top-style: hidden;">
<button class="btn-warning pull-right" id="echo">Echo</button>
<button class="btn-success pull-right" id="sendmessage">Send</button>
</div>
</div>
<div class="modal alert alert-danger fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<div>Connection Error...</div>
<div><strong style="font-size: 1.5em;">Hit Refresh/F5</strong> to rejoin. ;)</div>
</div>
</div>
</div>
</div>
<!--Reference the SignalR library. -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>
<!--Add script to update the page and send messages.-->
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function () {
function getUserName() {
function generateRandomName() {
return Math.random().toString(36).substring(2, 10);
}
// Get the user name and store it to prepend to messages.
var username = generateRandomName();
var promptMessage = "Enter your name:";
do {
username = prompt(promptMessage, username);
if (!username || username.startsWith("_") || username.indexOf("<") > -1 || username.indexOf(">") > -1) {
username = "";
promptMessage = "Invalid input. Enter your name:";
}
} while (!username)
return username;
}
username = getUserName();
// Set initial focus to message input box.
var messageInput = document.getElementById("message");
messageInput.focus();
function createMessageEntry(encodedName, encodedMsg) {
var entry = document.createElement("div");
entry.classList.add("message-entry");
if (encodedName === "_SYSTEM_") {
entry.innerHTML = encodedMsg;
entry.classList.add("text-center");
entry.classList.add("system-message");
} else if (encodedName === "_BROADCAST_") {
entry.classList.add("text-center");
entry.innerHTML = `<div class="text-center broadcast-message">${encodedMsg}</div>`;
} else if (encodedName === username) {
entry.innerHTML = `<div class="message-avatar pull-right">${encodedName}</div>` +
`<div class="message-content pull-right">${encodedMsg}<div>`;
} else {
entry.innerHTML = `<div class="message-avatar pull-left">${encodedName}</div>` +
`<div class="message-content pull-left">${encodedMsg}<div>`;
}
return entry;
}
function appendMessage(encodedName, encodedMsg) {
var messageEntry = createMessageEntry(encodedName, encodedMsg);
var messageBox = document.getElementById("messages");
messageBox.appendChild(messageEntry);
messageBox.scrollTop = messageBox.scrollHeight;
}
function bindConnectionMessage(connection) {
var messageCallback = function (name, message) {
if (!message) return;
// Html encode display name and message.
var encodedName = name;
var encodedMsg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
appendMessage(encodedName, encodedMsg);
};
// Create a function that the hub can call to broadcast messages.
connection.on("broadcastMessage", messageCallback);
connection.on("echo", messageCallback);
connection.onclose(onConnectionError);
}
function onConnected(connection) {
console.log("connection started");
connection.send("broadcastMessage", "_SYSTEM_", username + " JOINED");
document.getElementById("sendmessage").addEventListener("click", function (event) {
// Call the broadcastMessage method on the hub.
if (messageInput.value) {
connection.send("broadcastMessage", username, messageInput.value)
.catch((e) => appendMessage("_BROADCAST_", e.message));
}
// Clear text box and reset focus for next comment.
messageInput.value = "";
messageInput.focus();
event.preventDefault();
});
document.getElementById("message").addEventListener("keypress", function (event) {
if (event.keyCode === 13) {
event.preventDefault();
document.getElementById("sendmessage").click();
return false;
}
});
document.getElementById("echo").addEventListener("click", function (event) {
// Call the echo method on the hub.
connection.send("echo", username, messageInput.value);
// Clear text box and reset focus for next comment.
messageInput.value = "";
messageInput.focus();
event.preventDefault();
});
}
function onConnectionError(error) {
if (error && error.message) {
console.error(error.message);
}
var modal = document.getElementById("myModal");
modal.classList.add("in");
modal.style = "display: block;";
}
var connection = new signalR.HubConnectionBuilder()
.withUrl("/chat")
.build();
bindConnectionMessage(connection);
connection.start()
.then(function () {
onConnected(connection);
})
.catch(function (error) {
console.error(error.message);
});
});
</script>
</body>
</html>
index.html 中的程式碼會呼叫 HubConnectionBuilder.build()
,以建立與 Azure SignalR 資源的 HTTP 連線。
如果連線成功,該連線會傳遞至 bindConnectionMessage
,這會為針對用戶端的傳入內容推送新增事件處理常式。
HubConnection.start()
會開始與中樞進行通訊。 然後 onConnected()
會新增按鈕事件處理常式。 這些處理常式會使用連線來允許此用戶端將內容更新推送至所有已連線的用戶端。
於本機建置並執行應用程式
執行下列命令以在本機執行 Web 應用程式:
dotnet run
應用程式會裝載在本機,其輸出包含 localhost URL,如下所示:
Building... info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5000 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down. info: Microsoft.Hosting.Lifetime[0] Hosting environment: Development
開啟兩個瀏覽器視窗。 在每個瀏覽器中,移至輸出視窗中顯示的localhost URL,例如,如上述輸出視窗所示的 http://localhost:5000/。 系統將提示您輸入您的名稱。 為兩個用戶端輸入用戶端名稱,然後使用 [Send] \(傳送\) 按鈕在兩個用戶端之間測試推送訊息內容。
清除資源
如果您準備繼續進行下一個教學課程,則可以保留在本快速入門中所建立的資源,並重複使用它們。
如果您已完成快速入門範例應用程式,可以刪除在此快速入門中建立的 Azure 資源以避免衍生費用。
重要
刪除資源群組是無法回復的動作,而且會刪除該群組中的所有資源。 請確定您不會誤刪錯誤的資源群組或資源。 如果您在包含需保留資源的現有資源群組內部,建立了此範例中的資源,則可以從每個資源刀鋒視窗中個別刪除每個資源,而不必刪除整個資源群組。
登入 Azure 入口網站,然後選取 [資源群組]。
在 [依名稱篩選] 文字方塊中,輸入您資源群組的名稱。 本快速入門的指示是使用名為 SignalRTestResources 的資源群組。 在結果清單中的該資源群組上,選取 (...) >[刪除資源群組]。
系統將會要求您確認是否刪除資源群組。 輸入您資源群組的名稱以進行確認,然後選取 [刪除]。
不久後,系統便會刪除該資源群組及其所有的資源。
下一步
在本快速入門中,您已建立新的 Azure SignalR 服務資源。 然後會搭配 ASP.NET Core Web 應用程式使用它,以將內容更新即時推送至多個已連線的用戶端。 若要深入了解如何使用 Azure SignalR 服務,請繼續進行下一個示範驗證的教學課程。