翼度科技»论坛 编程开发 .net 查看内容

WPF+ASP.NET SignalR实现动态折线图

5

主题

5

帖子

15

积分

新手上路

Rank: 1

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

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

SignalR做了什么?

传统HTTP采用的是大家熟知的“拉模式”,即客户端发出的每次请求,服务端都是被动处理。此场景下客户端是老大,很显然只有一方主动,操作与处理起来就没那么完美。
为了能让服务端也能主动,html5的出现让这种变得可能,大家知道html5中有两种主动模式。第一种叫做websockect,WebSockets是Html5提供的新的API,可以在Web网页与服务器端间建立Socket连接,它是基于tcp模式的双工通讯。还有一种叫做SSE,也就是客户端来订阅服务器的一种事件模型。
在html5出来之前,如果要做到服务器主动,我们只能采用变相的longpool和iframe流勉强实现。
SignalR对上面四种方案进行了高度的封装,也就是说signalR会在这四种技术中根据浏览器和服务器设置采取最优的一种模式。

封装与集成

对于.NET开发者的福音,.NET平台为我们提供了一种简洁高效智能的实时信息交互技术->SignalR,它集成了上述数种技术,并能根据配置自动或手动选择其最佳应用。

SignalR用途

SignalR 提供了一个简单的 API,用于创建服务器到客户端远程过程调用 (RPC) ,该调用客户端浏览器 (和其他客户端平台中的 JavaScript 函数) 服务器端 .NET 代码。 SignalR 还包括用于连接管理的 API (,例如连接和断开连接事件) ,以及分组连接。
虽然聊天通常被用作示例,但你可以做更多的事情。每当用户刷新网页以查看新数据时,或者该网页实施 Ajax 长轮询以检索新数据时,它都是使用 SignalR 的候选者。SignalR 还支持需要从服务器进行高频更新的全新类型的应用,例如实时游戏。
 
官方网址和源码

 
官方网址:https://dotnet.microsoft.com/zh-cn/apps/aspnet/signalr
微软API文档:https://learn.microsoft.com/zh-cn/aspnet/signalr/overview/getting-started/introduction-to-signalr
GitHub网址:https://github.com/SignalR

示例截图

本示例主要实现一个动态折线图功能,主要分为服务端和客户端两部分,示例如下所示:

服务端项目创建

 
1. 创建一个Web服务端程序(以ASP.NET WebApi为例),默认情况下SignalR已经作为项目框架的一部分而存在,所以不需要安装,直接使用即可。通过项目--依赖性--框架--Microsoft.AspNetCore.App可以查看
2. 创建ChatHub,继承Hub基类,作为后台连接管理的中心
  1. 1 using Microsoft.AspNetCore.SignalR;
  2. 2
  3. 3 namespace DemoSignalR.Server.Chat
  4. 4 {
  5. 5     public class ChatHub : Hub
  6. 6     {
  7. 7         #region 连接和断开连接
  8. 8
  9. 9         public override async Task OnConnectedAsync()
  10. 10         {
  11. 11             var connId = Context.ConnectionId;
  12. 12             Console.WriteLine($"{connId} 已连接");
  13. 13             await base.OnConnectedAsync();
  14. 14         }
  15. 15
  16. 16         public void StartNotify(string type)
  17. 17         {
  18. 18             if (type == "1")
  19. 19             {
  20. 20
  21. 21             }
  22. 22             else if (type == "2")
  23. 23             {
  24. 24
  25. 25             };
  26. 26
  27. 27         }
  28. 28
  29. 29         public override async Task OnDisconnectedAsync(Exception ex)
  30. 30         {
  31. 31             //如果断开连接
  32. 32             var connId = Context.ConnectionId;
  33. 33             Console.WriteLine($"{connId} 已断开");
  34. 34             await base.OnDisconnectedAsync(ex);
  35. 35         }
  36. 36
  37. 37         #endregion
  38. 38     }
  39. 39 }
复制代码
SignalR服务端业务集成

在实际业务中,存在各种需要后台通知的功能,根据不同的业务,可以采用不同的通知触发方式:
1. 在调用接口时触发后台通知
  1. 1 using DemoSignalR.Server.Chat;
  2. 2 using Microsoft.AspNetCore.Mvc;
  3. 3 using Microsoft.AspNetCore.SignalR;
  4. 4
  5. 5 namespace DemoSignalR.Server.Controllers
  6. 6 {
  7. 7     [ApiController]
  8. 8     [Route("[controller]")]
  9. 9     public class TestWebApiController : ControllerBase
  10. 10     {
  11. 11
  12. 12
  13. 13         private readonly ILogger<TestWebApiController> _logger;
  14. 14
  15. 15         private IHubContext<ChatHub> _context;
  16. 16
  17. 17         public TestWebApiController(ILogger<TestWebApiController> logger, IHubContext<ChatHub> context)
  18. 18         {
  19. 19             _logger = logger;
  20. 20             _context = context;
  21. 21         }
  22. 22
  23. 23         [HttpGet]
  24. 24         public void GetTestA(string Name)
  25. 25         {
  26. 26             string info = $"当前接收到的信息为:{Name},{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}";
  27. 27             _context.Clients.All.SendAsync("", info);
  28. 28         }
  29. 29
  30. 30
  31. 31     }
  32. 32 }
