首页 > 其他分享 >让SignalR客户端回调支持强类型

让SignalR客户端回调支持强类型

时间:2023-08-18 11:33:05浏览次数:41  
标签:OpCodes return void SignalR il var 回调 Emit 客户端

几天写一个小程序的时候用到了SignalR,发现现在SingalR Server 支持强类型了,也就是说,我们可以定义一个客户端的通知契约:

    public interface IClient
    {
        void SayHello(string message);
    }

然后Hub就可以这么写了:

    public class MessageHub : Hub<IClient>
    {
        public void Hello(string message)
        {
            Clients.All.SayHello(message);        //Clients.All现在不是dynamic的了
        }
    }

主动通知也是强类型的了。

    public static void notify(string message)
    {
        var context = GlobalHost.ConnectionManager.GetHubContext<MessageHub, IClient>();
        context.Clients.All.SayHello(message);
    }

有强类型检查后感觉方便多了。但是SignalR Client却没有这个待遇,依然是这种手动关联的形式:

    var proxy = connection.CreateHubProxy("MessageHub");
    proxy.On<string>("SayHello", i => Console.WriteLine(i));

这种方式不够友好,因此我写了一个扩展函数,使得在客户端也可以使用强类型。使用方法如下:

    var proxy = connection.CreateHubProxy("MessageHub");
    proxy.Subcribe<IClient>(new ClientNotify());

    public interface Iclient
    {
        void SayHello(string message);
    }

    public class ClientNotify : Iclient
    {
        public void SayHello(string message)
        {
            Console.WriteLine(message);
        }
    }

代码如下(随手写的,质量较低,有需要的朋友自行重构下): 

 
 1     static class StrongTypeProxyExtension
 2     {
 3         public static IDisposable Subcribe<T>(this IHubProxy proxy, T obj)
 4         {
 5             var disposer = new Disposer();
 6 
 7             foreach (var method in typeof(T).GetMethods())
 8             {
 9                 Subcribe(proxy, obj, method, disposer);
10             }
11 
12             return disposer;
13         }
14 
15 
16         static void Subcribe<T>(IHubProxy proxy, T obj, MethodInfo method,  Disposer disposer)
17         {
18             var subscription = proxy.Subscribe(method.Name);
19             var methodParas = method.GetParameters().Select(i => i.ParameterType).ToArray();
20 
21             var invokeHandler = new Action<object[]>(para => method.Invoke(obj, para));
22 
23             Action<IList<JToken>> handler = args =>
24             {
25                 onData(methodParas, args, proxy.JsonSerializer, invokeHandler);
26             };
27 
28             subscription.Received += handler;
29             disposer.Add(() => subscription.Received -= handler);
30         }
31 
32         static void onData(Type[] paraTypes, IList<JToken> data, JsonSerializer serializer, Action<object[]> invokeHandler)
33         {
34             if (paraTypes.Length != data.Count)
35                 throw new InvalidOperationException();
36 
37             var para = data.Zip(paraTypes, (i1, i2) => i1.ToObject(i2)).ToArray();
38             invokeHandler(para);
39         }
40 
41         class Disposer : List<Action>, IDisposable
42         {
43             public void Dispose()
44             {
45                 foreach (var disposeHandler in this)
46                 {
47                     disposeHandler();
48                 }
49             }
50         }
51     }
View Code

这段代码功能本身没有什么问题,但是由于是用的反射来调用的接口函数,在大量调用的情况下可能有性能问题。(Subcribe函数中)

    var invokeHandler = new Action<object[]>(para => method.Invoke(obj, para));

