WPF+ASP.NET?SignalR實現(xiàn)簡易在線聊天功能的示例代碼
在實際業(yè)務(wù)中,當后臺數(shù)據(jù)發(fā)生變化,客戶端能夠?qū)崟r的收到通知,而不是由用戶主動的進行頁面刷新才能查看,這將是一個非常人性化的設(shè)計。有沒有那么一種場景,后臺數(shù)據(jù)明明已經(jīng)發(fā)生變化了,前臺卻因為沒有及時刷新,而導致頁面顯示的數(shù)據(jù)與實際存在差異,從而造成錯誤的判斷。那么如何才能在后臺數(shù)據(jù)變更時及時通知客戶端呢?本文以一個簡單的聊天示例,簡述如何通過WPF+ASP.NET SignalR實現(xiàn)消息后臺通知,僅供學習分享使用,如有不足之處,還請指正。
涉及知識點
在本示例中,涉及知識點如下所示:
開發(fā)工具:Visual Studio 2022 目標框架:.NET6.0
ASP.NET SignalR,一個ASP .NET 下的類庫,可以在ASP .NET 的Web項目中實現(xiàn)實時通信,目前新版已支持.NET6.0及以上版本。在本示例中,作為消息通知的服務(wù)端。
WPF,是微軟推出的基于Windows 的用戶界面框架,主要用于開發(fā)客戶端程序。
什么是ASP.NET SignalR
ASP.NET SignalR 是一個面向 ASP.NET 開發(fā)人員的庫,可簡化將實時 web 功能添加到應(yīng)用程序的過程。 實時 web 功能是讓服務(wù)器代碼將內(nèi)容推送到連接的客戶端立即可用,而不是讓服務(wù)器等待客戶端請求新數(shù)據(jù)的能力。
SignalR 提供了一個簡單的 API,用于創(chuàng)建服務(wù)器到客戶端遠程過程調(diào)用 (RPC) ,該調(diào)用客戶端瀏覽器 (和其他客戶端平臺中的 JavaScript 函數(shù)) 服務(wù)器端 .NET 代碼。 SignalR 還包括用于連接管理的 API (,例如連接和斷開連接事件) ,以及分組連接。
雖然聊天通常被用作示例,但你可以做更多的事情。每當用戶刷新網(wǎng)頁以查看新數(shù)據(jù)時,或者該網(wǎng)頁實施 Ajax 長輪詢以檢索新數(shù)據(jù)時,它都是使用 SignalR 的候選者。SignalR 還支持需要從服務(wù)器進行高頻更新的全新類型的應(yīng)用,例如實時游戲。
在線聊天整體架構(gòu)
在線聊天示例,主要分為服務(wù)端(ASP.NET Web API)和客戶端(WPF可執(zhí)行程序)。具體如下所示:
ASP.NET SignalR在線聊天服務(wù)端
服務(wù)端主要實現(xiàn)消息的接收,轉(zhuǎn)發(fā)等功能,具體步驟如下所示:
1. 創(chuàng)建ASP.NET Web API項目
首先創(chuàng)建ASP.NET Web API項目,默認情況下SignalR已經(jīng)作為項目框架的一部分而存在,所以不需要安裝,直接使用即可。通過項目--依賴性--框架--Microsoft.AspNetCore.App可以查看,如下所示
2. 創(chuàng)建消息通知中心Hub
在項目中新建Chat文件夾,然后創(chuàng)建ChatHub類,并繼承Hub基類。主要包括登錄(Login),聊天(Chat)等功能。如下所示:
using Microsoft.AspNetCore.SignalR; namespace SignalRChat.Chat { public class ChatHub:Hub { private static Dictionary<string,string> dictUsers = new Dictionary<string,string>(); public override Task OnConnectedAsync() { Console.WriteLine($"ID:{Context.ConnectionId} 已連接"); return base.OnConnectedAsync(); } public override Task OnDisconnectedAsync(Exception? exception) { Console.WriteLine($"ID:{Context.ConnectionId} 已斷開"); return base.OnDisconnectedAsync(exception); } /// <summary> /// 向客戶端發(fā)送信息 /// </summary> /// <param name="msg"></param> /// <returns></returns> public Task Send(string msg) { return Clients.Caller.SendAsync("SendMessage",msg); } /// <summary> /// 登錄功能,將用戶ID和ConntectionId關(guān)聯(lián)起來 /// </summary> /// <param name="userId"></param> public void Login(string userId) { if (!dictUsers.ContainsKey(userId)) { dictUsers[userId] = Context.ConnectionId; } Console.WriteLine($"{userId}登錄成功,ConnectionId={Context.ConnectionId}"); //向所有用戶發(fā)送當前在線的用戶列表 Clients.All.SendAsync("Users", dictUsers.Keys.ToList()); } /// <summary> /// 一對一聊天 /// </summary> /// <param name="userId"></param> /// <param name="targetUserId"></param> /// <param name="msg"></param> public void Chat(string userId, string targetUserId, string msg) { string newMsg = $"{userId}|{msg}";//組裝后的消息體 //如果當前用戶在線 if (dictUsers.ContainsKey(targetUserId)) { Clients.Client(dictUsers[targetUserId]).SendAsync("ChatInfo",newMsg); } else { //如果當前用戶不在線,正常是保存數(shù)據(jù)庫,等上線時加載,暫時不做處理 } } /// <summary> /// 退出功能,當客戶端退出時調(diào)用 /// </summary> /// <param name="userId"></param> public void Logout(string userId) { if (dictUsers.ContainsKey(userId)) { dictUsers.Remove(userId); } Console.WriteLine($"{userId}退出成功,ConnectionId={Context.ConnectionId}"); } } }
3. 注冊服務(wù)和路由
聊天類創(chuàng)建成功后,需要配置服務(wù)注入和路由,在Program中,添加代碼,如下所示:
using SignalRChat.Chat; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); //1.添加SignalR服務(wù) builder.Services.AddSignalR(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseRouting(); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); //2.映射路由 app.UseEndpoints(endpoints => { endpoints.MapHub<ChatHub>("/chat"); }); app.Run();
4. ASP.NET SignalR中心對象生存周期
你不會實例化 Hub 類或從服務(wù)器上自己的代碼調(diào)用其方法;由 SignalR Hubs 管道為你完成的所有操作。 SignalR 每次需要處理中心操作(例如客戶端連接、斷開連接或向服務(wù)器發(fā)出方法調(diào)用時)時,SignalR 都會創(chuàng)建 Hub 類的新實例。
由于 Hub 類的實例是暫時性的,因此無法使用它們來維護從一個方法調(diào)用到下一個方法的狀態(tài)。 每當服務(wù)器從客戶端收到方法調(diào)用時,中心類的新實例都會處理消息。 若要通過多個連接和方法調(diào)用來維護狀態(tài),請使用一些其他方法(例如數(shù)據(jù)庫)或 Hub 類上的靜態(tài)變量,或者不派生自 Hub的其他類。 如果在內(nèi)存中保留數(shù)據(jù),請使用 Hub 類上的靜態(tài)變量等方法,則應(yīng)用域回收時數(shù)據(jù)將丟失。
如果要從在 Hub 類外部運行的代碼將消息發(fā)送到客戶端,則無法通過實例化 Hub 類實例來執(zhí)行此操作,但可以通過獲取對 Hub 類的 SignalR 上下文對象的引用來執(zhí)行此操作。
注意:ChatHub每次調(diào)用都是一個新的實例,所以不可以有私有屬性或變量,不可以保存對像的值,所以如果需要記錄一些持久保存的值,則可以采用靜態(tài)變量,或者中心以外的對象。
SignalR客戶端
1. 安裝SignalR客戶端依賴庫
客戶端如果要調(diào)用SignalR的值,需要通過NuGet包管理器,安裝SignalR客戶端,如下所示:
2. 客戶端消息接收發(fā)送
在客戶端實現(xiàn)消息的接收和發(fā)送,主要通過HubConntection實現(xiàn),核心代碼,如下所示:
namespace SignalRClient { public class ChatViewModel:ObservableObject { #region 屬性及構(gòu)造函數(shù) private string targetUserName; public string TargetUserName { get { return targetUserName; } set { SetProperty(ref targetUserName , value); } } private string userName; public string UserName { get { return userName; } set { SetProperty(ref userName, value); Welcome = $"歡迎 {value} 來到聊天室"; } } private string welcome; public string Welcome { get { return welcome; } set { SetProperty(ref welcome , value); } } private List<string> users; public List<string> Users { get { return users; } set {SetProperty(ref users , value); } } private RichTextBox richTextBox; private HubConnection hubConnection; public ChatViewModel() { } #endregion #region 命令 private ICommand loadedCommand; public ICommand LoadedCommand { get { if (loadedCommand == null) { loadedCommand = new RelayCommand<object>(Loaded); } return loadedCommand; } } private void Loaded(object obj) { //1.初始化 InitInfo(); //2.監(jiān)聽 Listen(); //3.連接 Link(); //4.登錄 Login(); // if (obj != null) { var eventArgs = obj as RoutedEventArgs; var window= eventArgs.OriginalSource as ChatWindow; this.richTextBox = window.richTextBox; } } private IRelayCommand<string> sendCommand; public IRelayCommand<string> SendCommand { get { if (sendCommand == null) { sendCommand = new RelayCommand<string>(Send); } return sendCommand; } } private void Send(string msg) { if (string.IsNullOrEmpty(msg)) { MessageBox.Show("發(fā)送的消息為空"); return; } if (string.IsNullOrEmpty(this.TargetUserName)) { MessageBox.Show("發(fā)送的目標用戶為空"); return ; } hubConnection.InvokeAsync("Chat",this.UserName,this.TargetUserName,msg); if (this.richTextBox != null) { Run run = new Run(); Run run1 = new Run(); Paragraph paragraph = new Paragraph(); Paragraph paragraph1 = new Paragraph(); run.Foreground = Brushes.Blue; run.Text = this.UserName; run1.Foreground= Brushes.Black; run1.Text = msg; paragraph.Inlines.Add(run); paragraph1.Inlines.Add(run1); paragraph.LineHeight = 1; paragraph.TextAlignment = TextAlignment.Right; paragraph1.LineHeight = 1; paragraph1.TextAlignment = TextAlignment.Right; this.richTextBox.Document.Blocks.Add(paragraph); this.richTextBox.Document.Blocks.Add(paragraph1); this.richTextBox.ScrollToEnd(); } } #endregion /// <summary> /// 初始化Connection對象 /// </summary> private void InitInfo() { hubConnection = new HubConnectionBuilder().WithUrl("https://localhost:7149/chat").WithAutomaticReconnect().Build(); hubConnection.KeepAliveInterval =TimeSpan.FromSeconds(5); } /// <summary> /// 監(jiān)聽 /// </summary> private void Listen() { hubConnection.On<List<string>>("Users", RefreshUsers); hubConnection.On<string>("ChatInfo",ReceiveInfos); } /// <summary> /// 連接 /// </summary> private async void Link() { try { await hubConnection.StartAsync(); } catch (Exception ex) { MessageBox.Show(ex.Message); } } private void Login() { hubConnection.InvokeAsync("Login", this.UserName); } private void ReceiveInfos(string msg) { if (string.IsNullOrEmpty(msg)) { return; } if (this.richTextBox != null) { Run run = new Run(); Run run1 = new Run(); Paragraph paragraph = new Paragraph(); Paragraph paragraph1 = new Paragraph(); run.Foreground = Brushes.Red; run.Text = msg.Split("|")[0]; run1.Foreground = Brushes.Black; run1.Text = msg.Split("|")[1]; paragraph.Inlines.Add(run); paragraph1.Inlines.Add(run1); paragraph.LineHeight = 1; paragraph.TextAlignment = TextAlignment.Left; paragraph1.LineHeight = 1; paragraph1.TextAlignment = TextAlignment.Left; this.richTextBox.Document.Blocks.Add(paragraph); this.richTextBox.Document.Blocks.Add(paragraph1); this.richTextBox.ScrollToEnd(); } } private void RefreshUsers(List<string> users) { this.Users = users; } } }
運行示例
在示例中,需要同時啟動服務(wù)端和客戶端,所以以多項目方式啟動,如下所示:
運行成功后,服務(wù)端以ASP.NET Web API的方式呈現(xiàn),如下所示:
客戶端需要同時運行兩個,所以在調(diào)試運行啟動一個客戶端后,還要在Debug目錄下,手動雙擊客戶端,再打開一個,并進行登錄,如下所示:
系統(tǒng)運行時,后臺日志輸出如下所示:
到此這篇關(guān)于WPF+ASP.NET SignalR實現(xiàn)簡易在線聊天功能的示例代碼的文章就介紹到這了,更多相關(guān)WPF在線聊天功能內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#實現(xiàn)在PDF文檔中應(yīng)用多種不同字體
在PDF文檔中,可繪制不同字體樣式、不同語言的文字,可通過使用Standard字體、TrueType字體、CJK字體或者自定義(私有)等字體類型。本文將具體介紹實現(xiàn)的方法,需要的可以參考一下2022-01-01C#如何提取經(jīng)緯度文件中的經(jīng)緯度數(shù)據(jù)
近期開發(fā)時需要獲取當前的經(jīng)緯度坐標,下面這篇文章主要給大家介紹了關(guān)于C#如何提取經(jīng)緯度文件中經(jīng)緯度數(shù)據(jù)的相關(guān)資料,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2022-08-08C#數(shù)值轉(zhuǎn)換-隱式數(shù)值轉(zhuǎn)換表參考
隱式轉(zhuǎn)換就是直接使用,比如可以把一個 byte 類型直接用在 int 上2013-04-04C#實現(xiàn)json格式數(shù)據(jù)解析功能的方法詳解
這篇文章主要介紹了C#實現(xiàn)json格式數(shù)據(jù)解析功能的方法,結(jié)合實例形式較為詳細的分析了C#解析json格式數(shù)據(jù)的具體操作步驟與相關(guān)注意事項,需要的朋友可以參考下2017-12-12