构建实时应用程序在现代 Web 开发中变得至关重要,尤其是对于通知、聊天系统和实时更新等功能。SignalR 是一个强大的 ASP.NET 库,支持服务器端代码和客户端 Web 应用程序之间的无缝实时通信。在本指南中,我们将演练如何使用最少的 API 和 SignalR 在 .NET Core 8 中创建实时通知,确保应用程序保持响应性和吸引力。
为什么使用 SignalR?
安装
首先,让我们安装 SignalR 包。您可以通过 Visual Studio 中的 NuGet 包管理器控制台使用以下命令执行此操作:
Install-Package Microsoft.AspNetCore.SignalR
或者,您也可以使用 NuGet 包管理器 UI 通过搜索“Microsoft.AspNetCore.SignalR”来安装它。
打开 Visual Studio 并单击 Create New Project。
选择 ASP.NET Core Web API 模板,如下图所示,然后单击 下一步 按钮。
通过指定项目名称和要保存项目的位置来配置项目。
选择要使用的 .Net Core 框架版本,然后单击“创建”按钮。
转到 Program.cs 并为 SignalR 添加服务
builder.Services.AddSignalR();
添加类 StockHub.cs
using Microsoft.AspNetCore.SignalR;
namespace SignalRWebAPI
{
public class StockHub:Hub
{
public async Task SendStockPrice(string stockName, decimal price)
{
await Clients.All.SendAsync("ReceiveStockPrice", stockName, price);
}
public override async Task OnConnectedAsync()
{
string connectionId = Context.ConnectionId;
await base.OnConnectedAsync();
}
}
}
转到 Program.cs 并映射 stockHub
app.MapHub<StockHub>("/hubs/stock");
步骤 8 后台服务持续推送通知 添加类 Worker.cs
using Microsoft.AspNetCore.SignalR;
namespace SignalRWebAPI
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IHubContext<StockHub> _stockHub;
private const string stockName = "Basic Stock Name";
private decimal stockPrice = 100;
public Worker(ILogger<Worker> logger, IHubContext<StockHub> stockHub)
{
_logger = logger;
_stockHub = stockHub;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
Random rnd = new Random();
decimal stockRaise = rnd.Next(1, 10000);
//List of stock names
string[] stockNames = { "Apple", "Microsoft", "Google", "Amazon", "Facebook" };
//Random stock name
var stockName = stockNames[rnd.Next(0, stockNames.Length)];
//Send Notification
await _stockHub.Clients.All.SendAsync("ReceiveStockPrice", stockName, stockRaise);
_logger.LogInformation("Sent stock price: {stockName} - {stockRaise}", stockName, stockRaise);
await Task.Delay(4000, stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending stock price");
}
}
}
}
}
在 Program.cs 中添加此后台服务
builder.Services.AddHostedService<Worker>();
单独添加新的控制台项目,或在同一解决方案文件中添加新的控制台项目。
从 Program.cs 中删除 Console App 中的所有代码,并粘贴以下代码。
using Microsoft.AspNetCore.SignalR.Client;
//here is SignalR Sender URL
string hubUrl = "https://localhost:7091/hubs/stock";
var hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.Build();
// Register a handler for messages from the SignalR hub
// "ReceiveStockPrice" is the topic to which SignalR sending the singnals
hubConnection.On<string, decimal>("ReceiveStockPrice", (stockName, stockPrice) =>
{
Console.WriteLine($"Message received--> Stock Name: {stockName} Stock Price: {stockPrice}");
});
try
{
// Start the connection
hubConnection.StartAsync().Wait();
Console.WriteLine("SignalR connection started.");
}
catch (Exception ex)
{
Console.WriteLine($"Error connecting to SignalR: {ex.Message}");
throw;
}
//Create a cancellation token to stop the connection
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
//hubConnection.StopAsync().Wait();
var cancellationToken = cancellationTokenSource.Token;
// Handle Ctrl+C to gracefully shut down the application
Console.CancelKeyPress += (sender, a) =>
{
a.Cancel = true;
Console.WriteLine("Stopping SignalR connection...");
cancellationTokenSource.Cancel();
};
try
{
// Keep the application running until it is cancelled
await Task.Delay(Timeout.Infinite, cancellationToken);
}
catch (TaskCanceledException)
{
}
// Stop the connection gracefully
await hubConnection.StopAsync();
Console.WriteLine("SignalR connection closed.");
Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SignalR Client</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.18/signalr.min.js"></script>
</head>
<body>
<h1>Stock Prices</h1>
<div id="stockPrices"></div>
<script>
const connection = new signalR.HubConnectionBuilder()
.withUrl("https://localhost:7091/hubs/stock")
.build();
connection.on("ReceiveStockPrice", (stockName, stockPrice) => {
const stockPricesDiv = document.getElementById("stockPrices");
const newPrice = document.createElement("div");
newPrice.textContent = `Stock received: ${stockName} - ${stockPrice}`;
stockPricesDiv.appendChild(newPrice);
});
connection.start()
.then(() => {
console.log("SignalR connection started.");
})
.catch(err => {
console.error("Error connecting to SignalR: ", err);
});
window.addEventListener("beforeunload", () => {
connection.stop().then(() => {
console.log("SignalR connection stopped.");
}).catch(err => {
console.error("Error stopping SignalR connection: ", err);
});
});
</script>
</body>
</html>
现在在 Web 和控制台应用程序上接收通知。
实现实时通知终端节点
app.MapPost("sendStockNotification", async (
string stockName,
decimal price,
IHubContext<StockHub> context) =>
{
await context.Clients.All.SendAsync("ReceiveStockPrice", stockName, price);
});
当客户端向终端节点发送包含股票名称和价格的 POST 请求时,此代码会将股票价格更新实时广播到所有连接的客户端。客户端将通过他们订阅的方法接收此更新。