最近在写基于SignalR的WPF消息提示框,即在WPF客户端右下角当有消息来临时,收到SignalR发来的消息时,就弹个框,WPF使用SignalR之服务器端和客户端写法5分钟介绍,每个示例都会用到服务端,所以有了这篇文章用于记录SignalR服务端和WPF客户端的创建过程。

WPF使用SignalR之服务器端
不废话,直接上代码,下面是SignalR服务器端,是放在 .net core写的web api里面的,这里推荐参考OSharp框架里面关于SignalR模块的写法。
第1步:继承自Hub
/// <summary>
/// MessageHub基类
/// </summary>
public abstract class MessageHub: Hub
{
/// <summary>
/// 初始化一个<see cref="MessageHub"/>类型的新实例
/// </summary>
protected MessageHub(IConnectionUserCache userCache)
{
UserCache = userCache;
}
/// <summary>
/// 获取 通信连接用户缓存
/// </summary>
protected IConnectionUserCache UserCache { get; }
/// <summary>
/// 在与集线器建立新连接时调用。
/// </summary>
/// <returns>一个 <see cref="T:System.Threading.Tasks.Task" /> 表示异步连接的。</returns>
public override async Task OnConnectedAsync()
{
string userName = Context.User.Identity.Name;
if (!string.IsNullOrEmpty(userName))
{
await UserCache.AddConnectionId(userName, Context.ConnectionId);
}
await base.OnConnectedAsync();
}
/// <summary>当终止与集线器的连接时调用。</summary>
/// <returns>一个 <see cref="T:System.Threading.Tasks.Task" /> 表示异步连接的。</returns>
public override async Task OnDisconnectedAsync(Exception exception)
{
string userName = Context.User.Identity.Name;
if (!string.IsNullOrEmpty(userName))
{
await UserCache.RemoveConnectionId(userName, Context.ConnectionId);
}
await base.OnDisconnectedAsync(exception);
}
/// <summary>
/// 加入组
/// </summary>
/// <param name="groupNames">组名</param>
/// <returns></returns>
public virtual async Task AddToGroup(string[] groupNames)
{
foreach (string groupName in groupNames)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
}
/// <summary>
/// 离开组
/// </summary>
/// <param name="groupNames">组名</param>
/// <returns></returns>
public virtual async Task RemoveFromGroup(string[] groupNames)
{
foreach (string groupName in groupNames)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
}
}
}
/// <summary>
/// 支持强类型的MessageHub基类
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class MessageHub<T> : MessageHub where T : class
{
private IHubCallerClients<T> _clients;
/// <summary>
/// 初始化一个<see cref="HisHub"/>类型的新实例
/// </summary>
protected HisHub(IConnectionUserCache userCache)
: base(userCache)
{ }
/// <summary>
/// Gets or sets a <typeparamref name="T" /> that can be used to invoke methods on the clients connected to this hub.
/// </summary>
public new IHubCallerClients<T> Clients
{
get
{
if (_clients == null)
_clients = new TypedHubClients<T>(base.Clients);
return _clients;
}
set
{
_clients = value;
}
}
}
internal class TypedHubClients<T> : IHubCallerClients<T>, IHubClients<T>
{
private readonly IHubCallerClients _hubClients;
public TypedHubClients(IHubCallerClients dynamicContext)
{
_hubClients = dynamicContext;
}
/// <summary>
/// 所有连接的客户端
/// </summary>
public T All
{
get
{
return TypedClientBuilder<T>.Build(_hubClients.All);
}
}
/// <summary>
/// 调用集线器方法的客户端
/// </summary>
public T Caller
{
get
{
return TypedClientBuilder<T>.Build(_hubClients.Caller);
}
}
/// <summary>
/// 除当前连接外的所有客户端
/// </summary>
public T Others
{
get
{
return TypedClientBuilder<T>.Build(_hubClients.Others);
}
}
/// <summary>
/// 所有连接的客户端(指定的连接除外)
/// </summary>
/// <param name="excludedConnectionIds">要排除的多个连接</param>
/// <returns></returns>
public T AllExcept(IReadOnlyList<string> excludedConnectionIds)
{
return TypedClientBuilder<T>.Build(_hubClients.AllExcept(excludedConnectionIds));
}
/// <summary>
/// 指定连接的客户端
/// </summary>
/// <param name="connectionId">指定连接</param>
/// <returns></returns>
public T Client(string connectionId)
{
return TypedClientBuilder<T>.Build(_hubClients.Client(connectionId));
}
/// <summary>
/// 指定名称的组的客户端
/// </summary>
/// <param name="groupName">组名称</param>
/// <returns></returns>
public T Group(string groupName)
{
return TypedClientBuilder<T>.Build(_hubClients.Group(groupName));
}
/// <summary>
/// 指定名称的组并排除指定连接的客户端
/// </summary>
/// <param name="groupName">组名称</param>
/// <param name="excludedConnectionIds">排除的连接</param>
/// <returns></returns>
public T GroupExcept(string groupName, IReadOnlyList<string> excludedConnectionIds)
{
return TypedClientBuilder<T>.Build(_hubClients.GroupExcept(groupName, excludedConnectionIds));
}
/// <summary>
/// 指定连接的多个客户端
/// </summary>
/// <param name="connectionIds"></param>
/// <returns></returns>
public T Clients(IReadOnlyList<string> connectionIds)
{
return TypedClientBuilder<T>.Build(_hubClients.Clients(connectionIds));
}
/// <summary>
/// 指定名称的多个组的客户端
/// </summary>
/// <param name="groupNames">多个组名称</param>
/// <returns></returns>
public T Groups(IReadOnlyList<string> groupNames)
{
return TypedClientBuilder<T>.Build(_hubClients.Groups(groupNames));
}
/// <summary>
/// 一个组中的客户端,不包括调用该集线器方法的客户端
/// </summary>
/// <param name="groupName">组名称</param>
/// <returns></returns>
public T OthersInGroup(string groupName)
{
return TypedClientBuilder<T>.Build(_hubClients.OthersInGroup(groupName));
}
/// <summary>
/// 指定用户的客户端
/// </summary>
/// <param name="userId">用户标识</param>
/// <returns></returns>
public T User(string userId)
{
return TypedClientBuilder<T>.Build(_hubClients.User(userId));
}
/// <summary>
/// 指定的多个用户的客户端
/// </summary>
/// <param name="userIds">多个用户标识</param>
/// <returns></returns>
public T Users(IReadOnlyList<string> userIds)
{
return TypedClientBuilder<T>.Build(_hubClients.Users(userIds));
}
}
internal static class TypedClientBuilder<T>
{
private static readonly Lazy<Func<IClientProxy, T>> _builder = new Lazy<Func<IClientProxy, T>>(() => GenerateClientBuilder());
private static readonly PropertyInfo CancellationTokenNoneProperty = typeof(CancellationToken).GetProperty("None", BindingFlags.Static | BindingFlags.Public);
private const string ClientModuleName = "Microsoft.AspNetCore.SignalR.TypedClientBuilder";
public static T Build(IClientProxy proxy)
{
return _builder.Value(proxy);
}
public static void Validate()
{
_ = _builder.Value;
}
private static Func<IClientProxy, T> GenerateClientBuilder()
{
VerifyInterface(typeof(T));
Type clientType = GenerateInterfaceImplementation(AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Microsoft.AspNetCore.SignalR.TypedClientBuilder"), AssemblyBuilderAccess.Run).DefineDynamicModule("Microsoft.AspNetCore.SignalR.TypedClientBuilder"));
return proxy => (T)Activator.CreateInstance(clientType, (object)proxy);
}
private static Type GenerateInterfaceImplementation(ModuleBuilder moduleBuilder)
{
TypeBuilder type = moduleBuilder.DefineType("Microsoft.AspNetCore.SignalR.TypedClientBuilder." + typeof(T).Name + "Impl", TypeAttributes.Public, typeof(object), new Type[1]
{
typeof (T)
});
FieldBuilder fieldBuilder = type.DefineField("_proxy", typeof(IClientProxy), FieldAttributes.Private);
BuildConstructor(type, fieldBuilder);
foreach (MethodInfo allInterfaceMethod in GetAllInterfaceMethods(typeof(T)))
BuildMethod(type, allInterfaceMethod, fieldBuilder);
return type.CreateTypeInfo();
}
private static IEnumerable<MethodInfo> GetAllInterfaceMethods(
Type interfaceType)
{
Type[] typeArray = interfaceType.GetInterfaces();
int index;
for (index = 0; index < typeArray.Length; ++index)
{
foreach (MethodInfo allInterfaceMethod in GetAllInterfaceMethods(typeArray[index]))
yield return allInterfaceMethod;
}
typeArray = null;
MethodInfo[] methodInfoArray = interfaceType.GetMethods();
for (index = 0; index < methodInfoArray.Length; ++index)
yield return methodInfoArray[index];
methodInfoArray = null;
}
private static void BuildConstructor(TypeBuilder type, FieldInfo proxyField)
{
MethodBuilder methodBuilder = type.DefineMethod(".ctor", MethodAttributes.Public | MethodAttributes.HideBySig);
ConstructorInfo constructor = typeof(object).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null);
methodBuilder.SetReturnType(typeof(void));
methodBuilder.SetParameters(typeof(IClientProxy));
ILGenerator ilGenerator = methodBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Call, constructor);
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Stfld, proxyField);
ilGenerator.Emit(OpCodes.Ret);
}
private static void BuildMethod(
TypeBuilder type,
MethodInfo interfaceMethodInfo,
FieldInfo proxyField)
{
MethodAttributes attributes = MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.VtableLayoutMask;
ParameterInfo[] parameters = interfaceMethodInfo.GetParameters();
Type[] array1 = ((IEnumerable<ParameterInfo>)parameters).Select(param => param.ParameterType).ToArray();
MethodBuilder methodBuilder = type.DefineMethod(interfaceMethodInfo.Name, attributes);
MethodInfo method = typeof(IClientProxy).GetMethod("SendCoreAsync", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[3]
{
typeof (string),
typeof (object[]),
typeof (CancellationToken)
}, null);
methodBuilder.SetReturnType(interfaceMethodInfo.ReturnType);
methodBuilder.SetParameters(array1);
string[] array2 = ((IEnumerable<Type>)array1).Where(p => p.IsGenericParameter).Select(p => p.Name).Distinct().ToArray<string>();
if (((IEnumerable<string>)array2).Any<string>())
methodBuilder.DefineGenericParameters(array2);
ILGenerator ilGenerator = methodBuilder.GetILGenerator();
ilGenerator.DeclareLocal(typeof(object[]));
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldfld, proxyField);
ilGenerator.Emit(OpCodes.Ldstr, interfaceMethodInfo.Name);
ilGenerator.Emit(OpCodes.Ldc_I4, parameters.Length);
ilGenerator.Emit(OpCodes.Newarr, typeof(object));
ilGenerator.Emit(OpCodes.Stloc_0);
for (int index = 0; index < array1.Length; ++index)
{
ilGenerator.Emit(OpCodes.Ldloc_0);
ilGenerator.Emit(OpCodes.Ldc_I4, index);
ilGenerator.Emit(OpCodes.Ldarg, index + 1);
ilGenerator.Emit(OpCodes.Box, array1[index]);
ilGenerator.Emit(OpCodes.Stelem_Ref);
}
ilGenerator.Emit(OpCodes.Ldloc_0);
ilGenerator.Emit(OpCodes.Call, CancellationTokenNoneProperty.GetMethod);
ilGenerator.Emit(OpCodes.Callvirt, method);
ilGenerator.Emit(OpCodes.Ret);
}
private static void VerifyInterface(Type interfaceType)
{
if (!interfaceType.IsInterface)
throw new InvalidOperationException("Type must be an interface.");
if (interfaceType.GetProperties().Length != 0)
throw new InvalidOperationException("Type must not contain properties.");
if (interfaceType.GetEvents().Length != 0)
throw new InvalidOperationException("Type must not contain events.");
foreach (MethodInfo method in interfaceType.GetMethods())
VerifyMethod(interfaceType, method);
foreach (Type interfaceType1 in interfaceType.GetInterfaces())
VerifyInterface(interfaceType1);
}
private static void VerifyMethod(Type interfaceType, MethodInfo interfaceMethod)
{
if (interfaceMethod.ReturnType != typeof(Task))
throw new InvalidOperationException("Cannot generate proxy implementation for '" + typeof(T).FullName + "." + interfaceMethod.Name + "'. All client proxy methods must return '" + typeof(Task).FullName + "'.");
foreach (ParameterInfo parameter in interfaceMethod.GetParameters())
{
if (parameter.IsOut)
throw new InvalidOperationException("Cannot generate proxy implementation for '" + typeof(T).FullName + "." + interfaceMethod.Name + "'. Client proxy methods must not have 'out' parameters.");
if (parameter.ParameterType.IsByRef)
throw new InvalidOperationException("Cannot generate proxy implementation for '" + typeof(T).FullName + "." + interfaceMethod.Name + "'. Client proxy methods must not have 'ref' parameters.");
}
}
}
第2步:控制器类
public class TestController : BaseApiController
{
private IServiceProvider provider;
private ITestContract userContract;
private readonly IFilterService _filterService;
private ILogger logger;
private readonly IHubContext<MessageHub> _hub;
/// <summary>
/// 医嘱管理
/// </summary>
/// <param name="provider"></param>
public TestController(IServiceProvider provider)
{
this.provider = provider;
userContract = provider.GetService<ITestContract>();
_filterService = provider.GetService<IFilterService>();
logger = provider.GetService<ILogger<TestController>>();
_hub = provider.GetService<IHubContext<MessageHub>>();
}
/// <summary>
/// 测试推送消息方式。
/// </summary>
/// <returns></returns>
[HttpGet]
[ActionFunction("测试推送消息")]
[AllowAnonymous]
public Task Get()
{
HisMessage model = new HisMessage() { Content = $"这是消息内容,来自控微器", Title = $"这是标题", SendTime = DateTime.Now };
_hub.Clients.All.SendAsync("pushmsg", model);
throw new Exception("测试出错了。。。");
}
}
WPF使用SignalR之WPF客户端
客户端的话,WPF使用SignalR这里只是一个演示程序,并非真实项目中的做法,只是告诉需要的朋友,是如何做的;WPF客户端的代码是需要引用Microsoft.AspNetCore.SignalR.Client这个命名空间的库,直接通过nuget引用即可;完了之后,就可以正式开始撸了。完整代码下面会给出,界面代码和后置C#代码非常的简单,更复杂的业务,自己实现。
推送效果