复制代码
2. 定时器循环通知
  1. 1 using Microsoft.AspNetCore.SignalR;
  2. 2 using System.Timers;
  3. 3
  4. 4 namespace DemoSignalR.Server.Chat
  5. 5 {
  6. 6     public class TestChatInfo
  7. 7     {
  8. 8         private IHubContext<ChatHub> _context;
  9. 9
  10. 10         private System.Timers.Timer _timer;
  11. 11
  12. 12         private readonly static object locker = new object();//锁对象
  13. 13
  14. 14         public static TestChatInfo instance;//当前实例
  15. 15
  16. 16         private readonly ILogger _logger;
  17. 17
  18. 18         private int _index = 0;
  19. 19
  20. 20         private TestChatInfo(IHubContext<ChatHub> _context, ILogger _logger)
  21. 21         {
  22. 22             this._context = _context;
  23. 23             this._logger = _logger;
  24. 24             //定义定时器
  25. 25             _timer = new System.Timers.Timer(100);
  26. 26             _timer.AutoReset = true;
  27. 27             _timer.Enabled = true;
  28. 28             _timer.Elapsed += Timer_Elapsed;
  29. 29             _timer.Start();
  30. 30         }
  31. 31
  32. 32         private void Timer_Elapsed(object? sender, ElapsedEventArgs e)
  33. 33         {
  34. 34             //业务逻辑判断
  35. 35             var obj = new TestValue();
  36. 36             obj.Index = this._index;
  37. 37             obj.Value = DateTime.Now.Millisecond % 100;
  38. 38             _context.Clients.All.SendAsync("RefreshInfos", obj);
  39. 39             this._index++;
  40. 40         }
  41. 41
  42. 42         /// <summary>
  43. 43         /// 注册,即初始化单例实例
  44. 44         /// </summary>
  45. 45         /// <param name="context"></param>
  46. 46         /// <param name="reviewTaskService"></param>
  47. 47         /// <param name="sysParamService"></param>
  48. 48         public static void Register(IHubContext<ChatHub> context, ILogger logger)
  49. 49         {
  50. 50             lock (locker)
  51. 51             {
  52. 52                 if (instance == null)
  53. 53                 {
  54. 54                     lock (locker)
  55. 55                     {
  56. 56                         instance = new TestChatInfo(context, logger);
  57. 57                     }
  58. 58                 }
  59. 59             }
  60. 60         }
  61. 61
  62. 62     }
  63. 63
  64. 64     public class TestValue
  65. 65     {
  66. 66         private int index;
  67. 67         public int Index { get { return index; } set { index = value; } }
  68. 68
  69. 69         private float value;
  70. 70         public float Value { get { return value; } set { this.value = value; } }
  71. 71     }
  72. 72 }
复制代码
 
SignalR服务端配置

 SignalR服务端配置主要分成三步:
1. 添加SignalR服务
2. 映射SignalR路由
3. 注册单例后台通知服务(如果其他方式,可省略)
  1. 1 using DemoSignalR.Server.Chat;
  2. 2 using Microsoft.AspNetCore.SignalR;
  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 builder.Services.AddLogging(logging => logging.AddConsole());
  15. 15
  16. 16 var app = builder.Build();
  17. 17
  18. 18 // Configure the HTTP request pipeline.
  19. 19 if (app.Environment.IsDevelopment())
  20. 20 {
  21. 21     app.UseSwagger();
  22. 22     app.UseSwaggerUI();
  23. 23 }
  24. 24 app.UseRouting();
  25. 25 app.UseHttpsRedirection();
  26. 26
  27. 27 app.UseAuthorization();
  28. 28
  29. 29
  30. 30 //在Use中注册单例实例
  31. 31 app.Use(async (context, next) =>
  32. 32 {
  33. 33     var hubContext = context.RequestServices.GetRequiredService<IHubContext<ChatHub>>();
  34. 34     //var logger = context.RequestServices.GetRequiredService<ILogger>();
  35. 35     TestChatInfo.Register(hubContext, null);//调用静态方法注册
  36. 36     if (next != null)
  37. 37     {
  38. 38         await next.Invoke();
  39. 39     }
  40. 40 });
  41. 41
  42. 42 app.MapControllers();
  43. 43
  44. 44 //2.映射路由
  45. 45 app.UseEndpoints(endpoints => {
  46. 46     endpoints.MapHub<ChatHub>("/chat");
  47. 47 });
  48. 48
  49. 49 app.Run();
复制代码
客户端项目创建

