Skip to content
This repository
Browse code

Implemented hub pipeline

- Added IHubPipelineModule as an extensibility point
- Added ability to add Modules to the IPipeline on the Host
  • Loading branch information...
commit 83fdbfd9baa1f1cc3399d7f210cb062597c8084c 1 parent 3fa839a
David Fowler authored September 06, 2012 halter73 committed September 24, 2012
12  SignalR.Hosting.Common/GlobalHost.cs
... ...
@@ -1,4 +1,5 @@
1 1
 using System;
  2
+using SignalR.Hubs;
2 3
 
3 4
 namespace SignalR
4 5
 {
@@ -46,5 +47,16 @@ public static IConnectionManager ConnectionManager
46 47
                 return DependencyResolver.Resolve<IConnectionManager>();
47 48
             }
48 49
         }
  50
+
  51
+        /// <summary>
  52
+        /// 
  53
+        /// </summary>
  54
+        public static IHubPipeline HubPipeline
  55
+        {
  56
+            get
  57
+            {
  58
+                return DependencyResolver.Resolve<IHubPipeline>();
  59
+            }
  60
+        }
49 61
     }
50 62
 }
15  SignalR.Hosting.Common/Host.cs
... ...
@@ -1,4 +1,6 @@
1  
-namespace SignalR.Hosting.Common
  1
+using SignalR.Hubs;
  2
+
  3
+namespace SignalR.Hosting.Common
2 4
 {
3 5
     public class Host
4 6
     {
@@ -41,5 +43,16 @@ public IConfigurationManager Configuration
41 43
                 return DependencyResolver.Resolve<IConfigurationManager>();
42 44
             }
43 45
         }
  46
+
  47
+        /// <summary>
  48
+        /// 
  49
+        /// </summary>
  50
+        public IHubPipeline HubPipeline
  51
+        {
  52
+            get
  53
+            {
  54
+                return DependencyResolver.Resolve<IHubPipeline>();
  55
+            }
  56
+        }
44 57
     }
45 58
 }
6  SignalR/ConnectionManager.cs
... ...
@@ -1,6 +1,7 @@
1 1
 using System;
2 2
 using System.Diagnostics;
3 3
 using System.Linq;
  4
+using System.Threading.Tasks;
4 5
 using SignalR.Hubs;
5 6
 using SignalR.Infrastructure;
6 7
 
