接上一篇,文章末尾抛出了2个问题:

  1. 能不能让客户端声明一个强类型的方法列表呢?这样首先不容易写错。
  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方法。

好,挖到这儿,可以有一些思路了。

  1. Clients所有的操作最终都是通过IClientProxy的Invoke来执行的。
  2. 如果让IChatClient通过某种方式和IClientProxy建立起非运行时的联系,就能实现强类型了。
  3. 这样的话,就需要有一个Hub<T>的类,然后把Clients里所有的操作在Hub<T>中重新实现一次。
  4. 然后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, AOP

泛型Hub的更多相关文章

  1. SignalR循序渐进(二)泛型Hub

    接上一篇,文章末尾抛出了2个问题: 能不能让客户端声明一个强类型的方法列表呢?这样首先不容易写错. 同样的,能不能让服务端声明一个强类型的方法列表给客户端调用呢? 如果要让客户端的方法以强类型出现在服 ...

  2. Asp.Net Core SignalR 用泛型Hub优雅的调用前端方法及传参

    继续学习 最近一直在使用Asp.Net Core SignalR(下面成SignalR Core)为小程序提供websocket支持,前端时间也发了一个学习笔记,在使用过程中稍微看了下它的源码,不得不 ...

  3. SignalR 循序渐进

    SignalR 循序渐进(五)多个Hub服务器下的消息订阅 hellsoul86 2014-08-18 11:29 阅读:840 评论:7     SignalR 循序渐进(四) Hub的生命周期以及 ...

  4. SignalR循序渐进(三)简易的集群通讯组件

    上一篇演示了泛型Hub的实现,微软于6月17日更新了SignalR 2.1.0,然后自带了泛型Hub,于是就不需要自己去实现了…(微软你为啥不早一个月自带啊…).不过没关系,SignalR出彩之处不在 ...

  5. .Net Core SignalR 初体验

    前言 Asp.Net SignalR已经出来很久了,但是一直没有静下心来好好看看.昨天花了几个小时的时间看了下.首先借鉴了官方文档,如何搭建一个SignalR的Demo. 参考文章:https://d ...

  6. 一起学 Java(三) 集合框架、数据结构、泛型

    一.Java 集合框架 集合框架是一个用来代表和操纵集合的统一架构.所有的集合框架都包含如下内容: 接口:是代表集合的抽象数据类型.接口允许集合独立操纵其代表的细节.在面向对象的语言,接口通常形成一个 ...

  7. .NET面试题系列[8] - 泛型

    “可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用.“ - Jon Skeet .NET面试题系列目录 .NET面试题系列[1] - .NET框架基础知识(1) .NET面试题系列[2] ...

  8. C#4.0泛型的协变,逆变深入剖析

    C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...

  9. 编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议106~109)

    建议106:动态代理可以使代理模式更加灵活 Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发.我们知道一个静态代理是通过主题角色(Prox ...

随机推荐

  1. JNLP(Java Web Start )(转)

    JNLP(Java Network Launching Protocol )是java提供的一种可以通过浏览器直接执行java应用程序的途径,它使你可以直接通过一个网页上的url连接打开一个java应 ...

  2. TortoiseGit push失败原因小结(转)

    花了我一个晚上,终于弄明白为什么总是 push 失败的原因了!竟然是因为我用的是注册的用户名而不是邮箱名……囧死. 另外搞清楚了一个问题,就是 Git 和远程仓库交互有两种方式,即 https 方式和 ...

  3. tortoisegit使用密钥连接服务器(转)

    目录 [hide] 1 使用putty的密钥 1.1 生成putty密钥 2 在服务器上添加openssh公钥 3 在tortoisegit上使用密钥 4 putty密钥与openssh密钥转化 5  ...

  4. jvm对大对象分配内存的特殊处理(转)

    前段日子在和leader交流技术的时候,偶然听到jvm在分配内存空间给大对象时,如果young区空间不足会直接在old区切一块过去.对于这个结论很好奇,也比较怀疑,所以就上网搜了下,发现还真有这么回事 ...

  5. js中document.write()使用方法

    <script>             var hrf = window.location.href;           if(hrf.indexOf('change')>0){ ...

  6. View中选择的数据行中的部分数据传入到Controller中

    将View中选择的数据行中的部分数据传入到Controller中   ASP.NET MVC搭建项目后台UI框架—1.后台主框架 ASP.NET MVC搭建项目后台UI框架—2.菜单特效 ASP.NE ...

  7. 深入理解ASP.NET MVC Day1

    深入理解ASP.NET MVC   ASP.NET vs MVC vs WebForms 许多ASP.NET开发人员开始接触MVC认为MVC与ASP.NET完全没有关系,是一个全新的Web开发,事实上 ...

  8. NYoj 最舒适的路线

    题目链接:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=711 分析:枚举速度最大的边,找出能够从S到达T的最大速度,然后求出它们的比值,与已 ...

  9. elasticsearch的rest搜索--- 查询

    目录: 一.针对这次装B 的解释 二.下载,安装插件elasticsearch-1.7.0   三.索引的mapping 四. 查询 五.对于相关度的大牛的文档 四. 查询 1. 查询的官网的文档   ...

  10. C#-简单的定时器(C# ConsoleApp) ---ShinePans

    watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2hpbmVwYW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA ...