站点图标 江湖人士

WPF使用SignalR之服务器端和客户端写法5分钟介绍

WPF使用SignalR之服务器端和客户端写法5分钟介绍

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

WPF使用SignalR之服务器端和客户端写法5分钟介绍

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#代码非常的简单,更复杂的业务,自己实现。

推送效果

WPF使用SignalR之服务器端和客户端写法5分钟介绍

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,甚至移动端都是可以的。

退出移动版