泛型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
泛型Hub的更多相关文章
- SignalR循序渐进(二)泛型Hub
接上一篇,文章末尾抛出了2个问题: 能不能让客户端声明一个强类型的方法列表呢?这样首先不容易写错. 同样的,能不能让服务端声明一个强类型的方法列表给客户端调用呢? 如果要让客户端的方法以强类型出现在服 ...
- Asp.Net Core SignalR 用泛型Hub优雅的调用前端方法及传参
继续学习 最近一直在使用Asp.Net Core SignalR(下面成SignalR Core)为小程序提供websocket支持,前端时间也发了一个学习笔记,在使用过程中稍微看了下它的源码,不得不 ...
- SignalR 循序渐进
SignalR 循序渐进(五)多个Hub服务器下的消息订阅 hellsoul86 2014-08-18 11:29 阅读:840 评论:7 SignalR 循序渐进(四) Hub的生命周期以及 ...
- SignalR循序渐进(三)简易的集群通讯组件
上一篇演示了泛型Hub的实现,微软于6月17日更新了SignalR 2.1.0,然后自带了泛型Hub,于是就不需要自己去实现了…(微软你为啥不早一个月自带啊…).不过没关系,SignalR出彩之处不在 ...
- .Net Core SignalR 初体验
前言 Asp.Net SignalR已经出来很久了,但是一直没有静下心来好好看看.昨天花了几个小时的时间看了下.首先借鉴了官方文档,如何搭建一个SignalR的Demo. 参考文章:https://d ...
- 一起学 Java(三) 集合框架、数据结构、泛型
一.Java 集合框架 集合框架是一个用来代表和操纵集合的统一架构.所有的集合框架都包含如下内容: 接口:是代表集合的抽象数据类型.接口允许集合独立操纵其代表的细节.在面向对象的语言,接口通常形成一个 ...
- .NET面试题系列[8] - 泛型
“可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用.“ - Jon Skeet .NET面试题系列目录 .NET面试题系列[1] - .NET框架基础知识(1) .NET面试题系列[2] ...
- C#4.0泛型的协变,逆变深入剖析
C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...
- 编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议106~109)
建议106:动态代理可以使代理模式更加灵活 Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发.我们知道一个静态代理是通过主题角色(Prox ...
随机推荐
- git stash用法
使用场景: 当前修改的代码还不足以提交commit,但又必须切换到其他分支,要想完成这样的操作就可以使用git stash git stash意思就是备份当前的工作区的内容,从最近的一次提交中读取相关 ...
- Python 基于学习 网络小爬虫
<span style="font-size:18px;"># # 百度贴吧图片网络小爬虫 # import re import urllib def getHtml( ...
- [LeetCode] 032. Longest Valid Parentheses (Hard) (C++)
指数:[LeetCode] Leetcode 指标解释 (C++/Java/Python/Sql) Github: https://github.com/illuz/leetcode 032. Lon ...
- ES6 扫盲
原文地址:ECMAScript 6 扫盲--小胡子 1. let.const 和 block 作用域 let 允许创建块级作用域,ES6 推荐在函数中使用 let 定义变量,而非 var: var a ...
- 于ios7在遇到一些发展deprecated问题
cell.textLabel.textAlignment = UITextAlignmentCenter; 现在我想写cell.textLabel.textAlignment =NSTextAlign ...
- 平均得分 【杭州电-HDOJ-2023】 附加题+详细说明
/* 平均得分 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Su ...
- 杭州电acm理工大舞台版
我要参加全国软件设计大赛C/C++学生语言组,前一个假设<C训练和演习,并总结手>没看完,请阅读上述并根据所作的训练,然后做下面的练习. 门户:http://blog.csdn.net/l ...
- 联合县城市,采用ajax,而使用ul模拟select下拉
接待处代码 js //采用jquery展示鼠标放到省ul下拉显示 $("#province").hover(function(){ ...
- MVC4的过滤器
过滤器 提供的四种基本类型过滤器接口,IAuthorizationFilter.IActionFilter.IResultFilter和IExceptionFilter,可通过继承对应的接口和Filt ...
- .net EF 事物 订单流水号的生成 (二):观察者模式、事物、EF
针对.net EF 事物 订单流水号的生成 (一) 的封装. 数据依然不变. using System; using System.Linq; using System.Transactions; ...