1. 创建WPF项目
2. 通过NuGet包管理器安装SignalR客户端
3. 创建SignalR状态管理,主要管理SignalR的连接,关闭,重连等操作。
  1. 1 using Microsoft.AspNetCore.SignalR.Client;
  2. 2 using System;
  3. 3 using System.Collections.Generic;
  4. 4 using System.Configuration;
  5. 5 using System.Linq;
  6. 6 using System.Text;
  7. 7 using System.Threading.Tasks;
  8. 8
  9. 9 namespace DemoSignalR.Client
  10. 10 {
  11. 11     internal class SignalRClient
  12. 12     {
  13. 13         private readonly HubConnection hubConnection;
  14. 14
  15. 15         public HubConnection HubConnection
  16. 16         {
  17. 17             get { return hubConnection; }
  18. 18         }
  19. 19
  20. 20         public SignalRClient()
  21. 21         {
  22. 22             var server = ConfigurationManager.AppSettings["Server"].ToString();
  23. 23             hubConnection = new HubConnectionBuilder().WithUrl($"{server}/chat").WithAutomaticReconnect().Build();
  24. 24             hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(5);
  25. 25         }
  26. 26
  27. 27         public virtual void Listen()
  28. 28         {
  29. 29
  30. 30         }
  31. 31
  32. 32         public async void Start()
  33. 33         {
  34. 34             try
  35. 35             {
  36. 36                 await hubConnection.StartAsync();
  37. 37
  38. 38             }
  39. 39             catch (Exception e)
  40. 40             {
  41. 41
  42. 42             }
  43. 43         }
  44. 44     }
  45. 45 }
复制代码
客户端业务逻辑处理

1. 根据不同的业务逻辑分别监听不同的通知事件。
2. 示例详见源码
  1. 1 using Microsoft.AspNetCore.SignalR.Client;
  2. 2 using System;
  3. 3 using System.Collections.Generic;
  4. 4 using System.Linq;
  5. 5 using System.Text;
  6. 6 using System.Threading.Tasks;
  7. 7
  8. 8 namespace DemoSignalR.Client
  9. 9 {
  10. 10     internal class TestSignalRClient : SignalRClient
  11. 11     {
  12. 12         public Action<object> RefreshInfos;
  13. 13
  14. 14         public Action<string> Reconnected;
  15. 15
  16. 16         public TestSignalRClient() : base()
  17. 17         {
  18. 18         }
  19. 19
  20. 20         public override void Listen()
  21. 21         {
  22. 22             HubConnection.On<object>("RefreshInfos", (obj) =>
  23. 23             {
  24. 24                 //
  25. 25                 if (obj != null)
  26. 26                 {
  27. 27                     Console.WriteLine("收到数据");
  28. 28                     //发送消息
  29. 29                     if (RefreshInfos != null)
  30. 30                     {
  31. 31                         RefreshInfos(obj);
  32. 32                     }
  33. 33                 }
  34. 34             });
  35. 35             HubConnection.Reconnected += HubConnection_Reconnected;
  36. 36         }
  37. 37
  38. 38         private Task HubConnection_Reconnected(string arg)
  39. 39         {
  40. 40             return Task.Run(() =>
  41. 41             {
  42. 42                 if (Reconnected != null)
  43. 43                 {
  44. 44                     Reconnected(arg);
  45. 45                 }
  46. 46             });
  47. 47         }
  48. 48
  49. 49         public virtual void StartNotify(string condition)
  50. 50         {
  51. 51             HubConnection.SendAsync("StartNotify", condition);
  52. 52             Console.WriteLine($"开始通过知{condition}");
  53. 53         }
  54. 54     }
  55. 55 }
复制代码
 
SignalR需要注意事项

你不会实例化 Hub 类或从服务器上自己的代码调用其方法;由 SignalR Hubs 管道为你完成的所有操作。 SignalR 每次需要处理中心操作(例如客户端连接、断开连接或向服务器发出方法调用时)时,SignalR 都会创建 Hub 类的新实例。
由于 Hub 类的实例是暂时性的,因此无法使用它们来维护从一个方法调用到下一个方法的状态。 每当服务器从客户端收到方法调用时,中心类的新实例都会处理消息。 若要通过多个连接和方法调用来维护状态,请使用一些其他方法(例如数据库)或 Hub 类上的静态变量,或者不派生自 Hub的其他类。 如果在内存中保留数据,请使用 Hub 类上的静态变量等方法,则应用域回收时数据将丢失。
如果要从在 Hub 类外部运行的代码将消息发送到客户端,则无法通过实例化 Hub 类实例来执行此操作,但可以通过获取对 Hub 类的 SignalR 上下文对象的引用来执行此操作。
注意:ChatHub每次调用都是一个新的实例,所以不可以有私有属性或变量,不可以保存对像的值,所以如果需要记录一些持久保存的值,则可以采用静态变量,或者中心以外的对象。
关于源码

本示例中相关源码,已上传至gitee(码云),链接如下:
https://gitee.com/ahsiang/demo-signal-r

 

 
备注

以上就是WPF+ASP.NET SignalR实现动态折线图的全部内容,关于SignalR的应用,这只是一个简单的入门示例,希望可以抛砖引玉,一起学习,共同进步。学习编程,从关注【老码识途】开始!!!

来源:https://www.cnblogs.com/hsiang/archive/2023/01/02/17020265.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

x

举报 回复 使用道具