软件编程
位置:首页>> 软件编程>> C#编程>> WPF+ASP.NET SignalR实现简易在线聊天功能的示例代码

WPF+ASP.NET SignalR实现简易在线聊天功能的示例代码

作者:小六公子  发布时间:2023-01-09 09:34:07 

标签:WPF,在线,聊天

在实际业务中,当后台数据发生变化,客户端能够实时的收到通知,而不是由用户主动的进行页面刷新才能查看,这将是一个非常人性化的设计。有没有那么一种场景,后台数据明明已经发生变化了,前台却因为没有及时刷新,而导致页面显示的数据与实际存在差异,从而造成错误的判断。那么如何才能在后台数据变更时及时通知客户端呢?本文以一个简单的聊天示例,简述如何通过WPF+ASP.NET SignalR实现消息后台通知,仅供学习分享使用,如有不足之处,还请指正。

涉及知识点

在本示例中,涉及知识点如下所示:

开发工具:Visual Studio 2022 目标框架:.NET6.0

ASP.NET SignalR,一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信,目前新版已支持.NET6.0及以上版本。在本示例中,作为消息通知的服务端。

WPF,是微软推出的基于Windows 的用户界面框架,主要用于开发客户端程序。

什么是ASP.NET SignalR

ASP.NET SignalR 是一个面向 ASP.NET 开发人员的库,可简化将实时 web 功能添加到应用程序的过程。 实时 web 功能是让服务器代码将内容推送到连接的客户端立即可用,而不是让服务器等待客户端请求新数据的能力。

WPF+ASP.NET SignalR实现简易在线聊天功能的示例代码

SignalR 提供了一个简单的 API,用于创建服务器到客户端远程过程调用 (RPC) ,该调用客户端浏览器 (和其他客户端平台中的 JavaScript 函数) 服务器端 .NET 代码。 SignalR 还包括用于连接管理的 API (,例如连接和断开连接事件) ,以及分组连接。

WPF+ASP.NET SignalR实现简易在线聊天功能的示例代码

虽然聊天通常被用作示例,但你可以做更多的事情。每当用户刷新网页以查看新数据时,或者该网页实施 Ajax 长轮询以检索新数据时,它都是使用 SignalR 的候选者。SignalR 还支持需要从服务器进行高频更新的全新类型的应用,例如实时游戏。

在线聊天整体架构

在线聊天示例,主要分为服务端(ASP.NET Web API)和客户端(WPF可执行程序)。具体如下所示:

WPF+ASP.NET SignalR实现简易在线聊天功能的示例代码

ASP.NET SignalR在线聊天服务端

服务端主要实现消息的接收,转发等功能,具体步骤如下所示:

1. 创建ASP.NET Web API项目

首先创建ASP.NET Web API项目,默认情况下SignalR已经作为项目框架的一部分而存在,所以不需要安装,直接使用即可。通过项目--依赖性--框架--Microsoft.AspNetCore.App可以查看,如下所示

WPF+ASP.NET SignalR实现简易在线聊天功能的示例代码

2. 创建消息通知中心Hub

在项目中新建Chat文件夹,然后创建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>
       /// 向客户端发送信息
       /// </summary>
       /// <param name="msg"></param>
       /// <returns></returns>
       public Task Send(string msg) {
           return Clients.Caller.SendAsync("SendMessage",msg);
       }

/// <summary>
       /// 登录功能,将用户ID和ConntectionId关联起来
       /// </summary>
       /// <param name="userId"></param>
       public void Login(string userId) {
           if (!dictUsers.ContainsKey(userId)) {
               dictUsers[userId] = Context.ConnectionId;
           }
           Console.WriteLine($"{userId}登录成功,ConnectionId={Context.ConnectionId}");
           //向所有用户发送当前在线的用户列表
           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 {
               //如果当前用户不在线,正常是保存数据库,等上线时加载,暂时不做处理
           }
       }

/// <summary>
       /// 退出功能,当客户端退出时调用
       /// </summary>
       /// <param name="userId"></param>
       public void Logout(string userId)
       {
           if (dictUsers.ContainsKey(userId))
           {
               dictUsers.Remove(userId);
           }
           Console.WriteLine($"{userId}退出成功,ConnectionId={Context.ConnectionId}");
       }
   }
}

3. 注册服务和路由

聊天类创建成功后,需要配置服务注入和路由,在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服务
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 类或从服务器上自己的代码调用其方法;由 SignalR Hubs 管道为你完成的所有操作。 SignalR 每次需要处理中心操作(例如客户端连接、断开连接或向服务器发出方法调用时)时,SignalR 都会创建 Hub 类的新实例。

由于 Hub 类的实例是暂时性的,因此无法使用它们来维护从一个方法调用到下一个方法的状态。 每当服务器从客户端收到方法调用时,中心类的新实例都会处理消息。 若要通过多个连接和方法调用来维护状态,请使用一些其他方法(例如数据库)或 Hub 类上的静态变量,或者不派生自 Hub的其他类。 如果在内存中保留数据,请使用 Hub 类上的静态变量等方法,则应用域回收时数据将丢失。

如果要从在 Hub 类外部运行的代码将消息发送到客户端,则无法通过实例化 Hub 类实例来执行此操作,但可以通过获取对 Hub 类的 SignalR 上下文对象的引用来执行此操作。

注意:ChatHub每次调用都是一个新的实例,所以不可以有私有属性或变量,不可以保存对像的值,所以如果需要记录一些持久保存的值,则可以采用静态变量,或者中心以外的对象。

SignalR客户端

1. 安装SignalR客户端依赖库

客户端如果要调用SignalR的值,需要通过NuGet包管理器,安装SignalR客户端,如下所示:

WPF+ASP.NET SignalR实现简易在线聊天功能的示例代码

2. 客户端消息接收发送

在客户端实现消息的接收和发送,主要通过HubConntection实现,核心代码,如下所示:

namespace SignalRClient
{
   public class ChatViewModel:ObservableObject
   {
       #region 属性及构造函数

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.监听
           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("发送的消息为空");
               return;
           }
           if (string.IsNullOrEmpty(this.TargetUserName)) {
               MessageBox.Show("发送的目标用户为空");
               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>
       /// 监听
       /// </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;
       }
   }
}

运行示例

在示例中,需要同时启动服务端和客户端,所以以多项目方式启动,如下所示:

WPF+ASP.NET SignalR实现简易在线聊天功能的示例代码

运行成功后,服务端以ASP.NET Web API的方式呈现,如下所示:

WPF+ASP.NET SignalR实现简易在线聊天功能的示例代码

客户端需要同时运行两个,所以在调试运行启动一个客户端后,还要在Debug目录下,手动双击客户端,再打开一个,并进行登录,如下所示:

WPF+ASP.NET SignalR实现简易在线聊天功能的示例代码

WPF+ASP.NET SignalR实现简易在线聊天功能的示例代码

系统运行时,后台日志输出如下所示:

WPF+ASP.NET SignalR实现简易在线聊天功能的示例代码

来源:https://www.cnblogs.com/hsiang/p/16667826.html

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com