对于有性能要求的朋友,可以使用FastInvokeHandler来优化这一性能,它是使用的Emit实现的,试了一下,基本上和原生调用在一个数量级。由于CodeProject可能会由于方校长抖威风而不定时迁移到火星。这里我把相关代码摘录了下来(稍微改动了点): 

 
  1     using InvokeHandler = Func<object, object[], object>;
  2 
  3     class FastInvokeHandler
  4     {
  5         public static InvokeHandler Create(MethodInfo methodInfo)
  6         {
  7             DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object), typeof(object[]) }, methodInfo.DeclaringType.Module);
  8             ILGenerator il = dynamicMethod.GetILGenerator();
  9             ParameterInfo[] ps = methodInfo.GetParameters();
 10             Type[] paramTypes = new Type[ps.Length];
 11             for (int i = 0; i < paramTypes.Length; i++)
 12             {
 13                 if (ps[i].ParameterType.IsByRef)
 14                     paramTypes[i] = ps[i].ParameterType.GetElementType();
 15                 else
 16                     paramTypes[i] = ps[i].ParameterType;
 17             }
 18             LocalBuilder[] locals = new LocalBuilder[paramTypes.Length];
 19 
 20             for (int i = 0; i < paramTypes.Length; i++)
 21             {
 22                 locals[i] = il.DeclareLocal(paramTypes[i], true);
 23             }
 24             for (int i = 0; i < paramTypes.Length; i++)
 25             {
 26                 il.Emit(OpCodes.Ldarg_1);
 27                 EmitFastInt(il, i);
 28                 il.Emit(OpCodes.Ldelem_Ref);
 29                 EmitCastToReference(il, paramTypes[i]);
 30                 il.Emit(OpCodes.Stloc, locals[i]);
 31             }
 32             if (!methodInfo.IsStatic)
 33             {
 34                 il.Emit(OpCodes.Ldarg_0);
 35             }
 36             for (int i = 0; i < paramTypes.Length; i++)
 37             {
 38                 if (ps[i].ParameterType.IsByRef)
 39                     il.Emit(OpCodes.Ldloca_S, locals[i]);
 40                 else
 41                     il.Emit(OpCodes.Ldloc, locals[i]);
 42             }
 43             if (methodInfo.IsStatic)
 44                 il.EmitCall(OpCodes.Call, methodInfo, null);
 45             else
 46                 il.EmitCall(OpCodes.Callvirt, methodInfo, null);
 47             if (methodInfo.ReturnType == typeof(void))
 48                 il.Emit(OpCodes.Ldnull);
 49             else
 50                 EmitBoxIfNeeded(il, methodInfo.ReturnType);
 51 
 52             for (int i = 0; i < paramTypes.Length; i++)
 53             {
 54                 if (ps[i].ParameterType.IsByRef)
 55                 {
 56                     il.Emit(OpCodes.Ldarg_1);
 57                     EmitFastInt(il, i);
 58                     il.Emit(OpCodes.Ldloc, locals[i]);
 59                     if (locals[i].LocalType.IsValueType)
 60                         il.Emit(OpCodes.Box, locals[i].LocalType);
 61                     il.Emit(OpCodes.Stelem_Ref);
 62                 }
 63             }
 64 
 65             il.Emit(OpCodes.Ret);
 66             InvokeHandler invoder = (InvokeHandler)dynamicMethod.CreateDelegate(typeof(InvokeHandler));
 67             return invoder;
 68         }
 69 
 70         private static void EmitCastToReference(ILGenerator il, System.Type type)
 71         {
 72             if (type.IsValueType)
 73             {
 74                 il.Emit(OpCodes.Unbox_Any, type);
 75             }
 76             else
 77             {
 78                 il.Emit(OpCodes.Castclass, type);
 79             }
 80         }
 81 
 82         private static void EmitBoxIfNeeded(ILGenerator il, System.Type type)
 83         {
 84             if (type.IsValueType)
 85             {
 86                 il.Emit(OpCodes.Box, type);
 87             }
 88         }
 89 
 90         private static void EmitFastInt(ILGenerator il, int value)
 91         {
 92             switch (value)
 93             {
 94                 case -1:
 95                     il.Emit(OpCodes.Ldc_I4_M1);
 96                     return;
 97                 case 0:
 98                     il.Emit(OpCodes.Ldc_I4_0);
 99                     return;
100                 case 1:
101                     il.Emit(OpCodes.Ldc_I4_1);
102                     return;
103                 case 2:
104                     il.Emit(OpCodes.Ldc_I4_2);
105                     return;
106                 case 3:
107                     il.Emit(OpCodes.Ldc_I4_3);
108                     return;
109                 case 4:
110                     il.Emit(OpCodes.Ldc_I4_4);
111                     return;
112                 case 5:
113                     il.Emit(OpCodes.Ldc_I4_5);
114                     return;
115                 case 6:
116                     il.Emit(OpCodes.Ldc_I4_6);
117                     return;
118                 case 7:
119                     il.Emit(OpCodes.Ldc_I4_7);
120                     return;
121                 case 8:
122                     il.Emit(OpCodes.Ldc_I4_8);
123                     return;
124             }
125 
126             if (value > -129 && value < 128)
127             {
128                 il.Emit(OpCodes.Ldc_I4_S, (SByte)value);
129             }
130             else
131             {
132                 il.Emit(OpCodes.Ldc_I4, value);
133             }
134         }
135     }
View Code

