让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);
        }
    }

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

让SignalR客户端回调支持强类型
 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可能会由于方校长抖威风而不定时迁移到火星。这里我把相关代码摘录了下来(稍微改动了点): 

让SignalR客户端回调支持强类型
  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 。

 

你可能感兴趣的:(Signal)