@@ -73,13 +74,16 @@ public IHubContext GetHubContext(string hubName)
73 74
         {
74 75
             var connection = GetConnection(connectionName: null);
75 76
             var hubManager = _resolver.Resolve<IHubManager>();
  77
+            var pipelineInvoker = _resolver.Resolve<IHubPipelineInvoker>();
76 78
             HubDescriptor hubDescriptor = hubManager.EnsureHub(hubName,
77 79
                 _hubResolutionErrorsTotalCounter,
78 80
                 _hubResolutionErrorsPerSecCounter,
79 81
                 _allErrorsTotalCounter,
80 82
                 _allErrorsPerSecCounter);
81 83
 
82  
-            return new HubContext(new ClientProxy(connection, hubDescriptor.Name), 
  84
+            Func<string, ClientHubInvocation, Task> send = (signal, value) => pipelineInvoker.Send(new HubOutgoingInvokerContext(connection, signal, value));
  85
+
  86
+            return new HubContext(new ClientProxy(send, hubDescriptor.Name), 
83 87
                                   new GroupManager(connection, hubName));
84 88
         }
85 89
 
35  SignalR/Hubs/ClientHubInvocation.cs
... ...
@@ -0,0 +1,35 @@
  1
+using System.Collections.Generic;
  2
+
  3
+namespace SignalR.Hubs
  4
+{
  5
+    /// <summary>
  6
+    /// 
  7
+    /// </summary>
  8
+    public class ClientHubInvocation
  9
+    {
  10
+        /// <summary>
  11
+        /// 
  12
+        /// </summary>
  13
+        public string GroupOrConnectionId { get; set; }
  14
+
  15
+        /// <summary>
  16
+        /// 
  17
+        /// </summary>
  18
+        public string Hub { get; set; }
  19
+
  20
+        /// <summary>
  21
+        /// 
  22
+        /// </summary>
  23
+        public string Method { get; set; }
  24
+
  25
+        /// <summary>
  26
+        /// 
  27
+        /// </summary>
  28
+        public object[] Args { get; set; }
  29
+
  30
+        /// <summary>
  31
+        /// 
  32
+        /// </summary>
  33
+        public IDictionary<string, object> State { get; set; }
  34
+    }
  35
+}
15  SignalR/Hubs/ClientProxy.cs
... ...
@@ -1,16 +1,17 @@
1  
-using System.Dynamic;
  1
+using System;
  2
+using System.Dynamic;
2 3
 using System.Threading.Tasks;
3 4
 
4 5
 namespace SignalR.Hubs
5 6
 {
6 7
     public class ClientProxy : DynamicObject, IClientProxy
7 8
     {
8  
-        private readonly IConnection _connection;
  9
+        private readonly Func<string, ClientHubInvocation, Task> _send;
9 10
         private readonly string _hubName;
10 11
 
11  
-        public ClientProxy(IConnection connection, string hubName)
  12
+        public ClientProxy(Func<string, ClientHubInvocation, Task> send, string hubName)
12 13
         {
13  
-            _connection = connection;
  14
+            _send = send;
14 15
             _hubName = hubName;
15 16
         }
16 17
 
@@ -18,7 +19,7 @@ public ClientProxy(IConnection connection, string hubName)
18 19
         {
19 20
             get
20 21
             {
21  
-                return new SignalProxy(_connection, key, _hubName);
  22
+                return new SignalProxy(_send, key, _hubName);
22 23
             }
23 24
         }
24 25
 
@@ -36,14 +37,14 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o
36 37
 
37 38
         public Task Invoke(string method, params object[] args)
38 39
         {
39  
-            var invocation = new
  40
+            var invocation = new ClientHubInvocation
40 41
             {
41 42
                 Hub = _hubName,
42 43
                 Method = method,
43 44
                 Args = args
44 45
             };
45 46
 
46  
-            return _connection.Send(_hubName, invocation);
  47
+            return _send(_hubName, invocation);
47 48
         }
48 49
     }
49 50
 }
147  SignalR/Hubs/HubDispatcher.cs
@@ -19,6 +19,7 @@ public class HubDispatcher : PersistentConnection
19 19
         private IHubManager _manager;
20 20
         private IHubRequestParser _requestParser;
21 21
         private IParameterResolver _binder;
  22
+        private IHubPipelineInvoker _pipelineInvoker;
22 23
         private readonly List<HubDescriptor> _hubs = new List<HubDescriptor>();
23 24
         private bool _isDebuggingEnabled;
24 25
         private PerformanceCounter _allErrorsTotalCounter;
@@ -52,6 +53,7 @@ public override void Initialize(IDependencyResolver resolver)
52 53
             _manager = resolver.Resolve<IHubManager>();
53 54
             _binder = resolver.Resolve<IParameterResolver>();
54 55
             _requestParser = resolver.Resolve<IHubRequestParser>();
  56
+            _pipelineInvoker = resolver.Resolve<IHubPipelineInvoker>();
55 57
 
56 58
             var counters = resolver.Resolve<IPerformanceCounterWriter>();
57 59
             _allErrorsTotalCounter = counters.GetCounter(PerformanceCounters.ErrorsAllTotal);
@@ -93,82 +95,144 @@ protected override Task OnReceivedAsync(IRequest request, string connectionId, s
93 95
             var state = new TrackingDictionary(hubRequest.State);
94 96
             var hub = CreateHub(request, descriptor, connectionId, state, throwIfFailedToCreate: true);
95 97
 
96  
-            Task resultTask;
  98
+            return InvokeHubPipeline(request, connectionId, data, hubRequest, parameterValues, methodDescriptor, state, hub);
  99
+        }
  100
+
  101
+        private Task InvokeHubPipeline(IRequest request, string connectionId, string data, HubRequest hubRequest, IJsonValue[] parameterValues, MethodDescriptor methodDescriptor, TrackingDictionary state, IHub hub)
  102
+        {
  103
+
  104
+            var args = _binder.ResolveMethodParameters(methodDescriptor, parameterValues);
  105
+            var context = new HubInvokerContext(hub, state, methodDescriptor, args);
  106
+
  107
+            // Invoke the pipeline
  108
+            return _pipelineInvoker.Invoke(context)
  109
+                                   .ContinueWith(task =>
  110
+                                   {
  111
+                                       if (task.IsFaulted)
  112
+                                       {
  113
+                                           return ProcessResponse(state, null, hubRequest, task.Exception);
  114
+                                       }
  115
+                                       else
  116
+                                       {
  117
+                                           return ProcessResponse(state, task.Result, hubRequest, null);
  118
+                                       }
  119
+                                   })
  120
+                                   .FastUnwrap();
  121
+
  122
+        }
  123
+
  124
+        public override Task ProcessRequestAsync(HostContext context)
  125
+        {
  126
+            // Generate the proxy
  127
+            if (context.Request.Url.LocalPath.EndsWith("/hubs", StringComparison.OrdinalIgnoreCase))
  128
+            {
  129
+                context.Response.ContentType = "application/x-javascript";
  130
+                return context.Response.EndAsync(_proxyGenerator.GenerateProxy(_url));
  131
+            }
  132
+
  133
+            _isDebuggingEnabled = context.IsDebuggingEnabled();
  134
+
  135
+            return base.ProcessRequestAsync(context);
  136
+        }
  137
+
  138
+        internal static Task Connect(IHub hub)
  139
+        {
  140
+            return ((IConnected)hub).Connect();
  141
+        }
  142
+
  143
+        internal static Task Reconnect(IHub hub, IEnumerable<string> groups)
  144
+        {
  145
+            return ((IConnected)hub).Reconnect(groups);
  146
+        }
  147
+
  148
+        internal static Task Disconnect(IHub hub)
  149
+        {
  150
+            return ((IDisconnect)hub).Disconnect();
  151
+        }
  152
+
  153
+        internal static Task<object> Incoming(IHubIncomingInvokerContext context)
  154
+        {
  155
+            var tcs = new TaskCompletionSource<object>();
97 156
 
98 157
             try
99 158
             {
100  
-                // Invoke the method
101  
-                object result = methodDescriptor.Invoker.Invoke(hub, _binder.ResolveMethodParameters(methodDescriptor, parameterValues));
102  
-                Type returnType = result != null ? result.GetType() : methodDescriptor.ReturnType;
  159
+                var result = context.MethodDescriptor.Invoker.Invoke(context.Hub, context.Args);
  160
+                Type returnType = context.MethodDescriptor.ReturnType;
103 161
 
104 162
                 if (typeof(Task).IsAssignableFrom(returnType))
105 163
                 {
106 164
                     var task = (Task)result;
107 165
                     if (!returnType.IsGenericType)
108 166
                     {
109  
-                        return task.ContinueWith(t => ProcessResponse(state, null, hubRequest, t.Exception))
110  
-                                   .FastUnwrap();
  167
+                        task.ContinueWith(tcs);
111 168
                     }
112 169
                     else
113 170
                     {
114 171
                         // Get the <T> in Task<T>
115 172
                         Type resultType = returnType.GetGenericArguments().Single();
116 173
 
117  
-                        // Get the correct ContinueWith overload
118  
-                        var continueWith = TaskAsyncHelper.GetContinueWith(task.GetType());
  174
+                        Type genericTaskType = typeof(Task<>).MakeGenericType(resultType);
119 175
 
120  
-                        var taskParameter = Expression.Parameter(continueWith.Type);
121  
-                        var processResultMethod = typeof(HubDispatcher).GetMethod("ProcessTaskResult", BindingFlags.NonPublic | BindingFlags.Instance).MakeGenericMethod(resultType);
  176
+                        // Get the correct ContinueWith overload
  177
+                        var parameter = Expression.Parameter(typeof(object));
122 178
 
123  
-                        var body = Expression.Call(Expression.Constant(this),
124  
-                                                   processResultMethod,
125  
-                                                   Expression.Constant(state),
126  
-                                                   Expression.Constant(hubRequest),
127  
-                                                   taskParameter);
  179
+                        // TODO: Cache this whole thing
  180
+                        var continueWithMethod = typeof(HubDispatcher).GetMethod("ContinueWith", BindingFlags.NonPublic | BindingFlags.Static)
  181
+                                                                      .MakeGenericMethod(resultType);
128 182
 
129  
-                        var lambda = Expression.Lambda(body, taskParameter);
  183
+                        Expression body = Expression.Call(continueWithMethod,
  184
+                                                          Expression.Convert(parameter, genericTaskType),
  185
+                                                          Expression.Constant(tcs));
130 186
 
131  
-                        var call = Expression.Call(Expression.Constant(task, continueWith.Type), continueWith.Method, lambda);
132  
-                        Func<Task<Task>> continueWithMethod = Expression.Lambda<Func<Task<Task>>>(call).Compile();
133  
-                        return continueWithMethod.Invoke().FastUnwrap();
  187
+                        var continueWithInvoker = Expression.Lambda<Action<object>>(body, parameter).Compile();
  188
+                        continueWithInvoker.Invoke(result);
134 189
                     }
135 190
                 }
136 191
                 else
137 192
                 {
138  
-                    resultTask = ProcessResponse(state, result, hubRequest, null);
  193
+                    tcs.TrySetResult(result);
139 194
                 }
140 195
             }
141  
-            catch (TargetInvocationException e)
  196
+            catch (Exception ex)
142 197
             {
143  
-                resultTask = ProcessResponse(state, null, hubRequest, e);
  198
+                tcs.TrySetException(ex);
144 199
             }
145 200
 
146  
-            return resultTask.Then(() => base.OnReceivedAsync(request, connectionId, data))
147  
-                             .Catch();
  201
+            return tcs.Task;
148 202
         }
149 203
 
150  
-        public override Task ProcessRequestAsync(HostContext context)
  204
+        private static void ContinueWith<T>(Task<T> task, TaskCompletionSource<object> tcs)
151 205
         {
152  
-            // Generate the proxy
153  
-            if (context.Request.Url.LocalPath.EndsWith("/hubs", StringComparison.OrdinalIgnoreCase))
  206
+            task.ContinueWith(t =>
154 207
             {
155  
-                context.Response.ContentType = "application/x-javascript";
156  
-                return context.Response.EndAsync(_proxyGenerator.GenerateProxy(_url));
157  
-            }
158  
-
159  
-            _isDebuggingEnabled = context.IsDebuggingEnabled();
  208
+                if (t.IsFaulted)
  209
+                {
  210
+                    tcs.TrySetException(t.Exception);
  211
+                }
  212
+                else if (t.IsCanceled)
  213
+                {
  214
+                    tcs.TrySetCanceled();
  215
+                }
  216
+                else
  217
+                {
  218
+                    tcs.TrySetResult(t.Result);
  219
+                }
  220
+            });
  221
+        }
160 222
 
