WPF+ASP.NET SignalR实现后台通知功能的示例代码
作者:小六公子 发布时间:2021-10-11 04:49:45
在实际业务中,当后台数据发生变化,客户端能够实时的收到通知,而不是由用户主动的进行页面刷新才能查看,这将是一个非常人性化的设计。比如数字化大屏,并没有人工的干预,而是自动的刷新数据,那如何才能实现数据的实时刷新呢?本文以一个简单示例,简述如何通过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 服务器的客户端发送消息的核心抽象,负责客户端的连接和断开。如下所示:
using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Chat
{
public class ChatHub:Hub
{
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);
}
}
}
Worker实例为一个单例对象,常驻内容,实时监听数据变化,并通过ChatHub上下文(IHubContext<ChatHub>)获取连接信息,然后发送消息,如下所示:
using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Chat
{
public class Worker
{
public static Worker Instance;
private static readonly object locker=new object();
private IHubContext<ChatHub> context;
private System.Timers.Timer timer;
public Worker(IHubContext<ChatHub> context) {
this.context = context;
timer= new System.Timers.Timer(500);//单位毫秒
timer.Enabled=true;
timer.AutoReset=true;//自动重新
timer.Elapsed += Timer_Elapsed;
timer.Start();
}
private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
{
//模拟数据,一般情况下,从数据库获取,然后通知到客户端
Dictionary<string, object> data = new Dictionary<string, object>();
var online = new Random().Next(0, 100);
var male = Math.Floor(new Random().NextSingle() * online);
var female = online - male;
data["online"]=online;
data["male"] =male;
data["female"] = female;
context.Clients.All.SendAsync("Data",data);
}
public static void Register(IHubContext<ChatHub> context)
{
if (Instance == null)
{
lock (locker)
{
if (Instance == null)
{
Instance = new Worker(context);
}
}
}
}
}
}
注意:此处发送数据的是Data方法,客户端必须监听Data方法,才能接收数据。
如何创建单例对象呢,中心对象上下文不能自己创建,必须要和ChatHub通过注入方式的上下文是同一个,不然无法获取客户端连接信息。在项目启动时,通过中间件的方式创建,如下所示:
using Microsoft.AspNetCore.SignalR;
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();
//在Use中注册单例实例
app.Use(async (context, next) =>
{
var hubContext = context.RequestServices
.GetRequiredService<IHubContext<ChatHub>>();
Worker.Register(hubContext);//调用静态方法注册
if (next != null)
{
await next.Invoke();
}
});
app.MapControllers();
//2.映射路由
app.UseEndpoints(endpoints => {
endpoints.MapHub<ChatHub>("/chat");
});
app.Run();
客户端
客户端主要是连接服务器,然后监听服务端发送数据的方法即可,如下所示:
namespace SignalRClient
{
public class ShowDataViewModel : ObservableObject
{
#region 属性及构造函数
private int online;
public int Online
{
get { return online; }
set { SetProperty(ref online, value); }
}
private int male;
public int Male
{
get { return male; }
set { SetProperty(ref male, value); }
}
private int female;
public int Female
{
get { return female; }
set { SetProperty(ref female, value); }
}
private HubConnection hubConnection;
public ShowDataViewModel()
{
}
#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();
}
#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<Dictionary<string,object>>("Data", ReceiveInfos);
}
/// <summary>
/// 连接
/// </summary>
private async void Link()
{
try
{
await hubConnection.StartAsync();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void ReceiveInfos(Dictionary<string, object> data)
{
if (data == null || data.Count < 1)
{
return;
}
int.TryParse(data["online"]?.ToString(),out int online);
int.TryParse(data["male"]?.ToString(),out int male);
int.TryParse(data["female"]?.ToString(),out int female);
this.Online=online;
this.Male = male;
this.Female=female;
}
}
}
注意:监听Data方法,和服务端发送时保持一致。
运行示例
在示例中,需要同时启动服务端和客户端,所以以多项目方式启动,如下所示:
运行成功后,服务端以ASP.NET Web API的方式呈现,如下所示:
客户端运行如下:
注意:客户端可以有多个,也可以是一个,后台通知消息,会通知到每一个连接的客户端。
来源:https://www.cnblogs.com/hsiang/p/16687365.html


猜你喜欢
- JVM内部结构图Java虚拟机主要分为五个区域:方法区、堆、Java栈、PC寄存器、本地方法栈。下面来看一些关于JVM结构的重要问题。1.哪
- 一、Java 运行时数据区域友情提示:这部分内容可能大部分同学都有一定的了解了,可以跳过直接进入下一小节哈。Java 虚拟机在执行 Java
- 本篇概览在检测人脸数量、位置、性别、口罩等场景时,可以考虑使用百度开放平台提供的web接口,一个web请求就能完成检测得到结果,本篇记录了从
- 本文实例讲述了C#实现把图片转换成二进制以及把二进制转换成图片的方法。分享给大家供大家参考,具体如下:private void button
- 一、概述项目中经常用到倒计时的功能,比如说限时抢购,手机获取验证码等等。而google官方也帮我们封装好了一个类:CountDownTime
- 首先,我们假设这样一个场景:一个ViewPager里面嵌套一个ViewPager,内部滑动方向和外部滑动方向一样时,该怎么解决这一冲突呢?针
- Java实现简单的类似QQ聊天工具,供大家参考,具体内容如下所使用到的知识点:java socket编程之TCP协议java Swing简单
- 在做2048这个游戏时,因为菜单页面还能查看游戏规则,而这些规则又不在同一个页上,所以需要滑动页面实现页面切换,但是仅仅使用unity提供的
- 说到多渠道,这里不得不提一下友盟统计,友盟统计是大家日常开发中常用的渠道统计工具,而我们的打包方法就是基于友盟统计实施的。按照友盟官方文档说
- 通常我们遇到的图片缩放需求,都是图片基于屏幕自适应后,进行缩放和移动,且图片最小只能是自适应的大小。最近遇到一个需求,要求图片只能在屏幕内缩
- 在Mms中每个Thread都有其相应的联系人,但是threads表中并没有直接保存联系人的信息(号码或名字),而是保存一个叫做recipie
- 一、万能的字符串当然,任何时候都可以使用字符串作为属性的值,从配置文件里读取出来,如下:配置文件内容为:pkslow.admin=larry
- 我们知道android是基于Looper消息循环的系统,我们通过Handler向Looper包含的MessageQueue投递Message
- 汉诺塔的规则是:一共三根柱子,一根柱子从上到
- 1,实现方法一:通过给当前界面布局文件的父layout设置点击事件(相当于给整个Activity设置点击事件),在事件里进行键盘隐藏<
- 在java的JFrame内通过创建匿名对象的方式做登录界面package com.sxt;import java.awt.Container
- Android客户端请求服务器端的详细解释1. Android客户端与服务器端通信方式: Android与服务器通信通常采用HTTP通信方式
- spring cloud我想做成一个系列,所以spring cloud+eureka后面会慢慢说到的,有兴趣的小伙伴可以关注后续!这一节就简
- 在WPF中,TreeView默认情况是不支持右键选定的,也就是说,当右键点击某节点时,是无法选中该节点的。当我们想在TreeViewItem
- 下面给大家分享一小段代码给大家介绍C# 输出字符串到文本文件中,具体代码如下所示: public class WriteHelp