WPF+ASP.NETSignalR实现动态折线图的绘制

在实际业务中,当后台数据发生变化,客户端能够实时的收到通知,而不是由用户主动的进行页面刷新才能查看,这将是一个非常人性化的设计。有没有那么一种场景,后台数据明明已经发生变化了,前台却因为没有及时刷新,而导致页面显示的数据与实际存在差异,从而造成错误的判断。那么如何才能在后台数据变更时及时通知客户端呢?本文以一个简单的动态折线图示例,简述如何通过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基类,作为后台连接管理的中心

using Microsoft.AspNetCore.SignalR;

namespace DemoSignalR.Server.Chat
{
  public class ChatHub : Hub
  {
      #region 连接和断开连接

      public override async Task OnConnectedAsync()
      {
          var connId = Context.ConnectionId;
          Console.WriteLine($"{connId} 已连接");
          await base.OnConnectedAsync();
      }

      public void StartNotify(string type)
      {
          if (type == "1")
          {

          }
          else if (type == "2")
          {

          };

      }

      public override async Task OnDisconnectedAsync(Exception ex)
      {
          //如果断开连接
          var connId = Context.ConnectionId;
          Console.WriteLine($"{connId} 已断开");
          await base.OnDisconnectedAsync(ex);
      }

      #endregion
  }
}

 

SignalR服务端业务集成

在实际业务中,存在各种需要后台通知的功能,根据不同的业务,可以采用不同的通知触发方式:

1.在调用接口时触发后台通知

using DemoSignalR.Server.Chat;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

namespace DemoSignalR.Server.Controllers
{
  [ApiController]
  [Route("[controller]")]
  public class TestWebApiController : ControllerBase
  {


      private readonly ILogger<TestWebApiController> _logger;

      private IHubContext<ChatHub> _context;

      public TestWebApiController(ILogger<TestWebApiController> logger, IHubContext<ChatHub> context)
      {
          _logger = logger;
          _context = context;
      }

      [HttpGet]
      public void GetTestA(string Name)
      {
          string info = $"当前接收到的信息为:{Name},{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}";
          _context.Clients.All.SendAsync("", info);
      }


  }
}

2.定时器循环通知

using Microsoft.AspNetCore.SignalR;
using System.Timers;

namespace DemoSignalR.Server.Chat
{
  public class TestChatInfo
  {
      private IHubContext<ChatHub> _context;

      private System.Timers.Timer _timer;

      private readonly static object locker = new object();//锁对象

      public static TestChatInfo instance;//当前实例

      private readonly ILogger _logger;

      private int _index = 0;

      private TestChatInfo(IHubContext<ChatHub> _context, ILogger _logger)
      {
          this._context = _context;
          this._logger = _logger;
          //定义定时器
          _timer = new System.Timers.Timer(100);
          _timer.AutoReset = true;
          _timer.Enabled = true;
          _timer.Elapsed += Timer_Elapsed;
          _timer.Start();
      }

      private void Timer_Elapsed(object? sender, ElapsedEventArgs e)
      {
          //业务逻辑判断
          var obj = new TestValue();
          obj.Index = this._index;
          obj.Value = DateTime.Now.Millisecond % 100;
          _context.Clients.All.SendAsync("RefreshInfos", obj);
          this._index++;
      }

      /// <summary>
      /// 注册,即初始化单例实例
      /// </summary>
      /// <param name="context"></param>
      /// <param name="reviewTaskService"></param>
      /// <param name="sysParamService"></param>
      public static void Register(IHubContext<ChatHub> context, ILogger logger)
      {
          lock (locker)
          {
              if (instance == null)
              {
                  lock (locker)
                  {
                      instance = new TestChatInfo(context, logger);
                  }
              }
          }
      }

  }

  public class TestValue
  {
      private int index;
      public int Index { get { return index; } set { index = value; } }

      private float value;
      public float Value { get { return value; } set { this.value = value; } }
  }
}

 

SignalR服务端配置

SignalR服务端配置主要分成三步:

1.添加SignalR服务

2.映射SignalR路由

3.注册单例后台通知服务(如果其他方式,可省略)

using DemoSignalR.Server.Chat;
using Microsoft.AspNetCore.SignalR;

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();
builder.Services.AddLogging(logging => logging.AddConsole());

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
  app.UseSwagger();
  app.UseSwaggerUI();
}
app.UseRouting();
app.UseHttpsRedirection();

app.UseAuthorization();


//在Use中注册单例实例
app.Use(async (context, next) =>
{
  var hubContext = context.RequestServices.GetRequiredService<IHubContext<ChatHub>>();
  //var logger = context.RequestServices.GetRequiredService<ILogger>();
  TestChatInfo.Register(hubContext, null);//调用静态方法注册
  if (next != null)
  {
      await next.Invoke();
  }
});

app.MapControllers();

//2.映射路由
app.UseEndpoints(endpoints => {
  endpoints.MapHub<ChatHub>("/chat");
});

app.Run();

 

客户端项目创建

1.创建WPF项目

2.通过NuGet包管理器安装SignalR客户端

3.创建SignalR状态管理,主要管理SignalR的连接,关闭,重连等操作。

using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoSignalR.Client
{
  internal class SignalRClient
  {
      private readonly HubConnection hubConnection;

      public HubConnection HubConnection
      {
          get { return hubConnection; }
      }

      public SignalRClient()
      {
          var server = ConfigurationManager.AppSettings["Server"].ToString();
          hubConnection = new HubConnectionBuilder().WithUrl($"{server}/chat").WithAutomaticReconnect().Build();
          hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(5);
      }

      public virtual void Listen()
      {

      }

      public async void Start()
      {
          try
          {
              await hubConnection.StartAsync();

          }
          catch (Exception e)
          {

          }
      }
  }
}

 

客户端业务逻辑处理

1.根据不同的业务逻辑分别监听不同的通知事件。

2.示例详见源码

using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoSignalR.Client
{
  internal class TestSignalRClient : SignalRClient
  {
      public Action<object> RefreshInfos;

      public Action<string> Reconnected;

      public TestSignalRClient() : base()
      {
      }

      public override void Listen()
      {
          HubConnection.On<object>("RefreshInfos", (obj) =>
          {
              //
              if (obj != null)
              {
                  Console.WriteLine("收到数据");
                  //发送消息
                  if (RefreshInfos != null)
                  {
                      RefreshInfos(obj);
                  }
              }
          });
          HubConnection.Reconnected += HubConnection_Reconnected;
      }

      private Task HubConnection_Reconnected(string arg)
      {
          return Task.Run(() =>
          {
              if (Reconnected != null)
              {
                  Reconnected(arg);
              }
          });
      }

      public virtual void StartNotify(string condition)
      {
          HubConnection.SendAsync("StartNotify", condition);
          Console.WriteLine($"开始通过知{condition}");
      }
  }
}

 

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实现动态折线图的绘制的详细内容,更多关于WPF绘制动态折线图的资料请关注编程宝库其它相关文章!

 实践过程效果代码public partial class Form1 : Form{ public Form1() { InitializeComponent(); } public st ...