SignalR按部就班(二)泛型Hub

接上一篇,文章末尾拋出了2個問題:html

  1. 能不能讓客戶端聲明一個強類型的方法列表呢?這樣首先不容易寫錯。
  2. 一樣的,能不能讓服務端聲明一個強類型的方法列表給客戶端調用呢?

若是要讓客戶端的方法以強類型出如今服務端,一樣的,服務端的方法也以強類型出如今客戶端,那就必須聲明相似契約同樣的載體。好比: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

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

相關文章
相關標籤/搜索