WPF+ASP.NET SignalR实现后台通知

打印 上一主题 下一主题

主题 880|帖子 880|积分 2644

在实际业务中,当后台数据发生变化,客户端能够实时的收到通知,而不是由用户主动的进行页面刷新才能查看,这将是一个非常人性化的设计。比如数字化大屏,并没有人工的干预,而是自动的刷新数据,那如何才能实现数据的实时刷新呢?本文以一个简单示例,简述如何通过WPF+ASP.NET SignalR实现消息后台通知以及数据的实时刷新,仅供学习分享使用,如有不足之处,还请指正。

 
 通过上一篇文章的学习,了解了如何通过SignalR实现在线聊天功能,在示例中,我们发现每一次的客户端连接都是一个新的实例对象,所以没有办法在中心对象中存储状态信息,所以为了存储用户列表,我们采用了静态变量的方式。并且在线聊天功能是用户发送一条消息(Chat),然后触发中心对象(ChatHub),转发给另一个用户(SendAsync)。那么如果实现数字化大屏,需要服务端持续的往客户端发送消息,而不是客户端主动触发,应该怎么做呢?这就是本文需要分享的内容。
涉及知识点

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

  • 开发工具:Visual Studio 2022 目标框架:.NET6.0
  • ASP.NET SignalR,一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信,目前新版已支持.NET6.0及以上版本。在本示例中,作为消息通知的服务端。
  • WPF,是微软推出的基于Windows 的用户界面框架,主要用于开发客户端程序。
前提条件

实现服务端持续往客户端发送消息,除了业务上的需求外,还需要满足两个条件:

  • 在服务端有一个常驻内存对象,监听数据变化。
  • 常驻内存对象,可以访问中心对象(ChatHub),能够获取中心对象的所有连接客户端,并发送消息。
满足以上两个条件,才可以实现想要的功能。
服务端

经过以上分析后,服务端分为两方面,核心对象(ChatHub),处理业务对象(Worker)。下面我们逐一说明:
ChatHub 中心是用于向连接到 SignalR 服务器的客户端发送消息的核心抽象,负责客户端的连接和断开。如下所示:
  1. 1 using Microsoft.AspNetCore.SignalR;
  2. 2
  3. 3 namespace SignalRChat.Chat
  4. 4 {
  5. 5     public class ChatHub:Hub
  6. 6     {
  7. 7         public override Task OnConnectedAsync()
  8. 8         {
  9. 9             Console.WriteLine($"ID:{Context.ConnectionId} 已连接");
  10. 10             return base.OnConnectedAsync();
  11. 11         }
  12. 12
  13. 13         public override Task OnDisconnectedAsync(Exception? exception)
  14. 14         {
  15. 15             Console.WriteLine($"ID:{Context.ConnectionId} 已断开");
  16. 16             return base.OnDisconnectedAsync(exception);
  17. 17         }
  18. 18     }
  19. 19 }
复制代码
Worker实例为一个单例对象,常驻内容,实时监听数据变化,并通过ChatHub上下文(IHubContext)获取连接信息,然后发送消息,如下所示:
  1. 1 using Microsoft.AspNetCore.SignalR;
  2. 2
  3. 3 namespace SignalRChat.Chat
  4. 4 {
  5. 5     public class Worker
  6. 6     {
  7. 7         public static Worker Instance;
  8. 8         
  9. 9         private static readonly object locker=new object();
  10. 10
  11. 11         private IHubContext<ChatHub> context;
  12. 12
  13. 13         private System.Timers.Timer timer;
  14. 14
  15. 15         public Worker(IHubContext<ChatHub> context) {
  16. 16             this.context = context;
  17. 17             timer= new System.Timers.Timer(500);//单位毫秒
  18. 18             timer.Enabled=true;
  19. 19             timer.AutoReset=true;//自动重新
  20. 20             timer.Elapsed += Timer_Elapsed;
  21. 21             timer.Start();
  22. 22         }
  23. 23
  24. 24         private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
  25. 25         {
  26. 26             //模拟数据,一般情况下,从数据库获取,然后通知到客户端
  27. 27             Dictionary<string, object> data = new Dictionary<string, object>();
  28. 28             var online = new Random().Next(0, 100);
  29. 29             var male = Math.Floor(new Random().NextSingle() * online);
  30. 30             var female = online - male;
  31. 31             data["online"]=online;
  32. 32             data["male"] =male;
  33. 33             data["female"] = female;
  34. 34             context.Clients.All.SendAsync("Data",data);
  35. 35         }
  36. 36
  37. 37         public static void Register(IHubContext<ChatHub> context)
  38. 38         {
  39. 39             if (Instance == null)
  40. 40             {
  41. 41                 lock (locker)
  42. 42                 {
  43. 43                     if (Instance == null)
  44. 44                     {
  45. 45                         Instance = new Worker(context);
  46. 46                     }
  47. 47                 }
  48. 48             }
  49. 49         }
  50. 50     }
  51. 51 }