XAML代码
WPF使用SignalR,做一个简单的WPF窗体来演示,界面代码如下
<Window x:Class="JHRS.WPFSignalRCore.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:JHRS.WPFSignalRCore"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListBox x:Name="messagesList" RenderTransformOrigin="-0.304,0.109" BorderThickness="1" BorderBrush="Gainsboro"/>
</Grid>
</Window>
XAML的后置C#代码
WPF使用SignalR时,引入SignalR的客户端库,最基本的代码如下:
using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Threading.Tasks;
using System.Windows;
namespace JHRS.WPFSignalRCore
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
HubConnection connection = new HubConnectionBuilder()
.WithUrl("http://localhost:44321/message", options =>
{
options.AccessTokenProvider = () => Task.FromResult("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IjgiLCJvcmdJRCI6IjEiLCJkZXB0SUQiOiIwIiwiY2xpZW50SWQiOiI5NGQ2YmY0MS0yMzdkLTQyNGItOGViNS01ZGNiYjAwMWIzOGYiLCJjbGllbnRUeXBlIjoiRGVza3RvcCIsIm5iZiI6MTU5MDA0MjExNiwiZXhwIjoxNTkwMDc0NTE2LCJpYXQiOjE1OTAwNDIxMTYsImlzcyI6Imt3dCBpZGVudGl0eSIsImF1ZCI6Imt3dCBhbmd1bGFyIGRlbW8ifQ.Xnqv8lomY5qMPRgLH2m52vZuMH7CLkqGnIIXpQemlK8");
})
.Build();
connection.Closed += async (error) =>
{
await Task.Delay(new Random().Next(0, 5) * 1000);
await connection.StartAsync();
};
connection.On<HisMessage>("pushmsg", (message) =>
{
this.Dispatcher.Invoke(() =>
{
messagesList.Items.Add($"消息标题:{message.Title},消息内容:{message.Content},发送时间:{message.SendTime}");
});
});
try
{
connection.StartAsync();
messagesList.Items.Add("Connection started");
}
catch (Exception ex)
{
messagesList.Items.Add(ex.Message);
}
}
}
/// <summary>
/// 消息
/// </summary>
public class HisMessage
{
public string Title { get; set; }
public string Content { get; set; }
public DateTime SendTime { get; set; }
}
}
写在最后
在这篇文章中介绍了WPF使用SignalR时,服务器端代码和客户端代码是怎样实现的,当然只是纯演示目的,通过这个代码可以实现推送功能,即通过SignalR把消息推送给相关的客户端,这些客户端并不一定需要使用WPF来实现,还可以通过web,winform,甚至移动端都是可以的。
【江湖人士】(jhrs.com) 投稿作者:IT菜鸟,不代表江湖人士立场,如若转载,请注明出处:https://jhrs.com/2020/38205.html
扫码加入电报群,让你获得国外网赚一手信息。