ASP.NET Core SignalR :學習訊息通訊,實現一個訊息通知
什麼是 SignalR
目前我用業餘時間正在做一個部落格系統,其中有個功能就是評論通知,就是假如A使用者評論B使用者的時候,如果B使用者首頁處於開啟狀態,那麼就會提示B使用者有未讀訊息。暫時用SignalR來實現這個功能。我也是看了兩天的資料才明白怎麼去使用。
關於SignalR的理論知識可以去官網或者百度,我這裡只是結合自己的功能來分享下,如果有錯,請原諒指出。
下載js
SignalR是需要微軟提供的js,因為我的專案是前後端分離的,所以我是單獨下載到一個資料夾,然後複製js到我的前端專案裡。只需要signalr.js
頁面載入建立連線
//建立連線$.ajax({ success: function (response) { connection.start().then(function () { connection.invoke('SetConnectionMaps', response.data.account); } }, });
var connection = new signalR.HubConnectionBuilder().withUrl('http://127.0.0.1:5000/chatHub').build();
//ajax執行成功執行
首先你要了解到SignalR基本執行的原理,官網:https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/signalr?view=aspnetcore-3.1&tabs=visual-studio
你可以直接繼承Hub這個類,我這裡用的是強型別Hub<T>,我就是為了讓前端和後端統一下。剛開始Hub<T>我糾結了好久,不知道怎麼用,最後我手動做了下,認為它只是為了方便前端和後端統一。
如果你只是簡單的繼承Hub類,你就必須呼叫SendAsync方法,並且指定前端接收觸發的方法名稱“InvokeMessage”,如果你後端和前端名字對應不上,就會有問題。
public class SingalrService : Hub { private ISingalrSvc _singalrSvc; public SingalrService(ISingalrSvc singalrSvc) { _singalrSvc = singalrSvc; } public async Task SendMessageAsync(Message sendMessage) { await Clients.All.SendAsync("InvokeMessage",sendMessage); } public void SetConnectionMaps(string account) { string connectionid = Context.ConnectionId; _singalrSvc.SetConnectionMaps(connectionid, account); } public override Task OnDisconnectedAsync(Exception exception) { _singalrSvc.Remove(Context.ConnectionId); return base.OnDisconnectedAsync(exception); } }
所以有了強型別Hub<T>,自己定義一個介面,提過方法InvokeMessage供前前端呼叫。
/// <summary> /// 客戶端js呼叫方法 /// </summary> public interface ISingalrClient { Task InvokeMessage(Message sendMessage); } public class SingalrService : Hub<ISingalrClient> { private ISingalrSvc _singalrSvc; public SingalrService(ISingalrSvc singalrSvc) { _singalrSvc = singalrSvc; } public void SetConnectionMaps(string account) { string connectionid = Context.ConnectionId; _singalrSvc.SetConnectionMaps(connectionid, account); }
//連線中斷時執行,微軟這樣描述的:
//重寫OnDisconnectedAsync
虛方法,以便在客戶端斷開連線時執行操作。 如果客戶端故意斷開連線(例如,通過呼叫connection.stop()
),exception
引數將null
。
//但是,如果客戶端由於錯誤(例如網路故障)而斷開連線,則exception
引數將包含描述失敗的異常 public override Task OnDisconnectedAsync(Exception exception) { _singalrSvc.Remove(Context.ConnectionId); return base.OnDisconnectedAsync(exception); } }
這個時候一個使用者打開了首頁,然後首頁有個js方法來初始化連線,同一個頁面內的connectionid是一樣的,每次重新整理或新開啟一個視窗的新頁面的connectionid是不一樣的,並且你重新整理頁面或者關掉會認為是連線中斷,會執行OnDisconnectedAsync方法,這個方法時SingalR自帶的,它是個虛方法,你也可以重寫,就像我一樣。我這裡的程式碼邏輯是將連線id和當前登入人作為鍵值對存入記憶體,然後使用者關掉頁面就會執行OnDisconnectedAsync方法,將相關的coonectionid從記憶體刪掉:
layui.use(['element', 'layer'], function () {
var element = layui.element; element.render('nav'); initLoad();
//初始化連線,每個頁面的connection的connectionid是一樣的,但是每次建立的不一樣 var connection = new signalR.HubConnectionBuilder().withUrl('http://127.0.0.1:5000/chatHub').build();
//繫結後臺觸發的方法,前面已經講過了,具體業務還沒實現, connection.on('InvokeMessage', (reviceMessage) => { var v = reviceMessage; }); $.ajax({ url: url + 'user/userInfo', type: 'get', dataType: 'json', beforeSend: function (xhr) { doBeforeSend(xhr); }, success: function (response) { if (response.code == '1') { $("#nologin").show(); $("#user").hide(); } else { $("#nologin").hide(); $("#user").show(); $("#photo").attr('src', response.data.headPhoto);
//連線開始 connection.start().then(function () {
//呼叫後臺方法,不是api介面,將當前登入人賬號傳過去 connection.invoke('SetConnectionMaps', response.data.account); }) } }, complete: function (xhr) { doComplete(xhr); }, }); });
這個時候連線已經建立完成,並且使用者並沒有關閉首頁,連線一直處於連線狀態。這個時候另一個使用者打開了一篇文章詳情,並且對它評論提交內容後,我讓它觸發了一個連線SingalR的事件,
form.on('submit(review)', function (data) { loading = layer.load(2); var commentModel = { 'Content': data.field.desc, } $.ajax({ url: url + 'article/review/' + id, contentType: 'application/json; charset=utf-8', type: 'post', datatype: 'json', data: JSON.stringify(commentModel), beforeSend: function (xhr) { doBeforeSend(xhr); }, success: function (response) { if (response.code == 0) { //另一個使用者建立了連線 var connection = new signalR.HubConnectionBuilder().withUrl('http://127.0.0.1:5000/chatHub').build(); connection.start().then(function () { var apiRoute=url+'Singalr/admin';//admin是我設定死的,實際應該是自己判斷,會呼叫下面的api,[Route("api/[controller]")],SingalR也是支援api呼叫的 var token=localStorage.getItem('token'); fetch(apiRoute,{ method:'get', headers:{ 'Authorization':'Bearer ' + token } }) event.preventDefault(); }) layer.close(loading); } else { layer.close(loading); layer.msg("評論失敗", { icon: 5 }); } }, complete: function (xhr) { doComplete(xhr); }, })
[ApiController] public class SingalrController : ControllerBase { private IHubContext<SingalrService, ISingalrClient> _hubContext; private ISingalrSvc _singalrSvc; public SingalrController(IHubContext<SingalrService, ISingalrClient> hubContext, ISingalrSvc singalrSvc) { _hubContext = hubContext; _singalrSvc = singalrSvc; } /// <summary> /// 查詢未處理數量 /// </summary> /// <param name="account"></param> /// <returns></returns> [HttpGet("{account}")] public async Task NewsCount(string account) { Message sendMessage = new Message(); sendMessage.Data = "11";
//剛已經講了,使用者載入首頁的時候已經把connectionid和account存入到了記憶體裡面,現在再取使用者相關的connectionID,如果直接呼叫Clinets.ALL就是給所有客戶端傳送訊息 IReadOnlyList<string> connectionIds = (IReadOnlyList<string>)_singalrSvc.GetConnectionIds(account); await _hubContext.Clients.Clients(connectionIds).InvokeMessage(sendMessage); } }
這個時候呼叫了這個api執行了裡面的_hubContext.Clients.Clients(connectionIds).InvokeMessage(sendMessage),connectionIds是根據業務邏輯所判斷的觸發的那些客戶端;然後前端會根據方法名響應對應的js程式碼,如下
layui.use(['element', 'layer'], function () { var element = layui.element; element.render('nav'); initLoad(); var connection = new signalR.HubConnectionBuilder().withUrl('http://127.0.0.1:5000/chatHub').build();
//我認為是一個用來偵聽服務端方法的js connection.on('InvokeMessage', (reviceMessage) => { var v = reviceMessage;
//響應後端方法成功後,就開始自己的業務邏輯 }); $.ajax({ url: url + 'user/userInfo', type: 'get', dataType: 'json', beforeSend: function (xhr) { doBeforeSend(xhr); }, success: function (response) { if (response.code == '1') { $("#nologin").show(); $("#user").hide(); } else { $("#nologin").hide(); $("#user").show(); $("#photo").attr('src', response.data.headPhoto); connection.start().then(function () { connection.invoke('SetConnectionMaps', response.data.account); }) } }, complete: function (xhr) { doComplete(xhr); }, }); });
遊戲也能賺錢?如果你熱愛遊戲,並且想通過遊戲贏得零花錢,5173是個不錯的選擇 http://www.5173.com/?recommenduserid=US15061749098191-04F6