接上一篇,文章末尾抛出了2个问题:html
若是要让客户端的方法以强类型出如今服务端,一样的,服务端的方法也以强类型出如今客户端,那就必须声明相似契约同样的载体。好比:ide
public interface IChatClient { void broadcast(string name, string message); }
public interface IChatHub { void Send(string name, string message); }
分别创建ChatClient接口和ChatHub的接口。this
public class ChatHub : Hub<IChatClient> { ... }
这是最终的目标,一个泛型Hub。spa
好,如今须要进行一些分析,怎样才能让Hub支持泛型。3d
首先,看一下Hub是如何操做客户端方法的:code
Clients.AllExcept(Context.ConnectionId).broadcast(name, message);
Hub经过Clients来操做全部客户端的行为。那么这个Clients又是什么类型的呢?orm
// 摘要: // Gets a dynamic object that represents all clients connected to this hub (not // hub instance). IHubCallerConnectionContext Clients { get; set; }
经过IHub接口看到,Clients的类型是IHubCallerConnectionContext,点进去看:server
// 摘要: // 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,再点进去看:htm
// 摘要: // 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的时候已是动态的了,那么运行时,这些操做都是什么类型的呢?试一下:blog
运行时,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方法。
好,挖到这儿,能够有一些思路了。
核心攻克点找到了,解决了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