WPF+ASP.NET?SignalR實(shí)現(xiàn)簡(jiǎn)易在線聊天功能的示例代碼
在實(shí)際業(yè)務(wù)中,當(dāng)后臺(tái)數(shù)據(jù)發(fā)生變化,客戶端能夠?qū)崟r(shí)的收到通知,而不是由用戶主動(dòng)的進(jìn)行頁面刷新才能查看,這將是一個(gè)非常人性化的設(shè)計(jì)。有沒有那么一種場(chǎng)景,后臺(tái)數(shù)據(jù)明明已經(jīng)發(fā)生變化了,前臺(tái)卻因?yàn)闆]有及時(shí)刷新,而導(dǎo)致頁面顯示的數(shù)據(jù)與實(shí)際存在差異,從而造成錯(cuò)誤的判斷。那么如何才能在后臺(tái)數(shù)據(jù)變更時(shí)及時(shí)通知客戶端呢?本文以一個(gè)簡(jiǎn)單的聊天示例,簡(jiǎn)述如何通過WPF+ASP.NET SignalR實(shí)現(xiàn)消息后臺(tái)通知,僅供學(xué)習(xí)分享使用,如有不足之處,還請(qǐng)指正。
涉及知識(shí)點(diǎn)
在本示例中,涉及知識(shí)點(diǎn)如下所示:
開發(fā)工具:Visual Studio 2022 目標(biāo)框架:.NET6.0
ASP.NET SignalR,一個(gè)ASP .NET 下的類庫(kù),可以在ASP .NET 的Web項(xiàng)目中實(shí)現(xiàn)實(shí)時(shí)通信,目前新版已支持.NET6.0及以上版本。在本示例中,作為消息通知的服務(wù)端。
WPF,是微軟推出的基于Windows 的用戶界面框架,主要用于開發(fā)客戶端程序。
什么是ASP.NET SignalR
ASP.NET SignalR 是一個(gè)面向 ASP.NET 開發(fā)人員的庫(kù),可簡(jiǎn)化將實(shí)時(shí) web 功能添加到應(yīng)用程序的過程。 實(shí)時(shí) web 功能是讓服務(wù)器代碼將內(nèi)容推送到連接的客戶端立即可用,而不是讓服務(wù)器等待客戶端請(qǐng)求新數(shù)據(jù)的能力。

SignalR 提供了一個(gè)簡(jiǎn)單的 API,用于創(chuàng)建服務(wù)器到客戶端遠(yuǎn)程過程調(diào)用 (RPC) ,該調(diào)用客戶端瀏覽器 (和其他客戶端平臺(tái)中的 JavaScript 函數(shù)) 服務(wù)器端 .NET 代碼。 SignalR 還包括用于連接管理的 API (,例如連接和斷開連接事件) ,以及分組連接。

雖然聊天通常被用作示例,但你可以做更多的事情。每當(dāng)用戶刷新網(wǎng)頁以查看新數(shù)據(jù)時(shí),或者該網(wǎng)頁實(shí)施 Ajax 長(zhǎng)輪詢以檢索新數(shù)據(jù)時(shí),它都是使用 SignalR 的候選者。SignalR 還支持需要從服務(wù)器進(jìn)行高頻更新的全新類型的應(yīng)用,例如實(shí)時(shí)游戲。
在線聊天整體架構(gòu)
在線聊天示例,主要分為服務(wù)端(ASP.NET Web API)和客戶端(WPF可執(zhí)行程序)。具體如下所示:

ASP.NET SignalR在線聊天服務(wù)端
服務(wù)端主要實(shí)現(xiàn)消息的接收,轉(zhuǎn)發(fā)等功能,具體步驟如下所示:
1. 創(chuàng)建ASP.NET Web API項(xiàng)目
首先創(chuàng)建ASP.NET Web API項(xiàng)目,默認(rèn)情況下SignalR已經(jīng)作為項(xiàng)目框架的一部分而存在,所以不需要安裝,直接使用即可。通過項(xiàng)目--依賴性--框架--Microsoft.AspNetCore.App可以查看,如下所示

