• 泛型Hub


    接上一篇,文章末尾抛出了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的时候已经是动态的了,那么运行时,这些操作都是什么类型的呢?试一下:

    image

    运行时,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改一下:

    image

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

    image

    到此,服务端改造结束。服务端已经可以接受强类型的客户端行为。

    下一篇将对客户端部分进行强类型改造。

    最后附上一个基于SignalR的聊天室玩具,绿色无毒:http://www.royarea.cn/chatroom

    转载请注明出处:http://www.cnblogs.com/royding/p/3750412.html

     
     
     
    标签: SignalRAOP
  • 相关阅读:
    linux----安装jdk
    docker安装gitlab
    Centos7 安装docker
    容易混淆的字符
    nodejs 简单从淘宝的有好货根据关键词获取列表以及详情
    bash设置终端提示符
    C# NuGet打包和离线安装
    Shell 遍历文件夹中所有文件并进行相关操作
    css中“~”和“>”
    双击编辑表格字段
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3751618.html
Copyright © 2020-2023  润新知