有了这段代码后,然后把前面的Subcribe函数反射调用改写如下形式即可

    var fastMehod = FastInvokeHandler.Create(method);
    var invokeHandler = new Action<object[]>(para => fastMehod(obj, para));

另外,github上也有人写了一个客户端强类型的扩展,功能要完善一点(支持客户端调用服务器端方法,我一般都是用的通知,就懒得弄了),不过我觉得它的使用方式还是有点麻烦,感兴趣的朋友可以看下,地址是https://github.com/i-e-b/SignalR-TypeSafeClient 。

 

标签:OpCodes,return,void,SignalR,il,var,回调,Emit,客户端
From: https://www.cnblogs.com/webenh/p/17639981.html

相关文章

  • MySQL客户端工具 phpMyAdmin MySQL Workbench HeidiSQL Sequel Pro DBeaver
    MySQL是一种流行的关系型数据库管理系统,它被广泛用于Web应用程序和企业级应用程序的开发中。目前,市面上有不少好用的MySQL客户端工具,如Navicat,SQLyog等。但这些产品虽然功能强大,却都是收费的,而且费用还不低。幸运的是,收费产品并不是你的唯一选择,目前也有不少开源的工具。如果你不想......
  • SignalR 客户端源生成器 客户端强类型方法
     SignalR客户端源生成器根据您定义的接口生成强类型的发送和接收代码。您可以在客户端上重用来自强类型SignalR集线器的相同接口来代替松散类型的.On("methodName",...)方法。同样,您的集线器可以为其方法实现一个接口,并且客户端可以使用该相同接口来调用集线器方法。要使......
  • 服务端不回应客户端的syn握手,连接建立失败原因排查
    背景测试环境有一个后台服务,部署在内网服务器A上(无外网地址),给app提供接口。app访问这个后台服务时,ip地址是公网地址,那这个请求是如何到达我们的内网服务器A呢,这块我咨询了网络同事,我画了简图如下:请求会直接打到防火墙上,防火墙对请求先做了DNAT转换(将目的地址转换为后台服务器的......
  • JFrog CLI 客户端的使用
    概述JFrogCLI是一个智能的命令行客户端工具,它提供了一个简单的交互界面,可以自动访问JFrog仓库,简化指令脚本,便于操作维护,使用更加高效和可靠。官方介绍https://jfrog.com/help/r/jfrog-cli/about-jfrog-cli系统平台CentOSLinux7下载工具官方下载链接: https://jfrog.com/getcli......
  • 模拟修改客户端访问服务器时的IP方法
    产品需求有时候需要区分白名单城市和非白名单城市,例如:北上广深访问页面A,返回一套数据B,其他城市访问页面A,返回另一套数据C。这时候我们就需要通过一定的方法才能测试覆盖到这个场景。两个方法:1、使用第三方网络代理,代理到其他城市的网络环境(这里就不细说了,缺点就是网速不稳定,可......
  • MSP 客户端打开指定外部程序
    functionshowMedi(){if(typeofwindow.cefSharpExample!="undefined"){varpath="D:\\WebApp.exe",window.cefSharpExample.showExe(path)}else{alert("可能无权限,请用客户端程序操作!");}}......
  • EAS_客户端设置按钮和菜单栏操作选项隐藏
    我们要想隐藏客户端的按钮或者菜单,防止操作,可以在listUI或者EditUI中onload()方法中使用下列方式来设置控件是否显示和是否可用,控件名可通过dep查看或者通过shift+alt+d来查看控件publicvoidonload(){//禁用按钮this.btnSave.setVisible(false);......
  • 【程序员高阶工具】idea自带的http客户端插件使用
    idea自带的http客户端插件使用一.前言http客户端的工具还是很多的,如postman,jmeter,apifox等。其中jmeter只支持本地,如果多成员间需要协作,需要进行文件传输,较为繁琐。postman和apifox,更方便进行用户间共享,但是这些工具的使用,需要打开第三方软件,从研发人员的角度不是很简便。目......
  • TCP客户端开发
    什么是客户端&服务端?1.TCP网络应用程序开发分为客户端程序开发和服务端程序开发。2.主动发起建立连接请求的是客户端程序3.等待接受连接请求的是服务端程序 TCP客户端开发流程 TCP客户端与服务端启动、交换过程:TCP客户端开发步骤:1.创建客户端套接字对象(买电话)......
  • Socket客户端实现
    1importsocket#1.导入内置的socket模块23#2.创建Socket对象4client_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)56#3.连接服务器7server_address=('localhost',18080)8client_socket.connect(server_address)910try:1......