最近在写基于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,甚至移动端都是可以的。