WPF+ASP.NET SignalR实现动态折线图
在实际业务中,当后台数据发生变化,客户端能够实时的收到通知,而不是由用户主动的进行页面刷新才能查看,这将是一个非常人性化的设计。有没有那么一种场景,后台数据明明已经发生变化了,前台却因为没有及时刷新,而导致页面显示的数据与实际存在差异,从而造成错误的判断。那么如何才能在后台数据变更时及时通知客户端呢?本文以一个简单的动态折线图示例,简述如何通过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 using Microsoft.AspNetCore.SignalR; 2 3 namespace DemoSignalR.Server.Chat 4 { 5 public class ChatHub : Hub 6 { 7 #region 连接和断开连接 8 9 public override async Task OnConnectedAsync() 10 { 11 var connId = Context.ConnectionId; 12 Console.WriteLine($"{connId} 已连接"); 13 await base.OnConnectedAsync(); 14 } 15 16 public void StartNotify(string type) 17 { 18 if (type == "1") 19 { 20 21 } 22 else if (type == "2") 23 { 24 25 }; 26 27 } 28 29 public override async Task OnDisconnectedAsync(Exception ex) 30 { 31 //如果断开连接 32 var connId = Context.ConnectionId; 33 Console.WriteLine($"{connId} 已断开"); 34 await base.OnDisconnectedAsync(ex); 35 } 36 37 #endregion 38 } 39 }
SignalR服务端业务集成
在实际业务中,存在各种需要后台通知的功能,根据不同的业务,可以采用不同的通知触发方式:
1. 在调用接口时触发后台通知
1 using DemoSignalR.Server.Chat; 2 using Microsoft.AspNetCore.Mvc; 3 using Microsoft.AspNetCore.SignalR; 4 5 namespace DemoSignalR.Server.Controllers 6 { 7 [ApiController] 8 [Route("[controller]")] 9 public class TestWebApiController : ControllerBase 10 { 11 12 13 private readonly ILogger<TestWebApiController> _logger; 14 15 private IHubContext<ChatHub> _context; 16 17 public TestWebApiController(ILogger<TestWebApiController> logger, IHubContext<ChatHub> context) 18 { 19 _logger = logger; 20 _context = context; 21 } 22 23 [HttpGet] 24 public void GetTestA(string Name) 25 { 26 string info = $"当前接收到的信息为:{Name},{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"; 27 _context.Clients.All.SendAsync("", info); 28 } 29 30 31 } 32 }
2. 定时器循环通知
1 using Microsoft.AspNetCore.SignalR; 2 using System.Timers; 3 4 namespace DemoSignalR.Server.Chat 5 { 6 public class TestChatInfo 7 { 8 private IHubContext<ChatHub> _context; 9 10 private System.Timers.Timer _timer; 11 12 private readonly static object locker = new object();//锁对象 13 14 public static TestChatInfo instance;//当前实例 15 16 private readonly ILogger _logger; 17 18 private int _index = 0; 19 20 private TestChatInfo(IHubContext<ChatHub> _context, ILogger _logger) 21 { 22 this._context = _context; 23 this._logger = _logger; 24 //定义定时器 25 _timer = new System.Timers.Timer(100); 26 _timer.AutoReset = true; 27 _timer.Enabled = true; 28 _timer.Elapsed += Timer_Elapsed; 29 _timer.Start(); 30 } 31 32 private void Timer_Elapsed(object? sender, ElapsedEventArgs e) 33 { 34 //业务逻辑判断 35 var obj = new TestValue(); 36 obj.Index = this._index; 37 obj.Value = DateTime.Now.Millisecond % 100; 38 _context.Clients.All.SendAsync("RefreshInfos", obj); 39 this._index++; 40 } 41 42 /// <summary> 43 /// 注册,即初始化单例实例 44 /// </summary> 45 /// <param name="context"></param> 46 /// <param name="reviewTaskService"></param> 47 /// <param name="sysParamService"></param> 48 public static void Register(IHubContext<ChatHub> context, ILogger logger) 49 { 50 lock (locker) 51 { 52 if (instance == null) 53 { 54 lock (locker) 55 { 56 instance = new TestChatInfo(context, logger); 57 } 58 } 59 } 60 } 61 62 } 63 64 public class TestValue 65 { 66 private int index; 67 public int Index { get { return index; } set { index = value; } } 68 69 private float value; 70 public float Value { get { return value; } set { this.value = value; } } 71 } 72 }
SignalR服务端配置
SignalR服务端配置主要分成三步:
1. 添加SignalR服务
2. 映射SignalR路由
3. 注册单例后台通知服务(如果其他方式,可省略)
1 using DemoSignalR.Server.Chat; 2 using Microsoft.AspNetCore.SignalR; 3 4 var builder = WebApplication.CreateBuilder(args); 5 6 // Add services to the container. 7 8 builder.Services.AddControllers(); 9 // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 10 builder.Services.AddEndpointsApiExplorer(); 11 builder.Services.AddSwaggerGen(); 12 //1.添加SignalR服务 13 builder.Services.AddSignalR(); 14 builder.Services.AddLogging(logging => logging.AddConsole()); 15 16 var app = builder.Build(); 17 18 // Configure the HTTP request pipeline. 19 if (app.Environment.IsDevelopment()) 20 { 21 app.UseSwagger(); 22 app.UseSwaggerUI(); 23 } 24 app.UseRouting(); 25 app.UseHttpsRedirection(); 26 27 app.UseAuthorization(); 28 29 30 //在Use中注册单例实例 31 app.Use(async (context, next) => 32 { 33 var hubContext = context.RequestServices.GetRequiredService<IHubContext<ChatHub>>(); 34 //var logger = context.RequestServices.GetRequiredService<ILogger>(); 35 TestChatInfo.Register(hubContext, null);//调用静态方法注册 36 if (next != null) 37 { 38 await next.Invoke(); 39 } 40 }); 41 42 app.MapControllers(); 43 44 //2.映射路由 45 app.UseEndpoints(endpoints => { 46 endpoints.MapHub<ChatHub>("/chat"); 47 }); 48 49 app.Run();
客户端项目创建
1. 创建WPF项目
2. 通过NuGet包管理器安装SignalR客户端
3. 创建SignalR状态管理,主要管理SignalR的连接,关闭,重连等操作。
1 using Microsoft.AspNetCore.SignalR.Client; 2 using System; 3 using System.Collections.Generic; 4 using System.Configuration; 5 using System.Linq; 6 using System.Text; 7 using System.Threading.Tasks; 8 9 namespace DemoSignalR.Client 10 { 11 internal class SignalRClient 12 { 13 private readonly HubConnection hubConnection; 14 15 public HubConnection HubConnection 16 { 17 get { return hubConnection; } 18 } 19 20 public SignalRClient() 21 { 22 var server = ConfigurationManager.AppSettings["Server"].ToString(); 23 hubConnection = new HubConnectionBuilder().WithUrl($"{server}/chat").WithAutomaticReconnect().Build(); 24 hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(5); 25 } 26 27 public virtual void Listen() 28 { 29 30 } 31 32 public async void Start() 33 { 34 try 35 { 36 await hubConnection.StartAsync(); 37 38 } 39 catch (Exception e) 40 { 41 42 } 43 } 44 } 45 }
客户端业务逻辑处理
1. 根据不同的业务逻辑分别监听不同的通知事件。
2. 示例详见源码
1 using Microsoft.AspNetCore.SignalR.Client; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 namespace DemoSignalR.Client 9 { 10 internal class TestSignalRClient : SignalRClient 11 { 12 public Action<object> RefreshInfos; 13 14 public Action<string> Reconnected; 15 16 public TestSignalRClient() : base() 17 { 18 } 19 20 public override void Listen() 21 { 22 HubConnection.On<object>("RefreshInfos", (obj) => 23 { 24 // 25 if (obj != null) 26 { 27 Console.WriteLine("收到数据"); 28 //发送消息 29 if (RefreshInfos != null) 30 { 31 RefreshInfos(obj); 32 } 33 } 34 }); 35 HubConnection.Reconnected += HubConnection_Reconnected; 36 } 37 38 private Task HubConnection_Reconnected(string arg) 39 { 40 return Task.Run(() => 41 { 42 if (Reconnected != null) 43 { 44 Reconnected(arg); 45 } 46 }); 47 } 48 49 public virtual void StartNotify(string condition) 50 { 51 HubConnection.SendAsync("StartNotify", condition); 52 Console.WriteLine($"开始通过知{condition}"); 53 } 54 } 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的应用,这只是一个简单的入门示例,希望可以抛砖引玉,一起学习,共同进步。学习编程,从关注【老码识途】开始!!!
作者:老码识途
出处:http://www.cnblogs.com/hsiang/
本文版权归作者和博客园共有,写文不易,支持原创,欢迎转载【点赞】,转载请保留此段声明,且在文章页面明显位置给出原文连接,谢谢。
关注个人公众号,定时同步更新技术及职场文章