本篇是本系列入門篇的最後一遍,因爲工做關係,接觸SignalR的時間不是不少。等下次有空的話我會寫一個利用「SignalR」開發一個在線聊天室的系列博文。近期的話我更偏向於更新框架設計相關的文章,到時候我會在文章中分享我在工做中開發的「日誌框架」、「緩存框架」,「分佈式下載框架」等。有興趣的朋友能夠關注我,一塊兒交流。html
本篇博文參考:https://www.asp.net/signalr/overview/getting-started/tutorial-server-broadcast-with-signalrjquery
本教程演示如何建立一個 web 應用程序使用 ASP.NET SignalR 2 提供的服務器廣播功能。服務器廣播意味着發送到客戶端的通訊由服務器啓動。咱們以前聊天室的項目是一個用戶提交數據後,服務器接收到消息,而後把消息廣播給當前全部的用戶。以下圖web
本教程所講的偏偏相反,咱們是由服務器自動把消息推送給當前全部用戶。如股票信息顯示:緩存
在本教程中,咱們將會建立一個股票行情自動收錄的實時應用程序,在其中您想要按期"推"送數據,通知從服務器到全部鏈接的客戶端。在本教程的第一部分,你將從頭開始建立該應用程序的簡化的版本。在本教程的其他部分中,您會安裝 NuGet 包,其中包含額外的功能,並審查這些功能的代碼。安全
咱們依然新建一個空項目,並使用「程序包管理控制檯」執行「Install-PackAge Microsoft.AspNet.SignaLR」安裝最新版本的SignaLR。服務器
安裝完成後會自動打開「readme.txt」文件,文中告訴咱們要新建一個Startup並註冊SignalR。原文以下app
To enable SignalR in your application, create a class called Startup with the following: using Microsoft.Owin; using Owin; using MyWebApplication; namespace MyWebApplication { public class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR(); } } }
咱們按照它的來,新建一個Startup而後在Configuration中註冊SignalR路由。框架
咱們建立Stock用來存放股票詳細信息asp.net
public class Stock { private decimal _price; public string Symbol { get; set; } public decimal Price { get { return _price; } set { if (_price == value) { return; } _price = value; if (DayOpen == 0) { DayOpen = _price; } } } public decimal DayOpen { get; private set; } public decimal Change { get { return Price - DayOpen; } } public double PercentChange { get { return (double)Math.Round(Change / Price, 4); } } }
咱們將在StockTickerHub類中定義和JS交互的代碼。咱們須要維護股票數據的更新和刪除,可是咱們不能在StockTickerHub類中進行操做,由於StockTickerHub類是不保存數據的,若是咱們把股票的CURD代碼放在StockTickerHub中可能會形成咱們的數據丟失。咱們利用VS添加新項選擇集線器V2,並替換成如下代碼dom
[HubName("stockTickerMini")] public class StockTickerHub : Hub { private readonly StockTicker _stockTicker; public StockTickerHub() : this(StockTicker.Instance) { } public StockTickerHub(StockTicker stockTicker) { _stockTicker = stockTicker; } public IEnumerable<Stock> GetAllStocks() { return _stockTicker.GetAllStocks(); } }
在StockTickerHub類中,咱們公開了一個GetAllStocks的方法,當客戶端鏈接成功後咱們將調用「GetAllStocks」方法用來顯示股票信息。HubName標籤是一個別名,按照傳統咱們鏈接須要用StockTickerHub,可是咱們加了HubName後,前段就能夠經過stockTickerMini來建立鏈接。
咱們新建一個StockTicker類,把替換成如下代碼
public class StockTicker { private static readonly Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients)); /// <summary> /// ConcurrentDictionary 表示能夠由多個線程訪問的安全集合 /// 用來存放股票代碼以及股票價格 /// </summary> private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>(); private readonly object _updateStockPricesLock = new object(); //stock can go up or down by a percentage of this factor on each change 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; private StockTicker(IHubConnectionContext<dynamic> clients) { Clients = clients; _stocks.Clear(); var stocks = new List<Stock> { new Stock { Symbol = "MSFT", Price = 30.31m }, new Stock { Symbol = "APPL", Price = 578.18m }, new Stock { Symbol = "GOOG", Price = 570.30m } }; stocks.ForEach(stock => _stocks.TryAdd(stock.Symbol, stock)); _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval); } public static StockTicker Instance { get { return _instance.Value; } } private IHubConnectionContext<dynamic> Clients { get; set; } 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; stock.Price += change; return true; } private void BroadcastStockPrice(Stock stock) { Clients.All.updateStockPrice(stock); } }
因爲多個線程經過要訪問StockTicker,因此StockTicker必須是線程安全的。
好了,如今完成了基本的配置,咱們建立一個名爲index.html的文件並把它設置爲啓動項。index.html的代碼以下
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>ASP.NET SignalR Stock Ticker</title> <style> body { font-family: 'Segoe UI', Arial, Helvetica, sans-serif; font-size: 16px; } #stockTable table { border-collapse: collapse; } #stockTable table th, #stockTable table td { padding: 2px 6px; } #stockTable table td { text-align: right; } #stockTable .loading td { text-align: left; } </style> </head> <body> <h1>ASP.NET SignalR Stock Ticker Sample</h1> <h2>Live Stock Table</h2> <div id="stockTable"> <table border="1"> <thead> <tr><th>Symbol</th><th>Price</th><th>Open</th><th>Change</th><th>%</th></tr> </thead> <tbody> <tr class="loading"><td colspan="5">loading...</td></tr> </tbody> </table> </div> <script src="Scripts/jquery-1.6.4.min.js"></script> <script src="Scripts/jquery.signalR-2.2.0.js"></script> <script src="/signalr/hubs"></script> <script src="StockTicker.js"></script> </body> </html>
在項目新建一個名爲StockTicker.js的文件,並用如下代碼替換
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; } ); }; } $(function () { var ticker = $.connection.stockTickerMini, up = '▲', down = '▼', $stockTable = $('#stockTable'), $stockTableBody = $stockTable.find('tbody'), rowTemplate = '<tr data-symbol="{Symbol}"><td>{Symbol}</td><td>{Price}</td><td>{DayOpen}</td><td>{Direction} {Change}</td><td>{PercentChange}</td></tr>'; function formatStock(stock) { return $.extend(stock, { Price: stock.Price.toFixed(2), PercentChange: (stock.PercentChange * 100).toFixed(2) + '%', Direction: stock.Change === 0 ? '' : stock.Change >= 0 ? up : down }); } function init() { //創建鏈接成功後走的代碼 ticker.server.getAllStocks().done(function (stocks) { $stockTableBody.empty(); $.each(stocks, function () { var stock = formatStock(this); $stockTableBody.append(rowTemplate.supplant(stock)); }); }); } ticker.client.updateStockPrice = function (stock) { var displayStock = formatStock(stock), $row = $(rowTemplate.supplant(displayStock)); $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']') .replaceWith($row); } $.connection.hub.start().done(init); });
$.connection是指SignalR的代理,StockTickerMini是StockTickerHub類HubName所設置的別名。
在全部的變量和函數定義後,會在最後一行啓動SignalR鏈接,並進行初始化。運行起來便可看到效果
請你們注意一下StockTicker類中的BroadcastStockPrice方法,這個方法最終會獲取當前全部的鏈接用戶,並觸發updateStockPrice方法。在前兩章的時候咱們都是由客戶端主動調用Hub類的某一個方法,而後在由方法內部進行觸發前臺JS代碼。在本章中,咱們定義了一個定時器用來定時更新數據,每當數據發生修改時就會主動觸發updateStockPrice,這也是本章與前兩章不一樣的地方。