使用 SignalR 在 .NET Core 8 中构建实时通知的最小 API

d211eaa04aa688e487aa988859f3b582.png

构建实时应用程序在现代 Web 开发中变得至关重要,尤其是对于通知、聊天系统和实时更新等功能。SignalR 是一个强大的 ASP.NET 库,支持服务器端代码和客户端 Web 应用程序之间的无缝实时通信。在本指南中,我们将演练如何使用最少的 API 和 SignalR 在 .NET Core 8 中创建实时通知,确保应用程序保持响应性和吸引力。

为什么使用 SignalR?

  • 实时通信: 它支持实时更新,这对于现代动态应用程序(如聊天系统、游戏、实时体育更新和协作应用程序)至关重要。

  • 简化开发: SignalR 提供了一个简单的 API,它抽象了实时通信的复杂性,使开发人员能够更轻松地实现这些功能。

  • 跨平台: SignalR 支持多个平台,包括 .NET、JavaScript 和 Xamarin,从而在不同类型的应用程序中具有广泛的适用性。

  • 自动重新连接: SignalR 在连接断开的情况下处理自动重新连接,确保实时通信的可靠性。

  • 可扩展性: SignalR 是可缩放的,并且可以与其他服务(如 Azure SignalR 服务)结合使用,后者可处理横向扩展需求并提供高可用性。

创建 SignalR 服务器

安装

首先,让我们安装 SignalR 包。您可以通过 Visual Studio 中的 NuGet 包管理器控制台使用以下命令执行此操作:

Install-Package Microsoft.AspNetCore.SignalR

或者,您也可以使用 NuGet 包管理器 UI 通过搜索“Microsoft.AspNetCore.SignalR”来安装它。

dae52d48ac9e1bda25a86216861ba410.png

创建 ASP.Net Core Web API 项目

第 1 步

打开 Visual Studio 并单击 Create New Project。

步骤 2

选择 ASP.NET Core Web API 模板,如下图所示,然后单击 下一步 按钮。

44e44e8245579e8ffa4b54a38b68c767.png

步骤 3

通过指定项目名称和要保存项目的位置来配置项目。

2e737a2c0fe5879dd2de02cbab8ffb15.png

步骤 4

选择要使用的 .Net Core 框架版本,然后单击“创建”按钮。

f28a7738122c7020298ca4cd6bb06c38.png

步骤 5

转到 Program.cs 并为 SignalR 添加服务

builder.Services.AddSignalR();

步骤 6

添加类 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();  
        }  
    }  
}

步骤 7

转到 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>();

SignalR 接收器

服务器端

单独添加新的控制台项目,或在同一解决方案文件中添加新的控制台项目。

aa50430604b955a0b677d9aeeac4d470.png

从 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.");

56bac1949222ac7eca68dd662f41c71b.png

客户端

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 和控制台应用程序上接收通知。

329a10754c26c28be367d041ce624f97.png

实现实时通知终端节点

app.MapPost("sendStockNotification", async (  
    string stockName,  
    decimal price,  
    IHubContext<StockHub> context) =>  
{  
    await context.Clients.All.SendAsync("ReceiveStockPrice", stockName, price);  
});

当客户端向终端节点发送包含股票名称和价格的 POST 请求时,此代码会将股票价格更新实时广播到所有连接的客户端。客户端将通过他们订阅的方法接收此更新。

如果你喜欢我的文章,请给我一个赞!谢谢

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值