161  
-            return base.ProcessRequestAsync(context);
  223
+        internal static Task Outgoing(IHubOutgoingInvokerContext context)
  224
+        {
  225
+            return context.Connection.Send(context.Signal, context.Invocation);
162 226
         }
163 227
 
164 228
         protected override Task OnConnectedAsync(IRequest request, string connectionId)
165 229
         {
166  
-            return ExecuteHubEventAsync<IConnected>(request, connectionId, hub => hub.Connect());
  230
+            return ExecuteHubEventAsync<IConnected>(request, connectionId, hub => _pipelineInvoker.Connect(hub));
167 231
         }
168 232
 
169 233
         protected override Task OnReconnectedAsync(IRequest request, IEnumerable<string> groups, string connectionId)
170 234
         {
171  
-            return ExecuteHubEventAsync<IConnected>(request, connectionId, hub => hub.Reconnect(groups));
  235
+            return ExecuteHubEventAsync<IConnected>(request, connectionId, hub => _pipelineInvoker.Reconnect(hub, groups));
172 236
         }
173 237
 
174 238
         protected override IEnumerable<string> OnRejoiningGroups(IRequest request, IEnumerable<string> groups, string connectionId)
@@ -188,14 +252,14 @@ protected override IEnumerable<string> OnRejoiningGroups(IRequest request, IEnum
188 252
 
189 253
         protected override Task OnDisconnectAsync(string connectionId)
190 254
         {
191  
-            return ExecuteHubEventAsync<IDisconnect>(request: null, connectionId: connectionId, action: hub => hub.Disconnect());
  255
+            return ExecuteHubEventAsync<IDisconnect>(request: null, connectionId: connectionId, action: hub => _pipelineInvoker.Disconnect(hub));
192 256
         }
193 257
 
194  
-        private Task ExecuteHubEventAsync<T>(IRequest request, string connectionId, Func<T, Task> action) where T : class
  258
+        private Task ExecuteHubEventAsync<T>(IRequest request, string connectionId, Func<IHub, Task> action) where T : class
195 259
         {
196 260
             var operations = GetHubsImplementingInterface(typeof(T))
197 261
                 .Select(hub => CreateHub(request, hub, connectionId))
198  
-                .OfType<T>()
  262
+                .Where(hub => hub != null)
199 263
                 .Select(instance => action(instance).Catch().OrEmpty())
200 264
                 .ToList();
201 265
 
@@ -234,9 +298,12 @@ private IHub CreateHub(IRequest request, HubDescriptor descriptor, string connec
234 298
                 if (hub != null)
235 299
                 {
236 300
                     state = state ?? new TrackingDictionary();
  301
+
  302
+                    Func<string, ClientHubInvocation, Task> send = (signal, value) => _pipelineInvoker.Send(new HubOutgoingInvokerContext(Connection, signal, value));
  303
+
237 304
                     hub.Context = new HubCallerContext(request, connectionId);
238  
-                    hub.Caller = new StatefulSignalProxy(Connection, connectionId, descriptor.Name, state);
239  
-                    hub.Clients = new ClientProxy(Connection, descriptor.Name);
  305
+                    hub.Caller = new StatefulSignalProxy(send, connectionId, descriptor.Name, state);
  306
+                    hub.Clients = new ClientProxy(send, descriptor.Name);
240 307
                     hub.Groups = new GroupManager(Connection, descriptor.Name);
241 308
                 }
242 309
 
41  SignalR/Hubs/Pipeline/HubInvokerContext.cs
... ...
@@ -0,0 +1,41 @@
  1
+namespace SignalR.Hubs
  2
+{
  3
+    /// <summary>
  4
+    /// 
  5
+    /// </summary>
  6
+    public class HubInvokerContext : IHubIncomingInvokerContext
  7
+    {
  8
+        public HubInvokerContext(IHub hub, TrackingDictionary state, MethodDescriptor methodDescriptor, object[] args)
  9
+        {
  10
+            Hub = hub;
  11
+            MethodDescriptor = methodDescriptor;
  12
+            Args = args;
  13
+            State = state;
  14
+        }
  15
+
  16
+        public IHub Hub
  17
+        {
  18
+            get;
  19
+            private set;
  20
+        }
  21
+
  22
+        public MethodDescriptor MethodDescriptor
  23
+        {
  24
+            get;
  25
+            private set;
  26
+        }
  27
+
  28
+        public object[] Args
  29
+        {
  30
+            get;
  31
+            private set;
  32
+        }
  33
+
  34
+
  35
+        public TrackingDictionary State
  36
+        {
  37
+            get;
  38
+            private set;
  39
+        }
  40
+    }
  41
+}
36  SignalR/Hubs/Pipeline/HubOutgoingInvokerContext.cs
... ...
@@ -0,0 +1,36 @@
  1
+namespace SignalR.Hubs
  2
+{
  3
+    public class HubOutgoingInvokerContext : IHubOutgoingInvokerContext
  4
+    {        
  5
+        public HubOutgoingInvokerContext(IConnection connection, string signal, ClientHubInvocation invocation)
  6
+        {
  7
+            Connection = connection;
  8
+            Signal = signal;
  9
+            Invocation = invocation;
  10
+        }
  11
+
  12
+        public IConnection Connection
  13
+        {
  14
+            get;
  15
+            private set;
  16
+        }
  17
+
  18
+        public string Group
  19
+        {
  20
+            get;
  21
+            private set;
  22
+        }
  23
+
  24
+        public ClientHubInvocation Invocation
  25
+        {
  26
+            get;
  27
+            private set;
  28
+        }
  29
+
  30
+        public string Signal
  31
+        {
  32
+            get;
  33
+            private set;
  34
+        }
  35
+    }
  36
+}
159  SignalR/Hubs/Pipeline/HubPipeline.cs
... ...
@@ -0,0 +1,159 @@
  1
+using System;
  2
+using System.Collections.Generic;
  3
+using System.Linq;
  4
+using System.Threading.Tasks;
  5
+
  6
+namespace SignalR.Hubs
  7
+{
  8
+    public class HubPipeline : IHubPipeline, IHubPipelineInvoker
  9
+    {
  10
+        private readonly Stack<IHubPipelineModule> _modules = new Stack<IHubPipelineModule>();
  11
+
  12
+        private Func<IHubIncomingInvokerContext, Task<object>> _incomingPipeline;
  13
+        private Func<IHub, Task> _connectPipeline;
  14
+        private Func<IHub, IEnumerable<string>, Task> _reconnectPipeline;
  15
+        private Func<IHub, Task> _disconnectPipeline;
  16
+        private Func<IHubOutgoingInvokerContext, Task> _outgoingPipeling;
  17
+
  18
+        public HubPipeline()
  19
+        {
  20
+            // Add one item to the list so we don't have to special case the logic if
  21
+            // there's no builders in the pipeline
  22
+            AddModule(new NoopModule());
  23
+        }
  24
+
  25
+        public IHubPipeline AddModule(IHubPipelineModule builder)
  26
+        {
  27
+            _modules.Push(builder);
  28
+            return this;
  29
+        }
  30
+
  31
+        private void EnsurePipeline()
  32
+        {
  33
+            if (_incomingPipeline == null)
  34
+            {
  35
+                IHubPipelineModule module = _modules.Reverse().Aggregate((a, b) => new ComposedModule(a, b));
  36
+                _incomingPipeline = module.BuildIncoming(HubDispatcher.Incoming);
  37
+                _connectPipeline = module.BuildConnect(HubDispatcher.Connect);
  38
+                _reconnectPipeline = module.BuildReconnect(HubDispatcher.Reconnect);
  39
+                _disconnectPipeline = module.BuildDisconnect(HubDispatcher.Disconnect);
  40
+                _outgoingPipeling = module.BuildOutgoing(HubDispatcher.Outgoing);
  41
+            }
  42
+        }
  43
+
  44
+        public Task<object> Invoke(IHubIncomingInvokerContext context)
  45
+        {
  46
+            EnsurePipeline();
  47
+
  48
+            return _incomingPipeline.Invoke(context);
  49
+        }
  50
+
  51
+        public Task Connect(IHub hub)
  52
+        {
  53
+            EnsurePipeline();
  54
+
  55
+            return _connectPipeline.Invoke(hub);
  56
+        }
  57
+
  58
+        public Task Reconnect(IHub hub, IEnumerable<string> groups)
  59
+        {
  60
+            EnsurePipeline();
  61
+
  62
+            return _reconnectPipeline.Invoke(hub, groups);
  63
+        }
  64
+
  65
+        public Task Disconnect(IHub hub)
  66
+        {
  67
+            EnsurePipeline();
  68
+
  69
+            return _disconnectPipeline.Invoke(hub);
  70
+        }
  71
+
  72
+        public Task Send(IHubOutgoingInvokerContext context)
  73
+        {
  74
+            EnsurePipeline();
  75
+
  76
+            return _outgoingPipeling.Invoke(context);
  77
+        }
  78
+
  79
+        private class NoopModule : IHubPipelineModule
  80
+        {
  81
+            public Func<IHubIncomingInvokerContext, Task<object>> BuildIncoming(Func<IHubIncomingInvokerContext, Task<object>> invoke)
  82
+            {
  83
+                return invoke;
  84
+            }
  85
+
  86
+            public Func<IHubOutgoingInvokerContext, Task> BuildOutgoing(Func<IHubOutgoingInvokerContext, Task> send)
  87
+            {
  88
+                return send;
  89
+            }
  90
+
  91
+            public Func<IHub, Task> BuildConnect(Func<IHub, Task> connect)
  92
+            {
  93
+                return connect;
  94
+            }
  95
+
  96
+            public Func<IHub, IEnumerable<string>, Task> BuildReconnect(Func<IHub, IEnumerable<string>, Task> reconnect)
  97
+            {
  98
+                return reconnect;
  99
+            }
  100
+
  101
+            public Func<IHub, Task> BuildDisconnect(Func<IHub, Task> disconnect)
  102
+            {
  103
+                return disconnect;
  104
+            }
  105
+        }
  106
+
  107
+        private class ComposedModule : IHubPipelineModule
  108
+        {
  109
+            private readonly IHubPipelineModule _left;
  110
+            private readonly IHubPipelineModule _right;
  111
+
  112
+            public ComposedModule(IHubPipelineModule left, IHubPipelineModule right)
  113
+            {
  114
+                _left = left;
  115
+                _right = right;
  116
+            }
  117
+
  118
+            public Func<IHubIncomingInvokerContext, Task<object>> BuildIncoming(Func<IHubIncomingInvokerContext, Task<object>> callback)
  119
+            {
  120
+                return context =>
  121
+                {
  122
+                    return _left.BuildIncoming(_right.BuildIncoming(callback))(context);
  123
+                };
  124
+            }
  125
+
  126
+            public Func<IHub, Task> BuildConnect(Func<IHub, Task> callback)
  127
+            {
  128
+                return hub =>
  129
+                {
  130
+                    return _left.BuildConnect(_right.BuildConnect(callback))(hub);
  131
+                };
  132
+            }
  133
+
  134
+            public Func<IHub, IEnumerable<string>, Task> BuildReconnect(Func<IHub, IEnumerable<string>, Task> callback)
  135
+            {
  136
+                return (hub, groups) =>
  137
+                {
  138
+                    return _left.BuildReconnect(_right.BuildReconnect(callback))(hub, groups);
  139
+                };
  140
+            }
  141
+
  142
+            public Func<IHub, Task> BuildDisconnect(Func<IHub, Task> callback)
  143
+            {
  144
+                return hub =>
  145
+                {
  146
+                    return _left.BuildDisconnect(_right.BuildDisconnect(callback))(hub);
  147
+                };
  148
+            }
  149
+
  150
+            public Func<IHubOutgoingInvokerContext, Task> BuildOutgoing(Func<IHubOutgoingInvokerContext, Task> send)
  151
+            {
  152
+                return context =>
  153
+                {
  154
+                    return _left.BuildOutgoing(_right.BuildOutgoing(send))(context);
  155
+                };
  156
+            }
  157
+        }
  158
+    }
  159
+}
67  SignalR/Hubs/Pipeline/HubPipelineModule.cs
... ...
@@ -0,0 +1,67 @@
  1
+using System;
  2
+using System.Collections.Generic;
  3
+using System.Threading.Tasks;
  4
+
  5
+namespace SignalR.Hubs
  6
+{
  7
+    public class HubPipelineModule : IHubPipelineModule
  8
+    {
  9
+        public virtual Func<IHubIncomingInvokerContext, Task<object>> BuildIncoming(Func<IHubIncomingInvokerContext, Task<object>> invoke)
  10
+        {
  11
+            return context =>
  12
+            {
  13
+                OnBeforeInvoke(context);
  14
+                return invoke(context).Then(result =>
  15
+                {
  16
+                    OnAfterInvoke(result, context);
  17
+                    return result;
  18
+                })
  19
+                .Catch(ex => OnInvokeError(ex));
  20
+            };
  21
+        }
  22
+
  23
+        public virtual Func<IHub, Task> BuildConnect(Func<IHub, Task> connect)
  24
+        {
  25
+            return connect;
  26
+        }
  27
+
  28
+        public virtual Func<IHub, IEnumerable<string>, Task> BuildReconnect(Func<IHub, IEnumerable<string>, Task> reconnect)
  29
+        {
  30
+            return reconnect;
  31
+        }
  32
+
  33
+        public virtual Func<IHub, Task> BuildDisconnect(Func<IHub, Task> disconnect)
  34
+        {
  35
+            return disconnect;
  36
+        }
  37
+
  38
+        public Func<IHubOutgoingInvokerContext, Task> BuildOutgoing(Func<IHubOutgoingInvokerContext, Task> send)
  39
+        {
  40
+            return context =>
  41
+            {
  42
+                OnBeforeOutgoing(context);
  43
+                return send(context);
  44
+            };
  45
+        }
  46
+
  47
+        protected virtual void OnBeforeOutgoing(IHubOutgoingInvokerContext context)
  48
+        {
  49
+            
  50
+        }
  51
+
  52
+        protected virtual void OnBeforeInvoke(IHubIncomingInvokerContext context)
  53
+        {
  54
+
  55
+        }
  56
+
  57
+        protected virtual void OnAfterInvoke(object result, IHubIncomingInvokerContext context)
  58
+        {
  59
+
  60
+        }
  61
+
  62
+        protected virtual void OnInvokeError(Exception ex)
  63
+        {
  64
+
  65
+        }
  66
+    }
  67
+}
29  SignalR/Hubs/Pipeline/IHubIncomingInvokerContext.cs
... ...
@@ -0,0 +1,29 @@
  1
+
  2
+namespace SignalR.Hubs
  3
+{
  4
+    /// <summary>
  5
+    /// 
  6
+    /// </summary>
  7
+    public interface IHubIncomingInvokerContext
  8
+    {
  9
+        /// <summary>
  10
+        /// 
  11
+        /// </summary>
  12
+        IHub Hub { get; }
  13
+
  14
+        /// <summary>
  15
+        /// 
  16
+        /// </summary>
  17
+        MethodDescriptor MethodDescriptor { get; }
  18
+
  19
+        /// <summary>
  20
+        /// 
  21
+        /// </summary>
  22
+        object[] Args { get; }
  23
+
  24
+        /// <summary>
  25
+        /// 
  26
+        /// </summary>
  27
+        TrackingDictionary State { get; }
  28
+    }
  29
+}
23  SignalR/Hubs/Pipeline/IHubOutgoingInvokerContext.cs
... ...
@@ -0,0 +1,23 @@
  1
+namespace SignalR.Hubs
  2
+{
  3
+    /// <summary>
  4
+    /// 
  5
+    /// </summary>
  6
+    public interface IHubOutgoingInvokerContext
  7
+    {
  8
+        /// <summary>
  9
+        /// 
  10
+        /// </summary>
  11
+        IConnection Connection { get; }
  12
+
  13
+        /// <summary>
  14
+        /// 
  15
+        /// </summary>
  16
+        ClientHubInvocation Invocation { get; }
  17
+
  18
+        /// <summary>
  19
+        /// 
  20
+        /// </summary>
  21
+        string Signal { get; }
  22
+    }
  23
+}
15  SignalR/Hubs/Pipeline/IHubPipeline.cs
... ...
@@ -0,0 +1,15 @@
  1
+namespace SignalR.Hubs
  2
+{
  3
+    /// <summary>
  4
+    /// 
  5
+    /// </summary>
  6
+    public interface IHubPipeline
  7
+    {
  8
+        /// <summary>
  9
+        /// 
  10
+        /// </summary>
  11
+        /// <param name="module"></param>
  12
+        /// <returns></returns>
  13
+        IHubPipeline AddModule(IHubPipelineModule module);
  14
+    }
  15
+}
47  SignalR/Hubs/Pipeline/IHubPipelineInvoker.cs
... ...
@@ -0,0 +1,47 @@
  1
+using System.Collections.Generic;
  2
+using System.Threading.Tasks;
  3
+
  4
+namespace SignalR.Hubs
  5
+{
  6
+    /// <summary>
  7
+    /// 
  8
+    /// </summary>
  9
+    public interface IHubPipelineInvoker
  10
+    {
  11
+        /// <summary>
  12
+        /// 
  13
+        /// </summary>
  14
+        /// <param name="context"></param>
  15
+        /// <returns></returns>
  16
+        Task<object> Invoke(IHubIncomingInvokerContext context);
  17
+
  18
+        /// <summary>
  19
+        /// 
  20
+        /// </summary>
  21
+        /// <param name="context"></param>
  22
+        /// <returns></returns>
  23
+        Task Send(IHubOutgoingInvokerContext context);
  24
+
  25
+        /// <summary>
  26
+        /// 
  27
+        /// </summary>
  28
+        /// <param name="hub"></param>
  29
+        /// <returns></returns>
  30
+        Task Connect(IHub hub);
  31
+
  32
+        /// <summary>
  33
+        /// 
  34
+        /// </summary>
  35
+        /// <param name="hub"></param>
  36
+        /// <param name="groups"></param>
  37
+        /// <returns></returns>
  38
+        Task Reconnect(IHub hub, IEnumerable<string> groups);
  39
+
  40
+        /// <summary>
  41
+        /// 
  42
+        /// </summary>
  43
+        /// <param name="hub"></param>
  44
+        /// <returns></returns>
  45
+        Task Disconnect(IHub hub);
  46
+    }
  47
+}
48  SignalR/Hubs/Pipeline/IHubPipelineModule.cs
... ...
@@ -0,0 +1,48 @@
  1
+using System;
  2
+using System.Collections.Generic;
  3
+using System.Threading.Tasks;
  4
+
  5
+namespace SignalR.Hubs
  6
+{
  7
+    /// <summary>
  8
+    /// 
  9
+    /// </summary>
  10
+    public interface IHubPipelineModule
  11
+    {
  12
+        /// <summary>
  13
+        /// 
  14
+        /// </summary>
  15
+        /// <param name="invoke"></param>
  16
+        /// <returns></returns>
  17
+        Func<IHubIncomingInvokerContext, Task<object>> BuildIncoming(Func<IHubIncomingInvokerContext, Task<object>> invoke);
  18
+
  19
+
  20
+        /// <summary>
  21
+        /// 
  22
+        /// </summary>
  23
+        /// <param name="send"></param>
  24
+        /// <returns></returns>
  25
+        Func<IHubOutgoingInvokerContext, Task> BuildOutgoing(Func<IHubOutgoingInvokerContext, Task> send);
  26
+
  27
+        /// <summary>
  28
+        /// 
  29
+        /// </summary>
  30
+        /// <param name="connect"></param>
  31
+        /// <returns></returns>
  32
+        Func<IHub, Task> BuildConnect(Func<IHub, Task> connect);
  33
+
  34
+        /// <summary>
  35
+        /// 
  36
+        /// </summary>
  37
+        /// <param name="reconnect"></param>
  38
+        /// <returns></returns>
  39
+        Func<IHub, IEnumerable<string>, Task> BuildReconnect(Func<IHub, IEnumerable<string>, Task> reconnect);
  40
+
  41
+        /// <summary>
  42
+        /// 
  43
+        /// </summary>
  44
+        /// <param name="disconnect"></param>
  45
+        /// <returns></returns>
  46
+        Func<IHub, Task> BuildDisconnect(Func<IHub, Task> disconnect);
  47
+    }
  48
+}
18  SignalR/Hubs/SignalProxy.cs
... ...
@@ -1,17 +1,18 @@
1  
-using System.Dynamic;
  1
+using System;
  2
+using System.Dynamic;
2 3
 using System.Threading.Tasks;
3 4
 
4 5
 namespace SignalR.Hubs
5 6
 {
6 7
     public class SignalProxy : DynamicObject, IClientProxy
7 8
     {
8  
-        protected readonly IConnection _connection;
  9
+        protected readonly Func<string, ClientHubInvocation, Task> _send;
9 10
         protected readonly string _signal;
10 11
         protected readonly string _hubName;
11 12
 
12  
-        public SignalProxy(IConnection connection, string signal, string hubName)
  13
+        public SignalProxy(Func<string, ClientHubInvocation, Task> send, string signal, string hubName)
13 14
         {
14  
-            _connection = connection;
  15
+            _send = send;
15 16
             _signal = signal;
16 17
             _hubName = hubName;
17 18
         }
@@ -34,16 +35,17 @@ public Task Invoke(string method, params object[] args)
34 35
 
35 36
             string signal = _hubName + "." + _signal;
36 37
 
37  
-            return _connection.Send(signal, invocation);
  38
+            return _send.Invoke(signal, invocation);
38 39
         }
39 40
 
40  
-        protected virtual object GetInvocationData(string method, object[] args)
  41
+        protected virtual ClientHubInvocation GetInvocationData(string method, object[] args)
41 42
         {
42  
-            return new
  43
+            return new ClientHubInvocation
43 44
             {
44 45
                 Hub = _hubName,
45 46
                 Method = method,
46  
-                Args = args
  47
+                Args = args,
  48
+                GroupOrConnectionId = _signal
47 49
             };
48 50
         }
49 51
     }
15  SignalR/Hubs/StatefulSignalProxy.cs
... ...
@@ -1,4 +1,6 @@
1  
-using System.Dynamic;
  1
+using System;
  2
+using System.Dynamic;
  3
+using System.Threading.Tasks;
2 4
 
3 5
 namespace SignalR.Hubs
4 6
 {
@@ -6,8 +8,8 @@ public class StatefulSignalProxy : SignalProxy
6 8
     {
7 9
         private readonly TrackingDictionary _state;
8 10
 
9  
-        public StatefulSignalProxy(IConnection connection, string signal, string hubName, TrackingDictionary state)
10  
-            : base(connection, signal, hubName)
  11
+        public StatefulSignalProxy(Func<string, ClientHubInvocation, Task> send, string signal, string hubName, TrackingDictionary state)
  12
+            : base(send, signal, hubName)
11 13
         {
12 14
             _state = state;
13 15
         }
@@ -23,14 +25,15 @@ public override bool TryGetMember(GetMemberBinder binder, out object result)
23 25
             result = _state[binder.Name];
24 26
             return true;
25 27
         }
26  
-        
27  
-        protected override object GetInvocationData(string method, object[] args)
  28
+
  29
+        protected override ClientHubInvocation GetInvocationData(string method, object[] args)
28 30
         {
29  
-            return new
  31
+            return new ClientHubInvocation
30 32
             {
31 33
                 Hub = _hubName,
32 34
                 Method = method,
33 35
                 Args = args,
  36
+                GroupOrConnectionId = _signal,
34 37
                 State = _state.GetChanges()
35 38
             };
36 39
         }
5  SignalR/Infrastructure/DefaultDependencyResolver.cs
@@ -83,6 +83,11 @@ private void RegisterHubExtensions()
83 83
 
84 84
             var assemblyLocator = new Lazy<DefaultAssemblyLocator>(() => new DefaultAssemblyLocator());
85 85
             Register(typeof(IAssemblyLocator), () => assemblyLocator.Value);
  86
+
  87
+            // Setup the default hub pipeline
  88
+            var dispatcher = new Lazy<IHubPipeline>(() => new HubPipeline());
  89
+            Register(typeof(IHubPipeline), () => dispatcher.Value);
  90
+            Register(typeof(IHubPipelineInvoker), () => dispatcher.Value);
86 91
         }
87 92
 
88 93
         public virtual object GetService(Type serviceType)
10  SignalR/SignalR.csproj
@@ -63,7 +63,13 @@
63 63
     <Compile Include="Hosting\IWebSocket.cs" />
64 64
     <Compile Include="Hosting\ResponseExtensions.cs" />
65 65
     <Compile Include="Hubs\NullClientProxy.cs" />
  66
+    <Compile Include="Hubs\Pipeline\HubOutgoingInvokerContext.cs" />
  67
+    <Compile Include="Hubs\ClientHubInvocation.cs" />
66 68
     <Compile Include="Hubs\HubContext.cs" />
  69
+    <Compile Include="Hubs\Pipeline\HubPipelineModule.cs" />
  70
+    <Compile Include="Hubs\Pipeline\IHubOutgoingInvokerContext.cs" />
  71
+    <Compile Include="Hubs\Pipeline\IHubPipelineInvoker.cs" />
  72
+    <Compile Include="Hubs\Pipeline\HubInvokerContext.cs" />
67 73
     <Compile Include="Hubs\HubRequest.cs" />
68 74
     <Compile Include="Hubs\HubRequestParser.cs" />
69 75
     <Compile Include="Hubs\HubResponse.cs" />
@@ -72,6 +78,10 @@
72 78
     <Compile Include="Hubs\IHubRequestParser.cs" />
73 79
     <Compile Include="Hubs\Lookup\Descriptors\Descriptor.cs" />
74 80
     <Compile Include="IAckHandler.cs" />
  81
+    <Compile Include="Hubs\Pipeline\HubPipeline.cs" />
  82
+    <Compile Include="Hubs\Pipeline\IHubIncomingInvokerContext.cs" />
  83
+    <Compile Include="Hubs\Pipeline\IHubPipeline.cs" />
  84
+    <Compile Include="Hubs\Pipeline\IHubPipelineModule.cs" />
75 85
     <Compile Include="Infrastructure\DisposableAction.cs" />
76 86
     <Compile Include="Infrastructure\IPerformanceCounterWriter.cs" />
77 87
     <Compile Include="Infrastructure\PerformanceCounterExtensions.cs" />
19  samples/SignalR.Hosting.AspNet.Samples/Global.asax.cs
... ...
@@ -1,8 +1,11 @@
1 1
 using System;
  2
+using System.Collections.Generic;
2 3
 using System.Configuration;
3 4
 using System.Diagnostics;
4 5
 using System.Threading;
  6
+using System.Threading.Tasks;
5 7
 using System.Web.Routing;
  8
+using SignalR.Hubs;
6 9
 using SignalR.Samples.Hubs.DemoHub;
7 10
 using SignalR.Samples.Raw;
8 11
 using SignalR.Samples.Streaming;
@@ -15,6 +18,7 @@ public class Global : System.Web.HttpApplication
15 18
         protected void Application_Start(object sender, EventArgs e)
16 19
         {
17 20
             //GlobalHost.DependencyResolver.UseSqlServer(ConfigurationManager.ConnectionStrings["SignalRSamples"].ConnectionString);
  21
+            GlobalHost.HubPipeline.AddModule(new SamplePipelineModule());
18 22
 
19 23
             ThreadPool.QueueUserWorkItem(_ =>
20 24
             {
@@ -43,5 +47,20 @@ protected void Application_Start(object sender, EventArgs e)
43 47
             RouteTable.Routes.MapConnection<Raw>("raw", "raw/{*operation}");
44 48
             RouteTable.Routes.MapConnection<Streaming>("streaming", "streaming/{*operation}");
45 49
         }
  50
+
  51
+        private class SamplePipelineModule : HubPipelineModule
  52
+        {
  53
+            protected override void OnBeforeInvoke(IHubIncomingInvokerContext context)
  54
+            {
  55
+                Debug.WriteLine("=> Invoking " + context.MethodDescriptor.Name + " on hub " + context.MethodDescriptor.Hub.Name);
  56
+                base.OnBeforeInvoke(context);
  57
+            }
  58
+
  59
+            protected override void OnBeforeOutgoing(IHubOutgoingInvokerContext context)
  60
+            {
  61
+                Debug.WriteLine("<= Invoking " + context.Invocation.Method + " on client hub " + context.Invocation.Hub); 
  62
+                base.OnBeforeOutgoing(context);
  63
+            }
  64
+        }
46 65
     }
47 66
 }

0 notes on commit 83fdbfd

Please sign in to comment.
Something went wrong with that request. Please try again.