首页 > 代码库 > 让SignalR客户端回调支持强类型
让SignalR客户端回调支持强类型
几天写一个小程序的时候用到了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>, IDisposable42 {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 else131 {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 。
让SignalR客户端回调支持强类型