2. 創(chuàng)建消息通知中心Hub
在項(xiàng)目中新建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ā)送當(dāng)前在線的用戶列表
Clients.All.SendAsync("Users", dictUsers.Keys.ToList());
}
/// <summary>
/// 一對(duì)一聊天
/// </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}";//組裝后的消息體
//如果當(dāng)前用戶在線
if (dictUsers.ContainsKey(targetUserId))
{
Clients.Client(dictUsers[targetUserId]).SendAsync("ChatInfo",newMsg);
}
else {
//如果當(dāng)前用戶不在線,正常是保存數(shù)據(jù)庫(kù),等上線時(shí)加載,暫時(shí)不做處理
}
}
/// <summary>
/// 退出功能,當(dāng)客戶端退出時(shí)調(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. 注冊(cè)服務(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中心對(duì)象生存周期
你不會(huì)實(shí)例化 Hub 類或從服務(wù)器上自己的代碼調(diào)用其方法;由 SignalR Hubs 管道為你完成的所有操作。 SignalR 每次需要處理中心操作(例如客戶端連接、斷開連接或向服務(wù)器發(fā)出方法調(diào)用時(shí))時(shí),SignalR 都會(huì)創(chuàng)建 Hub 類的新實(shí)例。
由于 Hub 類的實(shí)例是暫時(shí)性的,因此無法使用它們來維護(hù)從一個(gè)方法調(diào)用到下一個(gè)方法的狀態(tài)。 每當(dāng)服務(wù)器從客戶端收到方法調(diào)用時(shí),中心類的新實(shí)例都會(huì)處理消息。 若要通過多個(gè)連接和方法調(diào)用來維護(hù)狀態(tài),請(qǐng)使用一些其他方法(例如數(shù)據(jù)庫(kù))或 Hub 類上的靜態(tài)變量,或者不派生自 Hub的其他類。 如果在內(nèi)存中保留數(shù)據(jù),請(qǐng)使用 Hub 類上的靜態(tài)變量等方法,則應(yīng)用域回收時(shí)數(shù)據(jù)將丟失。
如果要從在 Hub 類外部運(yùn)行的代碼將消息發(fā)送到客戶端,則無法通過實(shí)例化 Hub 類實(shí)例來執(zhí)行此操作,但可以通過獲取對(duì) Hub 類的 SignalR 上下文對(duì)象的引用來執(zhí)行此操作。
注意:ChatHub每次調(diào)用都是一個(gè)新的實(shí)例,所以不可以有私有屬性或變量,不可以保存對(duì)像的值,所以如果需要記錄一些持久保存的值,則可以采用靜態(tài)變量,或者中心以外的對(duì)象。
SignalR客戶端
1. 安裝SignalR客戶端依賴庫(kù)
客戶端如果要調(diào)用SignalR的值,需要通過NuGet包管理器,安裝SignalR客戶端,如下所示:

2. 客戶端消息接收發(fā)送
在客戶端實(shí)現(xiàn)消息的接收和發(fā)送,主要通過HubConntection實(shí)現(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ā)送的目標(biāo)用戶為空");
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對(duì)象
/// </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;
}
}
}運(yùn)行示例
在示例中,需要同時(shí)啟動(dòng)服務(wù)端和客戶端,所以以多項(xiàng)目方式啟動(dòng),如下所示:

運(yùn)行成功后,服務(wù)端以ASP.NET Web API的方式呈現(xiàn),如下所示:

客戶端需要同時(shí)運(yùn)行兩個(gè),所以在調(diào)試運(yùn)行啟動(dòng)一個(gè)客戶端后,還要在Debug目錄下,手動(dòng)雙擊客戶端,再打開一個(gè),并進(jìn)行登錄,如下所示:


系統(tǒng)運(yùn)行時(shí),后臺(tái)日志輸出如下所示:

到此這篇關(guān)于WPF+ASP.NET SignalR實(shí)現(xiàn)簡(jiǎn)易在線聊天功能的示例代碼的文章就介紹到這了,更多相關(guān)WPF在線聊天功能內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#實(shí)現(xiàn)在PDF文檔中應(yīng)用多種不同字體
在PDF文檔中,可繪制不同字體樣式、不同語言的文字,可通過使用Standard字體、TrueType字體、CJK字體或者自定義(私有)等字體類型。本文將具體介紹實(shí)現(xiàn)的方法,需要的可以參考一下2022-01-01
C#如何提取經(jīng)緯度文件中的經(jīng)緯度數(shù)據(jù)
近期開發(fā)時(shí)需要獲取當(dāng)前的經(jīng)緯度坐標(biāo),下面這篇文章主要給大家介紹了關(guān)于C#如何提取經(jīng)緯度文件中經(jīng)緯度數(shù)據(jù)的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
C#由當(dāng)前日期計(jì)算相應(yīng)的周一和周日的實(shí)例代碼
這篇文章介紹了C#由當(dāng)前日期計(jì)算相應(yīng)的周一和周日的實(shí)例代碼,有需要的朋友可以參考一下2013-09-09
C#數(shù)值轉(zhuǎn)換-隱式數(shù)值轉(zhuǎn)換表參考
隱式轉(zhuǎn)換就是直接使用,比如可以把一個(gè) byte 類型直接用在 int 上2013-04-04
C#使用ZXing.Net實(shí)現(xiàn)識(shí)別二維碼和條碼
ZXing用Java實(shí)現(xiàn)的多種格式的一維二維條碼圖像處理庫(kù),而ZXing.Net是其.Net版本的實(shí)現(xiàn),本文主要為大家詳細(xì)介紹了如何使用ZXing.Net實(shí)現(xiàn)識(shí)別二維碼和條碼,需要的可以參考下2024-01-01
C#實(shí)現(xiàn)json格式數(shù)據(jù)解析功能的方法詳解
這篇文章主要介紹了C#實(shí)現(xiàn)json格式數(shù)據(jù)解析功能的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了C#解析json格式數(shù)據(jù)的具體操作步驟與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-12-12

