几天写一个小程序的时候用到了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);
        }
    }

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

     static class StrongTypeProxyExtension
{
public static IDisposable Subcribe<T>(this IHubProxy proxy, T obj)
{
var disposer = new Disposer(); foreach (var method in typeof(T).GetMethods())
{
Subcribe(proxy, obj, method, disposer);
} return disposer;
} static void Subcribe<T>(IHubProxy proxy, T obj, MethodInfo method, Disposer disposer)
{
var subscription = proxy.Subscribe(method.Name);
var methodParas = method.GetParameters().Select(i => i.ParameterType).ToArray(); var invokeHandler = new Action<object[]>(para => method.Invoke(obj, para)); Action<IList<JToken>> handler = args =>
{
onData(methodParas, args, proxy.JsonSerializer, invokeHandler);
}; subscription.Received += handler;
disposer.Add(() => subscription.Received -= handler);
} static void onData(Type[] paraTypes, IList<JToken> data, JsonSerializer serializer, Action<object[]> invokeHandler)
{
if (paraTypes.Length != data.Count)
throw new InvalidOperationException(); var para = data.Zip(paraTypes, (i1, i2) => i1.ToObject(i2)).ToArray();
invokeHandler(para);
} class Disposer : List<Action>, IDisposable
{
public void Dispose()
{
foreach (var disposeHandler in this)
{
disposeHandler();
}
}
}
}

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

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

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

     using InvokeHandler = Func<object, object[], object>;

     class FastInvokeHandler
{
public static InvokeHandler Create(MethodInfo methodInfo)
{
DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object), typeof(object[]) }, methodInfo.DeclaringType.Module);
ILGenerator il = dynamicMethod.GetILGenerator();
ParameterInfo[] ps = methodInfo.GetParameters();
Type[] paramTypes = new Type[ps.Length];
for (int i = ; i < paramTypes.Length; i++)
{
if (ps[i].ParameterType.IsByRef)
paramTypes[i] = ps[i].ParameterType.GetElementType();
else
paramTypes[i] = ps[i].ParameterType;
}
LocalBuilder[] locals = new LocalBuilder[paramTypes.Length]; for (int i = ; i < paramTypes.Length; i++)
{
locals[i] = il.DeclareLocal(paramTypes[i], true);
}
for (int i = ; i < paramTypes.Length; i++)
{
il.Emit(OpCodes.Ldarg_1);
EmitFastInt(il, i);
il.Emit(OpCodes.Ldelem_Ref);
EmitCastToReference(il, paramTypes[i]);
il.Emit(OpCodes.Stloc, locals[i]);
}
if (!methodInfo.IsStatic)
{
il.Emit(OpCodes.Ldarg_0);
}
for (int i = ; i < paramTypes.Length; i++)
{
if (ps[i].ParameterType.IsByRef)
il.Emit(OpCodes.Ldloca_S, locals[i]);
else
il.Emit(OpCodes.Ldloc, locals[i]);
}
if (methodInfo.IsStatic)
il.EmitCall(OpCodes.Call, methodInfo, null);
else
il.EmitCall(OpCodes.Callvirt, methodInfo, null);
if (methodInfo.ReturnType == typeof(void))
il.Emit(OpCodes.Ldnull);
else
EmitBoxIfNeeded(il, methodInfo.ReturnType); for (int i = ; i < paramTypes.Length; i++)
{
if (ps[i].ParameterType.IsByRef)
{
il.Emit(OpCodes.Ldarg_1);
EmitFastInt(il, i);
il.Emit(OpCodes.Ldloc, locals[i]);
if (locals[i].LocalType.IsValueType)
il.Emit(OpCodes.Box, locals[i].LocalType);
il.Emit(OpCodes.Stelem_Ref);
}
} il.Emit(OpCodes.Ret);
InvokeHandler invoder = (InvokeHandler)dynamicMethod.CreateDelegate(typeof(InvokeHandler));
return invoder;
} private static void EmitCastToReference(ILGenerator il, System.Type type)
{
if (type.IsValueType)
{
il.Emit(OpCodes.Unbox_Any, type);
}
else
{
il.Emit(OpCodes.Castclass, type);
}
} private static void EmitBoxIfNeeded(ILGenerator il, System.Type type)
{
if (type.IsValueType)
{
il.Emit(OpCodes.Box, type);
}
} private static void EmitFastInt(ILGenerator il, int value)
{
switch (value)
{
case -:
il.Emit(OpCodes.Ldc_I4_M1);
return;
case :
il.Emit(OpCodes.Ldc_I4_0);
return;
case :
il.Emit(OpCodes.Ldc_I4_1);
return;
case :
il.Emit(OpCodes.Ldc_I4_2);
return;
case :
il.Emit(OpCodes.Ldc_I4_3);
return;
case :
il.Emit(OpCodes.Ldc_I4_4);
return;
case :
il.Emit(OpCodes.Ldc_I4_5);
return;
case :
il.Emit(OpCodes.Ldc_I4_6);
return;
case :
il.Emit(OpCodes.Ldc_I4_7);
return;
case :
il.Emit(OpCodes.Ldc_I4_8);
return;
} if (value > - && value < )
{
il.Emit(OpCodes.Ldc_I4_S, (SByte)value);
}
else
{
il.Emit(OpCodes.Ldc_I4, value);
}
}
}

