SignalR循序渐进(二)泛型Hub
接上一篇,文章末尾抛出了2个问题:
- 能不能让客户端声明一个强类型的方法列表呢?这样首先不容易写错。
- 同样的,能不能让服务端声明一个强类型的方法列表给客户端调用呢?
如果要让客户端的方法以强类型出现在服务端,同样的,服务端的方法也以强类型出现在客户端,那就必须声明类似契约一样的载体。比如:
public interface IChatClient
{
void broadcast(string name, string message);
}
public interface IChatHub
{
void Send(string name, string message);
}
分别建立ChatClient接口和ChatHub的接口。
public class ChatHub : Hub<IChatClient>
{
...
}
这是最终的目标,一个泛型Hub。
好,现在需要进行一些分析,怎样才能让Hub支持泛型。
首先,看一下Hub是如何操作客户端方法的:
Clients.AllExcept(Context.ConnectionId).broadcast(name, message);
Hub通过Clients来操作所有客户端的行为。那么这个Clients又是什么类型的呢?
// 摘要:
// Gets a dynamic object that represents all clients connected to this hub (not
// hub instance).
IHubCallerConnectionContext Clients { get; set; }
通过IHub接口看到,Clients的类型是IHubCallerConnectionContext,点进去看:
// 摘要:
// Encapsulates all information about an individual SignalR connection for an
// Microsoft.AspNet.SignalR.Hubs.IHub.
public interface IHubCallerConnectionContext : IHubConnectionContext
{
[Dynamic]
dynamic Caller { get; }
[Dynamic]
dynamic Others { get; } dynamic OthersInGroup(string groupName);
dynamic OthersInGroups(IList<string> groupNames);
}
IHubCallerConnectionContext又继承IHubConnectionContext,再点进去看:
// 摘要:
// Encapsulates all information about a SignalR connection for an Microsoft.AspNet.SignalR.Hubs.IHub.
public interface IHubConnectionContext
{
[Dynamic]
dynamic All { get; } dynamic AllExcept(params string[] excludeConnectionIds);
dynamic Client(string connectionId);
dynamic Clients(IList<string> connectionIds);
dynamic Group(string groupName, params string[] excludeConnectionIds);
dynamic Groups(IList<string> groupNames, params string[] excludeConnectionIds);
dynamic User(string userId);
}
一目了然,所有Clients的操作方法都在这儿了,全是动态类型的,这也是为什么在Hub中写到Clients.All.xxx的时候已经是动态的了,那么运行时,这些操作都是什么类型的呢?试一下:

运行时,Clients的操作返回的是ClientProxy类型,从代码中扒出来:
public class ClientProxy : DynamicObject, IClientProxy
{
public ClientProxy(IConnection connection, IHubPipelineInvoker invoker, string hubName, IList<string> exclude); public Task Invoke(string method, params object[] args);
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result);
}
// 摘要:
// A server side proxy for the client side hub.
public interface IClientProxy
{
// 摘要:
// Invokes a method on the connection(s) represented by the Microsoft.AspNet.SignalR.Hubs.IClientProxy
// instance.
//
// 参数:
// method:
// name of the method to invoke
//
// args:
// argumetns to pass to the client
//
// 返回结果:
// A task that represents when the data has been sent to the client.
Task Invoke(string method, params object[] args);
}
}
可以看到,运行时如果以IClientProxy注入,就一个Invoke方法。
好,挖到这儿,可以有一些思路了。
- Clients所有的操作最终都是通过IClientProxy的Invoke来执行的。
- 如果让IChatClient通过某种方式和IClientProxy建立起非运行时的联系,就能实现强类型了。
- 这样的话,就需要有一个Hub<T>的类,然后把Clients里所有的操作在Hub<T>中重新实现一次。
- 然后T又是客户端的行为接口,因此,需要对Hub<T>进行静态扩展,让IClientProxy的Invoke方法能够被T的所有方法自动调用。
核心攻克点找到了,解决了4,就能一路解决1。怎样才能让IClientProxy的Invoke自动的被T的所有方法调用呢?AOP可以!可以用Castle对T进行动态织入。到这儿可以动手了,先建立一个Hub扩展类:
public static class HubExtensions
{
static readonly ProxyGenerator generator = new ProxyGenerator(); public static T GetClientBehavior<T>(this IClientProxy clientProxy) where T : class
{
return (T)generator.CreateInterfaceProxyWithoutTarget<T>(new ClientBehaviorInterceptor(clientProxy));
}
}
让所有的IClientProxy执行GetClientBehavior方法,然后内部进行拦截器装载,并将IClientProxy塞进拦截器。
public class ClientBehaviorInterceptor:IInterceptor
{
public ClientBehaviorInterceptor(IClientProxy clientProxy)
{
this.clientProxy = clientProxy;
} IClientProxy clientProxy; public void Intercept(IInvocation invocation)
{
clientProxy.Invoke(invocation.Method.Name, invocation.Arguments);
}
}
拦截器中,每当T执行方法的时候,clientProxy就执行Invoke方法,把T的方法名和T的参数传入,这就达到了原先动态调用客户端方法传入参数并执行的效果。
然后就是写一个Hub<T>了。
public abstract class Hub<T> : Hub where T : class
{
protected T All { get { return (Clients.All as IClientProxy).GetClientBehavior<T>(); } } protected T Any(params string[] connectionIds)
{
return (Clients.Clients(connectionIds) as IClientProxy).GetClientBehavior<T>();
} protected T Except(params string[] connectionIds)
{
return (Clients.AllExcept(connectionIds) as IClientProxy).GetClientBehavior<T>();
} protected T Client(string connectionId)
{
return (Clients.Client(connectionId) as IClientProxy).GetClientBehavior<T>();
} protected T Caller { get { return (Clients.Caller as IClientProxy).GetClientBehavior<T>(); } }
}
把Clients中所有的操作都在这儿写一遍,例子中就写了5个。通过刚才的扩展方法,返回的T已经是经过AOP的了。最后,把最初的ChatHub改一下:

让ChatHub继承Hub<T>,T为IChatClient,如图示,已经可以通过Except方法用强类型调用客户端方法了。执行一下看看:

到此,服务端改造结束。服务端已经可以接受强类型的客户端行为。
下一篇将对客户端部分进行强类型改造。
最后附上一个基于SignalR的聊天室玩具,绿色无毒:http://www.royarea.cn/chatroom
转载请注明出处:http://www.cnblogs.com/royding/p/3750412.html
SignalR循序渐进(二)泛型Hub的更多相关文章
- SignalR 循序渐进
SignalR 循序渐进(五)多个Hub服务器下的消息订阅 hellsoul86 2014-08-18 11:29 阅读:840 评论:7 SignalR 循序渐进(四) Hub的生命周期以及 ...
- 泛型Hub
SignalR循序渐进(二)泛型Hub 接上一篇,文章末尾抛出了2个问题: 能不能让客户端声明一个强类型的方法列表呢?这样首先不容易写错. 同样的,能不能让服务端声明一个强类型的方法列表给客户端调用呢 ...
- SignalR循序渐进(三)简易的集群通讯组件
上一篇演示了泛型Hub的实现,微软于6月17日更新了SignalR 2.1.0,然后自带了泛型Hub,于是就不需要自己去实现了…(微软你为啥不早一个月自带啊…).不过没关系,SignalR出彩之处不在 ...
- SignalR 设计理念(二)
SignalR 设计理念(二) 实现客户端和服务器端的实时通讯. 前言: 客户端方法忽略大小写,主要原因基于是URL对大小写不敏感的问题,开发者之间为了更好的协同开发,定下的开发者协议. 问题阐述 客 ...
- asp.net core 使用 signalR(二)
asp.net core 使用 signalR(二) Intro 上次介绍了 asp.net core 中使用 signalR 服务端的开发,这次总结一下web前端如何接入和使用 signalR,本文 ...
- Asp.Net Core SignalR 用泛型Hub优雅的调用前端方法及传参
继续学习 最近一直在使用Asp.Net Core SignalR(下面成SignalR Core)为小程序提供websocket支持,前端时间也发了一个学习笔记,在使用过程中稍微看了下它的源码,不得不 ...
- SignalR 循序渐进(五)多个Hub服务器下的消息订阅
SignalR的通讯方式决定了其高性能,但是即便如此,当消息的并发量上来以后,单节点的Hub服务器依然可能无法承载总的消息吞吐量,那么如何对Hub服务器做水平扩展呢? 从微软官方的文档上看,Signa ...
- SignalR 循序渐进(四) Hub的生命周期以及IoC
有阵子没更新这个系列了,最近太忙了.本篇带来的是Hub的生命周期以及IoC. 首先,Hub的生命周期,我们用一个Demo来看看: public class TestHub : Hub { public ...
- SignalR入门二、使用 SignalR 2 实现服务器广播
一.概述 这篇教程通过实现一个股票报价的小程序来讲解如何使用SignalR进行服务器端的推送,服务器会模拟股票价格的波动,并把最新的股票价格推送给所有连接的客户端,最终的运行效果如下图所示. 教程:使 ...
随机推荐
- id ,NSObject, id<NSObject>区别
转自:http://blog.csdn.net/happytengfei/article/details/11473931 我们经常会混淆以下三种申明(我是没有留意过): 1. id foo1; ...
- zoj 1562 反素数 附上个人对反素数性质的证明
反素数的定义:对于不论什么正整数,其约数个数记为.比如,假设某个正整数满足:对随意的正整 数.都有,那么称为反素数. 从反素数的定义中能够看出两个性质: (1)一个反素数的全部质因子必定是从2開始的连 ...
- 如何利用dex2jar反编译APK
工具/原料 电脑 dex2jar JD-GUI 方法/步骤 1 下载dex2jar和JD-GUI,在参考资料中添加了这两个工具的百度网盘下载地址供读者下载使用(笔者亲测) 2 找到我们准备测试用的ap ...
- 错误:undefined reference to `__gxx_personality_v0'
使用gcc编译C代码,引用了C++ 库,出现这个错误,网上搜到这哥们的文章,解决问题 转自:错误:undefined reference to `__gxx_personality_v0' 1. Li ...
- Android——Android studio项目中如何查看R.java文件(转)
Android Studio 是Google推出的一个Android开发环境,它集成了Android 开发工具用于开发和调试,类似 Eclipse ADT.Google公司停止对eclipse的后续支 ...
- Spider Studio 新版本 (20140109) - 修复浏览器对部分网页不支持的BUG
SS对部分网页中引用的jquery.js有冲突, 会造成网页部分JS效果无法正常执行. 本次版本对其进行了修正, 优化了浏览器的脚本引用机制, 修正了这个BUG.
- 树莓派系统Raspbian安装小结
是有界面的系统. NOOBS, our easy installer for Raspbian 基于debian NOOBS stands for New Out Of Box Software h ...
- js学习笔记25----Event对象
Event : 事件对象,当一个事件发生的时候,和当前这个对象发生的这个事件有关的一些详细的信息都会被临时保存到一个指定的地方-event 对象,供我们在需要时调用. 事件对象必须在一个事件调用的函数 ...
- js实现EasyUI-datagrid前台分页
//实现假分页 function myLoader(param, success, error) { var that = $(this); var opts = that.datagrid(&quo ...
- [工具使用] 如何访问github
1.ping github.com,记录github的ip:192.30.252.129 2.找到系统的 hosts文件位置: C:\Windows\System32\drivers\etc\host ...