隐藏

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

发布:2016/2/19 17:32:53作者:管理员 来源:本站 浏览次数:1279

几天写一个小程序的时候用到了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 }
复制代码

这段代码功能本身没有什么问题,但是由于是用的反射来调用的接口函数,在大量调用的情况下可能有性能问题。(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 }
复制代码

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

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

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