如何将ASP.NET SignalR引用迁移至ASP.NET Core Razor Pages
一、服务端核心代码(StockTicker.cs)重构
ASP.NET Core SignalR完全重构了API,需替换命名空间并调整HubContext获取方式:
替换命名空间
移除旧的Microsoft.AspNet.SignalR和Microsoft.AspNet.SignalR.Hubs,改用Microsoft.AspNetCore.SignalR和System.Threading(Timer相关)。重构单例与依赖注入
取消原GlobalHost的硬编码实例化,改为通过依赖注入获取IHubContext<StockTickerHub>,并将StockTicker注册为单例服务。调整广播逻辑
原Clients.All.updateStockPrice(stock)改为Core SignalR的异步广播方法_hubContext.Clients.All.SendAsync("updateStockPrice", stock),同时补全Stock对象的Change和ChangePercent计算逻辑。
修改后的完整代码:
using System.Collections.Concurrent; using System.Threading; using Microsoft.AspNetCore.SignalR; namespace SignalR.StockTicker { public class StockTicker { private readonly IHubContext<StockTickerHub> _hubContext; private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>(); private readonly object _updateStockPricesLock = new object(); private readonly double _rangePercent = .002; private readonly TimeSpan _updateInterval = TimeSpan.FromMilliseconds(250); private readonly Random _updateOrNotRandom = new Random(); private readonly Timer _timer; private volatile bool _updatingStockPrices = false; // 通过依赖注入获取HubContext public StockTicker(IHubContext<StockTickerHub> hubContext) { _hubContext = hubContext; _stocks.Clear(); var stocks = new List<Stock> { new Stock { Symbol = "MSFT", Price = 30.31m, Open = 30.31m }, new Stock { Symbol = "AAPL", Price = 578.18m, Open = 578.18m }, new Stock { Symbol = "GOOG", Price = 570.30m, Open = 570.30m } }; stocks.ForEach(stock => _stocks.TryAdd(stock.Symbol, stock)); _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval); } public IEnumerable<Stock> GetAllStocks() { return _stocks.Values; } private void UpdateStockPrices(object state) { lock (_updateStockPricesLock) { if (!_updatingStockPrices) { _updatingStockPrices = true; foreach (var stock in _stocks.Values) { if (TryUpdateStockPrice(stock)) { BroadcastStockPrice(stock); } } _updatingStockPrices = false; } } } private bool TryUpdateStockPrice(Stock stock) { var r = _updateOrNotRandom.NextDouble(); if (r > .1) { return false; } var random = new Random((int)Math.Floor(stock.Price)); var percentChange = random.NextDouble() * _rangePercent; var pos = random.NextDouble() > .51; var change = Math.Round(stock.Price * (decimal)percentChange, 2); change = pos ? change : -change; var oldPrice = stock.Price; stock.Price += change; // 补全Change和ChangePercent计算 stock.Change = stock.Price - stock.Open; stock.ChangePercent = stock.Change / stock.Open; return true; } private async void BroadcastStockPrice(Stock stock) { // 使用Core SignalR异步广播方法 await _hubContext.Clients.All.SendAsync("updateStockPrice", stock); } } }
二、实现StockTickerHub
创建SignalR Hub类,暴露客户端调用的GetAllStocks方法:
using Microsoft.AspNetCore.SignalR; namespace SignalR.StockTicker { public class StockTickerHub : Hub { private readonly StockTicker _stockTicker; public StockTickerHub(StockTicker stockTicker) { _stockTicker = stockTicker; } public IEnumerable<Stock> GetAllStocks() { return _stockTicker.GetAllStocks(); } } }
三、完善Program.cs配置
在Program.cs中注册SignalR服务、StockTicker单例,并映射Hub端点:
var builder = WebApplication.CreateBuilder(args); // 添加Razor Pages和SignalR服务 builder.Services.AddRazorPages(); builder.Services.AddSignalR(); // 注册StockTicker为单例 builder.Services.AddSingleton<StockTicker>(); var app = builder.Build(); // 环境配置 if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); // 映射Razor Pages和SignalR Hub app.MapRazorPages(); app.MapHub<StockTickerHub>("/stockTickerHub"); // 端点路径需与客户端一致 app.Run();
四、客户端脚本与页面调整
1. 更新页面脚本引用
移除旧的jQuery和ASP.NET SignalR脚本,改用ASP.NET Core SignalR官方客户端库:
<!-- 移除旧脚本 --> <!-- <script src="/Scripts/jquery-1.10.2.min.js" ></script> --> <!-- <script src="/Scripts/jquery.signalR-2.1.0.js"></script> --> <!-- <script src="/signalr/hubs"></script> --> <!-- 引入ASP.NET Core SignalR客户端 --> <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/7.0.10/signalr.min.js"></script> <!-- 引用自定义脚本 --> <script src="/StockTicker.js"></script>
2. 重构StockTicker.js(移除jQuery依赖)
改用原生JavaScript实现客户端逻辑,适配Core SignalR API:
if (!String.prototype.supplant) { String.prototype.supplant = function (o) { return this.replace(/{([^{}]*)}/g, function (a, b) { var r = o[b]; return typeof r === 'string' || typeof r === 'number' ? r : a; } ); }; } document.addEventListener('DOMContentLoaded', function () { const up = '▲', down = '▼', stockTable = document.getElementById('stockTable'), stockTableBody = stockTable.querySelector('tbody'), rowTemplate = '<tr data-symbol="{Symbol}"><td>{Symbol}</td><td>{Price}</td><td>{Open}</td><td>{Direction} {Change}</td><td>{PercentChange}</td></tr>'; function formatStock(stock) { return { ...stock, Price: stock.Price.toFixed(2), Change: stock.Change.toFixed(2), PercentChange: (stock.ChangePercent * 100).toFixed(2) + '%', Direction: stock.Change === 0 ? '' : stock.Change >= 0 ? up : down }; } // 创建SignalR连接 const connection = new signalR.HubConnectionBuilder() .withUrl("/stockTickerHub") // 与Program.cs中MapHub路径一致 .build(); // 接收服务器推送的更新 connection.on("updateStockPrice", function (stock) { const displayStock = formatStock(stock); const rowHtml = rowTemplate.supplant(displayStock); const existingRow = stockTableBody.querySelector(`tr[data-symbol='${stock.Symbol}']`); if (existingRow) { existingRow.outerHTML = rowHtml; } else { stockTableBody.insertAdjacentHTML('beforeend', rowHtml); } }); // 初始化获取所有股票 async function init() { try { const stocks = await connection.invoke("GetAllStocks"); stockTableBody.innerHTML = ''; stocks.forEach(stock => { const displayStock = formatStock(stock); stockTableBody.insertAdjacentHTML('beforeend', rowTemplate.supplant(displayStock)); }); } catch (err) { console.error(err); } } // 启动连接 connection.start().then(init).catch(err => console.error(err.toString())); });
五、页面模型简化(可选)
若页面无需额外业务逻辑,可简化StockTickerModel.cs:
using Microsoft.AspNetCore.Mvc.RazorPages; namespace SignalR.Pages { public class StockTickerModel : PageModel { public void OnGet() { } } }
内容的提问来源于stack exchange,提问作者Douar Gwenn