有了这段代码后,然后把前面的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客户端回调支持强类型的更多相关文章

  1. 如何排查APP服务端和客户端是否支持ATS

    服务端排查 取得客户端直接连接的服务端域名及端口,例如mob.com.cn,端口443,即HTTPS默认端口.针对公网可访问的生产环境地址,建议使用的在线监测工具.https://wosign.ssl ...

  2. 如何创建一个客户端回调:js获得服务端的内容?

    答案:表面上看去就是前端的js调用服务的C#方法,本质就是ajax,通过XMLHttpRequest对象和服务端进行交互.回调:就说回过头来调用,按理说js是一种脚本语言,怎么能用来调用服务端的呢?就 ...

  3. SharePoint 2010 "客户端不支持使用windows资源管理器打开此列表" 解决方法

    SharePoint 2010 在“库”--“库工具”,有一个“使用资源管理器打开”的按钮,点上去报“客户端不支持使用windows资源管理器打开此列表”.如图: 解决方案:在“开始”--“管理工具” ...

  4. 在 ASP.NET 网页中不经过回发而实现客户端回调

    一.使用回调函数的好处 在 ASP.NET 网页的默认模型中,用户会与页交互,单击按钮或执行导致回发的一些其他操作.此时将重新创建页及其控件,并在服务器上运行页代码,且新版本的页被呈现到浏览器.但是, ...

  5. 解决有关flask-socketio中服务端和客户端回调函数callback参数的问题(全网最全)

    由于工作当中需要用的flask_socketio,所以自己学习了一下如何使用,查阅了有关文档,当看到回调函数callback的时候,发现文档里都描述的不太清楚,最后终于琢磨出来了,分享给有需要的朋友 ...

  6. 在 ASP.NET 网页中不经过回发而以编程方式实现客户端回调

    在 ASP.NET 网页的默认模型中,用户会与页交互,单击按钮或执行导致回发的一些其他操作.此时将重新创建页及其控件,并在服务器上运行页代码,且新版本的页被呈现到浏览器.但是,在有些情况下,需要从客户 ...

  7. 开源即时通讯GGTalk 8.0发布,增加Linux客户端,支持在统信UOS、银河麒麟上运行!

    GGTalk在2021年推出7.0后,经过一年多时间的开发,终于推出8.0版本,实现了Linux客户端. 这几年,信创国产化的势头越来越猛,政府事企业单位都在逐步转向使用国产OS.国产CPU.国产数据 ...

  8. 曹工杂谈:花了两天时间,写了一个netty实现的http客户端,支持同步转异步和连接池(1)--核心逻辑讲解

    背景 先说下写这个的目的,其实是好奇,dubbo是怎么实现同步转异步的,然后了解到,其依赖了请求中携带的请求id来完成这个连接复用:然后我又发现,redisson这个redis客户端,底层也是用的ne ...

  9. QT实现TCP通信服务器端和客户端(支持多个客户端)精简版

    上星期接了个私活,工期两星期,报酬3000,写一个小软件,采集定向网络上的数据,并进行双向通信,捣鼓了两天,终于把QT中tcp通信这块调通了,找过N多例子,绝大部分都是基本的一个服务端一个客户端通信的 ...

随机推荐

  1. 真正的上锁前,为何要调用preempt_disable()来关闭抢占的case【转】

    转自:http://blog.csdn.net/kasalyn/article/details/11473885 static inline void raw_spin_lock(raw_spinlo ...

  2. python爬虫模块之数据存储模块

    数据存储模块的话,目前我这用的比较多的是存储到mysql,所以下面的这个例子也是保存到mysql,用到了ORM映射的SQLAlchemy ,(ORM:Object-Relational Mapping ...

  3. Oracle 内存管理

    --内存分配建库时可以先分配系统内存的50%-80%给Oracle,后期根据业务再进行调整.SGA.PGA分配比例:OLTP:SGA %80 , PGA %20OLAP:SGA %50 , PGA % ...

  4. 2017多校第8场 HDU 6138 Fleet of the Eternal Throne AC自动机或者KMP

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6138 题意:给n个串,每次询问x号串和y号串的最长公共子串的长度,这个子串必须是n个串中某个串的前缀 ...

  5. 小知识-为什么Linux不需要磁盘碎片整理

      转载至:http://beikeit.com/post-495.html 简单译文: 这段linux官方资料主要介绍了外部碎片(external fragmentation).内部碎片(inter ...

  6. react表单提交

    class FlavorForm extends React.Component { constructor(props) { super(props); this.state = {value: ' ...

  7. 【转载】Web开发技术发展历史-版本2

    原文在这里. Web开发的发展史 导读:Arunr 把过去 15 年以来,Web开发从最初的纯 HTML 到 CGI.PHP\JSP\ASP.Ajax.Rails.NodeJS 这个过程简要地进行了介 ...

  8. [图解算法]线性时间选择Linear Select——<递归与分治策略>

    #include <ctime> #include <iostream> using namespace std; template <class Type> vo ...

  9. 【转】Mac系统新建txt文本文件技巧

    很多时候,我们需要在 Mac 中创建 txt 文件来记录一些信息,但是打开系统自带的文本编辑默认并不是创建 txt 文本文件 方法一: 打开终端,cd 到想要创建 txt 文本文件的目录(如桌面) 1 ...

  10. Monty Hall悖论

    Monty Hall悖论又称为蒙提·霍尔悖论.三门问题.Monty Hall是上个世纪60年代,电视游戏节目“Let's Make a Deal”的主持人,这个悖论便是以他的名字来命名的.节目的规则是 ...