复制代码
注意:此处发送数据的是Data方法,客户端必须监听Data方法,才能接收数据。
如何创建单例对象呢,中心对象上下文不能自己创建,必须要和ChatHub通过注入方式的上下文是同一个,不然无法获取客户端连接信息。在项目启动时,通过中间件的方式创建,如下所示:
  1. 1 using Microsoft.AspNetCore.SignalR;
  2. 2 using SignalRChat.Chat;
  3. 3
  4. 4 var builder = WebApplication.CreateBuilder(args);
  5. 5
  6. 6 // Add services to the container.
  7. 7
  8. 8 builder.Services.AddControllers();
  9. 9 // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
  10. 10 builder.Services.AddEndpointsApiExplorer();
  11. 11 builder.Services.AddSwaggerGen();
  12. 12 //1.添加SignalR服务
  13. 13 builder.Services.AddSignalR();
  14. 14 var app = builder.Build();
  15. 15
  16. 16 // Configure the HTTP request pipeline.
  17. 17 if (app.Environment.IsDevelopment())
  18. 18 {
  19. 19     app.UseSwagger();
  20. 20     app.UseSwaggerUI();
  21. 21 }
  22. 22 app.UseRouting();
  23. 23 app.UseHttpsRedirection();
  24. 24
  25. 25 app.UseAuthorization();
  26. 26 //在Use中注册单例实例
  27. 27 app.Use(async (context, next) =>
  28. 28 {
  29. 29     var hubContext = context.RequestServices
  30. 30                             .GetRequiredService<IHubContext<ChatHub>>();
  31. 31     Worker.Register(hubContext);//调用静态方法注册
  32. 32
  33. 33     if (next != null)
  34. 34     {
  35. 35         await next.Invoke();
  36. 36     }
  37. 37 });
  38. 38 app.MapControllers();
  39. 39 //2.映射路由
  40. 40 app.UseEndpoints(endpoints => {
  41. 41     endpoints.MapHub<ChatHub>("/chat");
  42. 42 });
  43. 43
  44. 44 app.Run();
复制代码
客户端

客户端主要是连接服务器,然后监听服务端发送数据的方法即可,如下所示:
  1.   1 namespace SignalRClient
  2.   2 {
  3.   3     public class ShowDataViewModel : ObservableObject
  4.   4     {
  5.   5         #region 属性及构造函数
  6.   6
  7.   7         private int online;
  8.   8
  9.   9         public int Online
  10. 10         {
  11. 11             get { return online; }
  12. 12             set { SetProperty(ref online, value); }
  13. 13         }
  14. 14
  15. 15         private int male;
  16. 16
  17. 17         public int Male
  18. 18         {
  19. 19             get { return male; }
  20. 20             set { SetProperty(ref male, value); }
  21. 21         }
  22. 22
  23. 23
  24. 24         private int female;
  25. 25
  26. 26         public int Female
  27. 27         {
  28. 28             get { return female; }
  29. 29             set { SetProperty(ref female, value); }
  30. 30         }
  31. 31
  32. 32         private HubConnection hubConnection;
  33. 33
  34. 34         public ShowDataViewModel()
  35. 35         {
  36. 36
  37. 37         }
  38. 38
  39. 39         #endregion
  40. 40
  41. 41         #region 命令
  42. 42
  43. 43         private ICommand loadedCommand;
  44. 44
  45. 45         public ICommand LoadedCommand
  46. 46         {
  47. 47             get
  48. 48             {
  49. 49                 if (loadedCommand == null)
  50. 50                 {
  51. 51                     loadedCommand = new RelayCommand<object>(Loaded);
  52. 52                 }
  53. 53                 return loadedCommand;
  54. 54             }
  55. 55         }
  56. 56
  57. 57         private void Loaded(object obj)
  58. 58         {
  59. 59             //1.初始化
  60. 60             InitInfo();
  61. 61             //2.监听
  62. 62             Listen();
  63. 63             //3.连接
  64. 64             Link();
  65. 65         }
  66. 66
  67. 67         #endregion
  68. 68
  69. 69         /// <summary>
  70. 70         /// 初始化Connection对象
  71. 71         /// </summary>
  72. 72         private void InitInfo()
  73. 73         {
  74. 74             hubConnection = new HubConnectionBuilder().WithUrl("https://localhost:7149/chat").WithAutomaticReconnect().Build();
  75. 75             hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(5);
  76. 76         }
  77. 77
  78. 78         /// <summary>
  79. 79         /// 监听
  80. 80         /// </summary>
  81. 81         private void Listen()
  82. 82         {
  83. 83             hubConnection.On<Dictionary<string,object>>("Data", ReceiveInfos);
  84. 84         }
  85. 85
  86. 86         /// <summary>
  87. 87         /// 连接
  88. 88         /// </summary>
  89. 89         private async void Link()
  90. 90         {
  91. 91             try
  92. 92             {
  93. 93                 await hubConnection.StartAsync();
  94. 94             }
  95. 95             catch (Exception ex)
  96. 96             {
  97. 97                 MessageBox.Show(ex.Message);
  98. 98             }
  99. 99         }
  100. 100
  101. 101         private void ReceiveInfos(Dictionary<string, object> data)
  102. 102         {
  103. 103             if (data == null || data.Count < 1)
  104. 104             {
  105. 105                 return;
  106. 106             }
  107. 107             int.TryParse(data["online"]?.ToString(),out int online);
  108. 108             int.TryParse(data["male"]?.ToString(),out int male);
  109. 109             int.TryParse(data["female"]?.ToString(),out int female);
  110. 110             this.Online=online;
  111. 111             this.Male = male;
  112. 112             this.Female=female;
  113. 113         }
  114. 114     }
  115. 115 }
复制代码
注意:监听Data方法,和服务端发送时保持一致。
运行示例

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

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

 
 客户端运行如下:

注意:客户端可以有多个,也可以是一个,后台通知消息,会通知到每一个连接的客户端。
源码下载

关注微信公众号,然后发送信息SignalR即可获取下载链接。如下所示:

 

 
 
备注

以上就是WPF+ASP.NET SignalR实现后台实时通知的全部内容,关于SignalR的应用,实际场景有很多,这只是一个简单的入门示例,希望可以抛砖引玉,一起学习,共同进步。学习编程,从关注【老码识途】开始!!!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

刘俊凯

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表