imported signalr 1.1.3 into NzbDrone.

This commit is contained in:
kayone
2013-11-21 21:26:57 -08:00
parent 891443e05d
commit 0e623e7ce4
236 changed files with 20490 additions and 35 deletions
@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// A description of a client-side hub method invocation.
/// </summary>
public class ClientHubInvocation
{
/// <summary>
/// The signal that clients receiving this invocation are subscribed to.
/// </summary>
[JsonIgnore]
public string Target { get; set; }
/// <summary>
/// The name of the hub that the method being invoked belongs to.
/// </summary>
[JsonProperty("H")]
public string Hub { get; set; }
/// <summary>
/// The name of the client-side hub method be invoked.
/// </summary>
[JsonProperty("M")]
public string Method { get; set; }
/// <summary>
/// The argument list the client-side hub method will be called with.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Type is used for serialization.")]
[JsonProperty("A")]
public object[] Args { get; set; }
/// <summary>
/// A key-value store representing the hub state on the server that has changed since the last time the hub
/// state was sent to the client.
/// </summary>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Type is used for serialization.")]
[JsonProperty("S", NullValueHandling = NullValueHandling.Ignore)]
public IDictionary<string, object> State { get; set; }
}
}
@@ -0,0 +1,44 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class ClientProxy : DynamicObject, IClientProxy
{
private readonly Func<string, ClientHubInvocation, IList<string>, Task> _send;
private readonly string _hubName;
private readonly IList<string> _exclude;
public ClientProxy(Func<string, ClientHubInvocation, IList<string>, Task> send, string hubName, IList<string> exclude)
{
_send = send;
_hubName = hubName;
_exclude = exclude;
}
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Binder is passed in by the DLR")]
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
result = Invoke(binder.Name, args);
return true;
}
public Task Invoke(string method, params object[] args)
{
var invocation = new ClientHubInvocation
{
Hub = _hubName,
Method = method,
Args = args
};
return _send(PrefixHelper.GetHubName(_hubName), invocation, _exclude);
}
}
}
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class ConnectionIdProxy : SignalProxy
{
public ConnectionIdProxy(Func<string, ClientHubInvocation, IList<string>, Task> send, string signal, string hubName, params string[] exclude) :
base(send, signal, hubName, PrefixHelper.HubConnectionIdPrefix, exclude)
{
}
}
}
@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class DefaultAssemblyLocator : IAssemblyLocator
{
public virtual IList<Assembly> GetAssemblies()
{
return AppDomain.CurrentDomain.GetAssemblies();
}
}
}
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class DefaultHubActivator : IHubActivator
{
private readonly IDependencyResolver _resolver;
public DefaultHubActivator(IDependencyResolver resolver)
{
_resolver = resolver;
}
public IHub Create(HubDescriptor descriptor)
{
if (descriptor == null)
{
throw new ArgumentNullException("descriptor");
}
if(descriptor.HubType == null)
{
return null;
}
object hub = _resolver.Resolve(descriptor.HubType) ?? Activator.CreateInstance(descriptor.HubType);
return hub as IHub;
}
}
}
@@ -0,0 +1,211 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNet.SignalR.Json;
using Newtonsoft.Json;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class DefaultJavaScriptProxyGenerator : IJavaScriptProxyGenerator
{
private static readonly Lazy<string> _templateFromResource = new Lazy<string>(GetTemplateFromResource);
private static readonly Type[] _numberTypes = new[] { typeof(byte), typeof(short), typeof(int), typeof(long), typeof(float), typeof(decimal), typeof(double) };
private static readonly Type[] _dateTypes = new[] { typeof(DateTime), typeof(DateTimeOffset) };
private const string ScriptResource = "Microsoft.AspNet.SignalR.Scripts.hubs.js";
private readonly IHubManager _manager;
private readonly IJavaScriptMinifier _javaScriptMinifier;
private readonly Lazy<string> _generatedTemplate;
public DefaultJavaScriptProxyGenerator(IDependencyResolver resolver) :
this(resolver.Resolve<IHubManager>(),
resolver.Resolve<IJavaScriptMinifier>())
{
}
public DefaultJavaScriptProxyGenerator(IHubManager manager, IJavaScriptMinifier javaScriptMinifier)
{
_manager = manager;
_javaScriptMinifier = javaScriptMinifier ?? NullJavaScriptMinifier.Instance;
_generatedTemplate = new Lazy<string>(() => GenerateProxy(_manager, _javaScriptMinifier, includeDocComments: false));
}
public string GenerateProxy(string serviceUrl)
{
serviceUrl = JavaScriptEncode(serviceUrl);
var generateProxy = _generatedTemplate.Value;
return generateProxy.Replace("{serviceUrl}", serviceUrl);
}
public string GenerateProxy(string serviceUrl, bool includeDocComments)
{
serviceUrl = JavaScriptEncode(serviceUrl);
string generateProxy = GenerateProxy(_manager, _javaScriptMinifier, includeDocComments);
return generateProxy.Replace("{serviceUrl}", serviceUrl);
}
private static string GenerateProxy(IHubManager hubManager, IJavaScriptMinifier javaScriptMinifier, bool includeDocComments)
{
string script = _templateFromResource.Value;
var hubs = new StringBuilder();
var first = true;
foreach (var descriptor in hubManager.GetHubs().OrderBy(h => h.Name))
{
if (!first)
{
hubs.AppendLine(";");
hubs.AppendLine();
hubs.Append(" ");
}
GenerateType(hubManager, hubs, descriptor, includeDocComments);
first = false;
}
if (hubs.Length > 0)
{
hubs.Append(";");
}
script = script.Replace("/*hubs*/", hubs.ToString());
return javaScriptMinifier.Minify(script);
}
private static void GenerateType(IHubManager hubManager, StringBuilder sb, HubDescriptor descriptor, bool includeDocComments)
{
// Get only actions with minimum number of parameters.
var methods = GetMethods(hubManager, descriptor);
var hubName = GetDescriptorName(descriptor);
sb.AppendFormat(" proxies.{0} = this.createHubProxy('{1}'); ", hubName, hubName).AppendLine();
sb.AppendFormat(" proxies.{0}.client = {{ }};", hubName).AppendLine();
sb.AppendFormat(" proxies.{0}.server = {{", hubName);
bool first = true;
foreach (var method in methods)
{
if (!first)
{
sb.Append(",").AppendLine();
}
GenerateMethod(sb, method, includeDocComments, hubName);
first = false;
}
sb.AppendLine();
sb.Append(" }");
}
private static string GetDescriptorName(Descriptor descriptor)
{
if (descriptor == null)
{
throw new ArgumentNullException("descriptor");
}
string name = descriptor.Name;
// If the name was not specified then do not camel case
if (!descriptor.NameSpecified)
{
name = JsonUtility.CamelCase(name);
}
return name;
}
private static IEnumerable<MethodDescriptor> GetMethods(IHubManager manager, HubDescriptor descriptor)
{
return from method in manager.GetHubMethods(descriptor.Name)
group method by method.Name into overloads
let oload = (from overload in overloads
orderby overload.Parameters.Count
select overload).FirstOrDefault()
orderby oload.Name
select oload;
}
private static void GenerateMethod(StringBuilder sb, MethodDescriptor method, bool includeDocComments, string hubName)
{
var parameterNames = method.Parameters.Select(p => p.Name).ToList();
sb.AppendLine();
sb.AppendFormat(" {0}: function ({1}) {{", GetDescriptorName(method), Commas(parameterNames)).AppendLine();
if (includeDocComments)
{
sb.AppendFormat(Resources.DynamicComment_CallsMethodOnServerSideDeferredPromise, method.Name, method.Hub.Name).AppendLine();
var parameterDoc = method.Parameters.Select(p => String.Format(CultureInfo.CurrentCulture, Resources.DynamicComment_ServerSideTypeIs, p.Name, MapToJavaScriptType(p.ParameterType), p.ParameterType)).ToList();
if (parameterDoc.Any())
{
sb.AppendLine(String.Join(Environment.NewLine, parameterDoc));
}
}
sb.AppendFormat(" return proxies.{0}.invoke.apply(proxies.{0}, $.merge([\"{1}\"], $.makeArray(arguments)));", hubName, method.Name).AppendLine();
sb.Append(" }");
}
private static string MapToJavaScriptType(Type type)
{
if (!type.IsPrimitive && !(type == typeof(string)))
{
return "Object";
}
if (type == typeof(string))
{
return "String";
}
if (_numberTypes.Contains(type))
{
return "Number";
}
if (typeof(IEnumerable).IsAssignableFrom(type))
{
return "Array";
}
if (_dateTypes.Contains(type))
{
return "Date";
}
return String.Empty;
}
private static string Commas(IEnumerable<string> values)
{
return Commas(values, v => v);
}
private static string Commas<T>(IEnumerable<T> values, Func<T, string> selector)
{
return String.Join(", ", values.Select(selector));
}
private static string GetTemplateFromResource()
{
using (Stream resourceStream = typeof(DefaultJavaScriptProxyGenerator).Assembly.GetManifestResourceStream(ScriptResource))
{
var reader = new StreamReader(resourceStream);
return reader.ReadToEnd();
}
}
private static string JavaScriptEncode(string value)
{
value = JsonConvert.SerializeObject(value);
// Remove the quotes
return value.Substring(1, value.Length - 2);
}
}
}
@@ -0,0 +1,144 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class DynamicDictionary : DynamicObject, IDictionary<string, object>
{
private readonly IDictionary<string, object> _obj;
public DynamicDictionary(IDictionary<string, object> obj)
{
_obj = obj;
}
public object this[string key]
{
get
{
object result;
_obj.TryGetValue(key, out result);
return Wrap(result);
}
set
{
_obj[key] = Unwrap(value);
}
}
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "The compiler generates calls to invoke this")]
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = this[binder.Name];
return true;
}
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "The compiler generates calls to invoke this")]
public override bool TrySetMember(SetMemberBinder binder, object value)
{
this[binder.Name] = value;
return true;
}
public static object Wrap(object value)
{
var obj = value as IDictionary<string, object>;
if (obj != null)
{
return new DynamicDictionary(obj);
}
return value;
}
public static object Unwrap(object value)
{
var dictWrapper = value as DynamicDictionary;
if (dictWrapper != null)
{
return dictWrapper._obj;
}
return value;
}
public void Add(string key, object value)
{
_obj.Add(key, value);
}
public bool ContainsKey(string key)
{
return _obj.ContainsKey(key);
}
public ICollection<string> Keys
{
get { return _obj.Keys; }
}
public bool Remove(string key)
{
return _obj.Remove(key);
}
public bool TryGetValue(string key, out object value)
{
return _obj.TryGetValue(key, out value);
}
public ICollection<object> Values
{
get { return _obj.Values; }
}
public void Add(KeyValuePair<string, object> item)
{
_obj.Add(item);
}
public void Clear()
{
_obj.Clear();
}
public bool Contains(KeyValuePair<string, object> item)
{
return _obj.Contains(item);
}
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
_obj.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _obj.Count; }
}
public bool IsReadOnly
{
get { return _obj.IsReadOnly; }
}
public bool Remove(KeyValuePair<string, object> item)
{
return _obj.Remove(item);
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return _obj.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Globalization;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class EmptyJavaScriptProxyGenerator : IJavaScriptProxyGenerator
{
public string GenerateProxy(string serviceUrl)
{
return String.Format(CultureInfo.InvariantCulture, "throw new Error('{0}');", Resources.Error_JavaScriptProxyDisabled);
}
}
}
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using Microsoft.AspNet.SignalR.Infrastructure;
namespace Microsoft.AspNet.SignalR.Hubs
{
public static class HubManagerExtensions
{
public static HubDescriptor EnsureHub(this IHubManager hubManager, string hubName, params IPerformanceCounter[] counters)
{
if (hubManager == null)
{
throw new ArgumentNullException("hubManager");
}
if (String.IsNullOrEmpty(hubName))
{
throw new ArgumentNullException("hubName");
}
if (counters == null)
{
throw new ArgumentNullException("counters");
}
var descriptor = hubManager.GetHub(hubName);
if (descriptor == null)
{
for (var i = 0; i < counters.Length; i++)
{
counters[i].Increment();
}
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_HubCouldNotBeResolved, hubName));
}
return descriptor;
}
public static IEnumerable<HubDescriptor> GetHubs(this IHubManager hubManager)
{
if (hubManager == null)
{
throw new ArgumentNullException("hubManager");
}
return hubManager.GetHubs(d => true);
}
public static IEnumerable<MethodDescriptor> GetHubMethods(this IHubManager hubManager, string hubName)
{
if (hubManager == null)
{
throw new ArgumentNullException("hubManager");
}
return hubManager.GetHubMethods(hubName, m => true);
}
}
}
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
namespace Microsoft.AspNet.SignalR.Hubs
{
internal static class HubTypeExtensions
{
internal static string GetHubName(this Type type)
{
if (!typeof(IHub).IsAssignableFrom(type))
{
return null;
}
return GetHubAttributeName(type) ?? type.Name;
}
internal static string GetHubAttributeName(this Type type)
{
if (!typeof(IHub).IsAssignableFrom(type))
{
return null;
}
// We can still return null if there is no attribute name
return ReflectionHelper.GetAttributeValue<HubNameAttribute, string>(type, attr => attr.HubName);
}
}
}
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.SignalR.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.SignalR.Hubs
{
public static class MethodExtensions
{
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "The condition checks for null parameters")]
public static bool Matches(this MethodDescriptor methodDescriptor, IList<IJsonValue> parameters)
{
if (methodDescriptor == null)
{
throw new ArgumentNullException("methodDescriptor");
}
if ((methodDescriptor.Parameters.Count > 0 && parameters == null)
|| methodDescriptor.Parameters.Count != parameters.Count)
{
return false;
}
return true;
}
}
}
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class GroupProxy : SignalProxy
{
public GroupProxy(Func<string, ClientHubInvocation, IList<string>, Task> send, string signal, string hubName, IList<string> exclude) :
base(send, signal, hubName, PrefixHelper.HubGroupPrefix, exclude)
{
}
}
}
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Security.Principal;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class HubCallerContext
{
/// <summary>
/// Gets the connection id of the calling client.
/// </summary>
public string ConnectionId { get; private set; }
/// <summary>
/// Gets the cookies for the request.
/// </summary>
public IDictionary<string, Cookie> RequestCookies
{
get
{
return Request.Cookies;
}
}
/// <summary>
/// Gets the headers for the request.
/// </summary>
public NameValueCollection Headers
{
get
{
return Request.Headers;
}
}
/// <summary>
/// Gets the querystring for the request.
/// </summary>
public NameValueCollection QueryString
{
get
{
return Request.QueryString;
}
}
/// <summary>
/// Gets the <see cref="IPrincipal"/> for the request.
/// </summary>
public IPrincipal User
{
get
{
return Request.User;
}
}
/// <summary>
/// Gets the <see cref="IRequest"/> for the current HTTP request.
/// </summary>
public IRequest Request { get; private set; }
public HubCallerContext(IRequest request, string connectionId)
{
ConnectionId = connectionId;
Request = request;
}
}
}
@@ -0,0 +1,111 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Encapsulates all information about an individual SignalR connection for an <see cref="IHub"/>.
/// </summary>
public class HubConnectionContext : IHubConnectionContext
{
private readonly string _hubName;
private readonly string _connectionId;
private readonly Func<string, ClientHubInvocation, IList<string>, Task> _send;
/// <summary>
/// Initializes a new instance of the <see cref="HubConnectionContext"/>.
/// </summary>
public HubConnectionContext()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="HubConnectionContext"/>.
/// </summary>
/// <param name="pipelineInvoker">The pipeline invoker.</param>
/// <param name="connection">The connection.</param>
/// <param name="hubName">The hub name.</param>
/// <param name="connectionId">The connection id.</param>
/// <param name="tracker">The connection hub state.</param>
public HubConnectionContext(IHubPipelineInvoker pipelineInvoker, IConnection connection, string hubName, string connectionId, StateChangeTracker tracker)
{
_send = (signal, invocation, exclude) => pipelineInvoker.Send(new HubOutgoingInvokerContext(connection, signal, invocation, exclude));
_connectionId = connectionId;
_hubName = hubName;
Caller = new StatefulSignalProxy(_send, connectionId, PrefixHelper.HubConnectionIdPrefix, hubName, tracker);
All = AllExcept();
Others = AllExcept(connectionId);
}
/// <summary>
/// All connected clients.
/// </summary>
public dynamic All { get; set; }
/// <summary>
/// All connected clients except the calling client.
/// </summary>
public dynamic Others { get; set; }
/// <summary>
/// Represents the calling client.
/// </summary>
public dynamic Caller { get; set; }
/// <summary>
/// Returns a dynamic representation of all clients except the calling client ones specified.
/// </summary>
/// <param name="excludeConnectionIds">The list of connection ids to exclude</param>
/// <returns>A dynamic representation of all clients except the calling client ones specified.</returns>
public dynamic AllExcept(params string[] excludeConnectionIds)
{
return new ClientProxy(_send, _hubName, PrefixHelper.GetPrefixedConnectionIds(excludeConnectionIds));
}
/// <summary>
/// Returns a dynamic representation of all clients in a group except the calling client.
/// </summary>
/// <param name="groupName">The name of the group</param>
/// <returns>A dynamic representation of all clients in a group except the calling client.</returns>
public dynamic OthersInGroup(string groupName)
{
return Group(groupName, _connectionId);
}
/// <summary>
/// Returns a dynamic representation of the specified group.
/// </summary>
/// <param name="groupName">The name of the group</param>
/// <param name="excludeConnectionIds">The list of connection ids to exclude</param>
/// <returns>A dynamic representation of the specified group.</returns>
public dynamic Group(string groupName, params string[] excludeConnectionIds)
{
if (string.IsNullOrEmpty(groupName))
{
throw new ArgumentException(Resources.Error_ArgumentNullOrEmpty, "groupName");
}
return new GroupProxy(_send, groupName, _hubName, PrefixHelper.GetPrefixedConnectionIds(excludeConnectionIds));
}
/// <summary>
/// Returns a dynamic representation of the connection with the specified connectionid.
/// </summary>
/// <param name="connectionId">The connection id</param>
/// <returns>A dynamic representation of the specified client.</returns>
public dynamic Client(string connectionId)
{
if (string.IsNullOrEmpty(connectionId))
{
throw new ArgumentException(Resources.Error_ArgumentNullOrEmpty, "connectionId");
}
return new ConnectionIdProxy(_send, connectionId, _hubName);
}
}
}
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure;
namespace Microsoft.AspNet.SignalR.Hubs
{
internal class HubContext : IHubContext
{
public HubContext(Func<string, ClientHubInvocation, IList<string>, Task> send, string hubName, IConnection connection)
{
Clients = new ExternalHubConnectionContext(send, hubName);
Groups = new GroupManager(connection, PrefixHelper.GetHubGroupName(hubName));
}
public IHubConnectionContext Clients { get; private set; }
public IGroupManager Groups { get; private set; }
private class ExternalHubConnectionContext : IHubConnectionContext
{
private readonly Func<string, ClientHubInvocation, IList<string>, Task> _send;
private readonly string _hubName;
public ExternalHubConnectionContext(Func<string, ClientHubInvocation, IList<string>, Task> send, string hubName)
{
_send = send;
_hubName = hubName;
All = AllExcept();
}
public dynamic All
{
get;
private set;
}
public dynamic AllExcept(params string[] exclude)
{
return new ClientProxy(_send, _hubName, exclude);
}
public dynamic Group(string groupName, params string[] exclude)
{
if (string.IsNullOrEmpty(groupName))
{
throw new ArgumentException(Resources.Error_ArgumentNullOrEmpty, "groupName");
}
return new GroupProxy(_send, groupName, _hubName, exclude);
}
public dynamic Client(string connectionId)
{
if (string.IsNullOrEmpty(connectionId))
{
throw new ArgumentException(Resources.Error_ArgumentNullOrEmpty, "connectionId");
}
return new ConnectionIdProxy(_send, connectionId, _hubName);
}
}
}
}
@@ -0,0 +1,522 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Hosting;
using Microsoft.AspNet.SignalR.Infrastructure;
using Microsoft.AspNet.SignalR.Json;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Handles all communication over the hubs persistent connection.
/// </summary>
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This dispatcher makes use of many interfaces.")]
public class HubDispatcher : PersistentConnection
{
private const string HubsSuffix = "/hubs";
private readonly List<HubDescriptor> _hubs = new List<HubDescriptor>();
private readonly bool _enableJavaScriptProxies;
private readonly bool _enableDetailedErrors;
private IJavaScriptProxyGenerator _proxyGenerator;
private IHubManager _manager;
private IHubRequestParser _requestParser;
private IParameterResolver _binder;
private IHubPipelineInvoker _pipelineInvoker;
private IPerformanceCounterManager _counters;
private bool _isDebuggingEnabled;
private static readonly MethodInfo _continueWithMethod = typeof(HubDispatcher).GetMethod("ContinueWith", BindingFlags.NonPublic | BindingFlags.Static);
/// <summary>
/// Initializes an instance of the <see cref="HubDispatcher"/> class.
/// </summary>
/// <param name="configuration">Configuration settings determining whether to enable JS proxies and provide clients with detailed hub errors.</param>
public HubDispatcher(HubConfiguration configuration)
{
if (configuration == null)
{
throw new ArgumentNullException("configuration");
}
_enableJavaScriptProxies = configuration.EnableJavaScriptProxies;
_enableDetailedErrors = configuration.EnableDetailedErrors;
}
protected override TraceSource Trace
{
get
{
return TraceManager["SignalR.HubDispatcher"];
}
}
internal override string GroupPrefix
{
get
{
return PrefixHelper.HubGroupPrefix;
}
}
public override void Initialize(IDependencyResolver resolver, HostContext context)
{
if (resolver == null)
{
throw new ArgumentNullException("resolver");
}
if (context == null)
{
throw new ArgumentNullException("context");
}
_proxyGenerator = _enableJavaScriptProxies ? resolver.Resolve<IJavaScriptProxyGenerator>()
: new EmptyJavaScriptProxyGenerator();
_manager = resolver.Resolve<IHubManager>();
_binder = resolver.Resolve<IParameterResolver>();
_requestParser = resolver.Resolve<IHubRequestParser>();
_pipelineInvoker = resolver.Resolve<IHubPipelineInvoker>();
_counters = resolver.Resolve<IPerformanceCounterManager>();
base.Initialize(resolver, context);
}
protected override bool AuthorizeRequest(IRequest request)
{
// Populate _hubs
string data = request.QueryStringOrForm("connectionData");
if (!String.IsNullOrEmpty(data))
{
var clientHubInfo = JsonSerializer.Parse<IEnumerable<ClientHubInfo>>(data);
// If there's any hubs then perform the auth check
if (clientHubInfo != null && clientHubInfo.Any())
{
var hubCache = new Dictionary<string, HubDescriptor>(StringComparer.OrdinalIgnoreCase);
foreach (var hubInfo in clientHubInfo)
{
if (hubCache.ContainsKey(hubInfo.Name))
{
throw new InvalidOperationException(Resources.Error_DuplicateHubs);
}
// Try to find the associated hub type
HubDescriptor hubDescriptor = _manager.EnsureHub(hubInfo.Name,
_counters.ErrorsHubResolutionTotal,
_counters.ErrorsHubResolutionPerSec,
_counters.ErrorsAllTotal,
_counters.ErrorsAllPerSec);
if (_pipelineInvoker.AuthorizeConnect(hubDescriptor, request))
{
// Add this to the list of hub descriptors this connection is interested in
hubCache.Add(hubDescriptor.Name, hubDescriptor);
}
}
_hubs.AddRange(hubCache.Values);
// If we have any hubs in the list then we're authorized
return _hubs.Count > 0;
}
}
return base.AuthorizeRequest(request);
}
/// <summary>
/// Processes the hub's incoming method calls.
/// </summary>
protected override Task OnReceived(IRequest request, string connectionId, string data)
{
HubRequest hubRequest = _requestParser.Parse(data);
// Create the hub
HubDescriptor descriptor = _manager.EnsureHub(hubRequest.Hub,
_counters.ErrorsHubInvocationTotal,
_counters.ErrorsHubInvocationPerSec,
_counters.ErrorsAllTotal,
_counters.ErrorsAllPerSec);
IJsonValue[] parameterValues = hubRequest.ParameterValues;
// Resolve the method
MethodDescriptor methodDescriptor = _manager.GetHubMethod(descriptor.Name, hubRequest.Method, parameterValues);
if (methodDescriptor == null)
{
_counters.ErrorsHubInvocationTotal.Increment();
_counters.ErrorsHubInvocationPerSec.Increment();
// Empty (noop) method descriptor
// Use: Forces the hub pipeline module to throw an error. This error is encapsulated in the HubDispatcher.
// Encapsulating it in the HubDispatcher prevents the error from bubbling up to the transport level.
// Specifically this allows us to return a faulted task (call .fail on client) and to not cause the
// transport to unintentionally fail.
methodDescriptor = new NullMethodDescriptor(hubRequest.Method);
}
// Resolving the actual state object
var tracker = new StateChangeTracker(hubRequest.State);
var hub = CreateHub(request, descriptor, connectionId, tracker, throwIfFailedToCreate: true);
return InvokeHubPipeline(hub, parameterValues, methodDescriptor, hubRequest, tracker)
.ContinueWith(task => hub.Dispose(), TaskContinuationOptions.ExecuteSynchronously);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions are flown to the caller.")]
private Task InvokeHubPipeline(IHub hub,
IJsonValue[] parameterValues,
MethodDescriptor methodDescriptor,
HubRequest hubRequest,
StateChangeTracker tracker)
{
Task<object> piplineInvocation;
try
{
var args = _binder.ResolveMethodParameters(methodDescriptor, parameterValues);
var context = new HubInvokerContext(hub, tracker, methodDescriptor, args);
// Invoke the pipeline and save the task
piplineInvocation = _pipelineInvoker.Invoke(context);
}
catch (Exception ex)
{
piplineInvocation = TaskAsyncHelper.FromError<object>(ex);
}
// Determine if we have a faulted task or not and handle it appropriately.
return piplineInvocation.ContinueWith(task =>
{
if (task.IsFaulted)
{
return ProcessResponse(tracker, result: null, request: hubRequest, error: task.Exception);
}
else if (task.IsCanceled)
{
return ProcessResponse(tracker, result: null, request: hubRequest, error: new OperationCanceledException());
}
else
{
return ProcessResponse(tracker, task.Result, hubRequest, error: null);
}
})
.FastUnwrap();
}
public override Task ProcessRequest(HostContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
// Trim any trailing slashes
string normalized = context.Request.Url.LocalPath.TrimEnd('/');
if (normalized.EndsWith(HubsSuffix, StringComparison.OrdinalIgnoreCase))
{
// Generate the proper hub url
string hubUrl = normalized.Substring(0, normalized.Length - HubsSuffix.Length);
// Generate the proxy
context.Response.ContentType = JsonUtility.JavaScriptMimeType;
return context.Response.End(_proxyGenerator.GenerateProxy(hubUrl));
}
_isDebuggingEnabled = context.IsDebuggingEnabled();
return base.ProcessRequest(context);
}
internal static Task Connect(IHub hub)
{
return hub.OnConnected();
}
internal static Task Reconnect(IHub hub)
{
return hub.OnReconnected();
}
internal static Task Disconnect(IHub hub)
{
return hub.OnDisconnected();
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "A faulted task is returned.")]
internal static Task<object> Incoming(IHubIncomingInvokerContext context)
{
var tcs = new TaskCompletionSource<object>();
try
{
object result = context.MethodDescriptor.Invoker(context.Hub, context.Args.ToArray());
Type returnType = context.MethodDescriptor.ReturnType;
if (typeof(Task).IsAssignableFrom(returnType))
{
var task = (Task)result;
if (!returnType.IsGenericType)
{
task.ContinueWith(tcs);
}
else
{
// Get the <T> in Task<T>
Type resultType = returnType.GetGenericArguments().Single();
Type genericTaskType = typeof(Task<>).MakeGenericType(resultType);
// Get the correct ContinueWith overload
var parameter = Expression.Parameter(typeof(object));
// TODO: Cache this whole thing
// Action<object> callback = result => ContinueWith((Task<T>)result, tcs);
MethodInfo continueWithMethod = _continueWithMethod.MakeGenericMethod(resultType);
Expression body = Expression.Call(continueWithMethod,
Expression.Convert(parameter, genericTaskType),
Expression.Constant(tcs));
var continueWithInvoker = Expression.Lambda<Action<object>>(body, parameter).Compile();
continueWithInvoker.Invoke(result);
}
}
else
{
tcs.TrySetResult(result);
}
}
catch (Exception ex)
{
tcs.TrySetUnwrappedException(ex);
}
return tcs.Task;
}
internal static Task Outgoing(IHubOutgoingInvokerContext context)
{
var message = new ConnectionMessage(context.Signal, context.Invocation, context.ExcludedSignals);
return context.Connection.Send(message);
}
protected override Task OnConnected(IRequest request, string connectionId)
{
return ExecuteHubEvent(request, connectionId, hub => _pipelineInvoker.Connect(hub));
}
protected override Task OnReconnected(IRequest request, string connectionId)
{
return ExecuteHubEvent(request, connectionId, hub => _pipelineInvoker.Reconnect(hub));
}
protected override IList<string> OnRejoiningGroups(IRequest request, IList<string> groups, string connectionId)
{
return _hubs.Select(hubDescriptor =>
{
string groupPrefix = hubDescriptor.Name + ".";
var hubGroups = groups.Where(g => g.StartsWith(groupPrefix, StringComparison.OrdinalIgnoreCase))
.Select(g => g.Substring(groupPrefix.Length))
.ToList();
return _pipelineInvoker.RejoiningGroups(hubDescriptor, request, hubGroups)
.Select(g => groupPrefix + g);
}).SelectMany(groupsToRejoin => groupsToRejoin).ToList();
}
protected override Task OnDisconnected(IRequest request, string connectionId)
{
return ExecuteHubEvent(request, connectionId, hub => _pipelineInvoker.Disconnect(hub));
}
protected override IList<string> GetSignals(string connectionId)
{
return _hubs.SelectMany(info => new[] { PrefixHelper.GetHubName(info.Name), PrefixHelper.GetHubConnectionId(info.CreateQualifiedName(connectionId)) })
.Concat(new[] { PrefixHelper.GetConnectionId(connectionId), PrefixHelper.GetAck(connectionId) })
.ToList();
}
private Task ExecuteHubEvent(IRequest request, string connectionId, Func<IHub, Task> action)
{
var hubs = GetHubs(request, connectionId).ToList();
var operations = hubs.Select(instance => action(instance).Catch().OrEmpty()).ToArray();
if (operations.Length == 0)
{
DisposeHubs(hubs);
return TaskAsyncHelper.Empty;
}
var tcs = new TaskCompletionSource<object>();
Task.Factory.ContinueWhenAll(operations, tasks =>
{
DisposeHubs(hubs);
var faulted = tasks.FirstOrDefault(t => t.IsFaulted);
if (faulted != null)
{
tcs.SetUnwrappedException(faulted.Exception);
}
else if (tasks.Any(t => t.IsCanceled))
{
tcs.SetCanceled();
}
else
{
tcs.SetResult(null);
}
});
return tcs.Task;
}
private IHub CreateHub(IRequest request, HubDescriptor descriptor, string connectionId, StateChangeTracker tracker = null, bool throwIfFailedToCreate = false)
{
try
{
var hub = _manager.ResolveHub(descriptor.Name);
if (hub != null)
{
tracker = tracker ?? new StateChangeTracker();
hub.Context = new HubCallerContext(request, connectionId);
hub.Clients = new HubConnectionContext(_pipelineInvoker, Connection, descriptor.Name, connectionId, tracker);
hub.Groups = new GroupManager(Connection, PrefixHelper.GetHubGroupName(descriptor.Name));
}
return hub;
}
catch (Exception ex)
{
Trace.TraceInformation(String.Format(CultureInfo.CurrentCulture, Resources.Error_ErrorCreatingHub + ex.Message, descriptor.Name));
if (throwIfFailedToCreate)
{
throw;
}
return null;
}
}
private IEnumerable<IHub> GetHubs(IRequest request, string connectionId)
{
return from descriptor in _hubs
select CreateHub(request, descriptor, connectionId) into hub
where hub != null
select hub;
}
private static void DisposeHubs(IEnumerable<IHub> hubs)
{
foreach (var hub in hubs)
{
hub.Dispose();
}
}
private Task ProcessResponse(StateChangeTracker tracker, object result, HubRequest request, Exception error)
{
var hubResult = new HubResponse
{
State = tracker.GetChanges(),
Result = result,
Id = request.Id,
};
if (error != null)
{
_counters.ErrorsHubInvocationTotal.Increment();
_counters.ErrorsHubInvocationPerSec.Increment();
_counters.ErrorsAllTotal.Increment();
_counters.ErrorsAllPerSec.Increment();
if (_enableDetailedErrors)
{
var exception = error.InnerException ?? error;
hubResult.StackTrace = _isDebuggingEnabled ? exception.StackTrace : null;
hubResult.Error = exception.Message;
}
else
{
hubResult.Error = String.Format(CultureInfo.CurrentCulture, Resources.Error_HubInvocationFailed, request.Hub, request.Method);
}
}
return Transport.Send(hubResult);
}
private static void ContinueWith<T>(Task<T> task, TaskCompletionSource<object> tcs)
{
if (task.IsCompleted)
{
// Fast path for tasks that completed synchronously
ContinueSync<T>(task, tcs);
}
else
{
ContinueAsync<T>(task, tcs);
}
}
private static void ContinueSync<T>(Task<T> task, TaskCompletionSource<object> tcs)
{
if (task.IsFaulted)
{
tcs.TrySetUnwrappedException(task.Exception);
}
else if (task.IsCanceled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(task.Result);
}
}
private static void ContinueAsync<T>(Task<T> task, TaskCompletionSource<object> tcs)
{
task.ContinueWith(t =>
{
if (t.IsFaulted)
{
tcs.TrySetUnwrappedException(t.Exception);
}
else if (t.IsCanceled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(t.Result);
}
});
}
[SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "It is instantiated through JSON deserialization.")]
private class ClientHubInfo
{
public string Name { get; set; }
}
}
}
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
namespace Microsoft.AspNet.SignalR.Hubs
{
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class HubMethodNameAttribute : Attribute
{
public HubMethodNameAttribute(string methodName)
{
if (String.IsNullOrEmpty(methodName))
{
throw new ArgumentNullException("methodName");
}
MethodName = methodName;
}
public string MethodName
{
get;
private set;
}
}
}
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
namespace Microsoft.AspNet.SignalR.Hubs
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class HubNameAttribute : Attribute
{
public HubNameAttribute(string hubName)
{
if (String.IsNullOrEmpty(hubName))
{
throw new ArgumentNullException("hubName");
}
HubName = hubName;
}
public string HubName
{
get;
private set;
}
}
}
@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.SignalR.Json;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class HubRequest
{
public string Hub { get; set; }
public string Method { get; set; }
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This type is used for de-serialization.")]
public IJsonValue[] ParameterValues { get; set; }
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This type is used for de-serialization.")]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This type is used for de-serialization.")]
public IDictionary<string, object> State { get; set; }
public string Id { get; set; }
}
}
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.AspNet.SignalR.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.SignalR.Hubs
{
internal class HubRequestParser : IHubRequestParser
{
private static readonly IJsonValue[] _emptyArgs = new IJsonValue[0];
public HubRequest Parse(string data)
{
var serializer = new JsonNetSerializer();
var deserializedData = serializer.Parse<HubInvocation>(data);
var request = new HubRequest();
request.Hub = deserializedData.Hub;
request.Method = deserializedData.Method;
request.Id = deserializedData.Id;
request.State = GetState(deserializedData);
request.ParameterValues = (deserializedData.Args != null) ? deserializedData.Args.Select(value => new JRawValue(value)).ToArray() : _emptyArgs;
return request;
}
[SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "This type is used for deserialzation")]
private class HubInvocation
{
[JsonProperty("H")]
public string Hub { get; set; }
[JsonProperty("M")]
public string Method { get; set; }
[JsonProperty("I")]
public string Id { get; set; }
[JsonProperty("S")]
public JRaw State { get; set; }
[JsonProperty("A")]
public JRaw[] Args { get; set; }
}
private static IDictionary<string, object> GetState(HubInvocation deserializedData)
{
if (deserializedData.State == null)
{
return new Dictionary<string, object>();
}
// Get the raw JSON string and check if it's over 4K
string json = deserializedData.State.ToString();
if (json.Length > 4096)
{
throw new InvalidOperationException(Resources.Error_StateExceededMaximumLength);
}
var settings = new JsonSerializerSettings();
settings.Converters.Add(new SipHashBasedDictionaryConverter());
var serializer = new JsonNetSerializer(settings);
return serializer.Parse<IDictionary<string, object>>(json);
}
}
}
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// The response returned from an incoming hub request.
/// </summary>
public class HubResponse
{
/// <summary>
/// The changes made the the round tripped state.
/// </summary>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Type is used for serialization")]
[JsonProperty("S", NullValueHandling = NullValueHandling.Ignore)]
public IDictionary<string, object> State { get; set; }
/// <summary>
/// The result of the invocation.
/// </summary>
[JsonProperty("R", NullValueHandling = NullValueHandling.Ignore)]
public object Result { get; set; }
/// <summary>
/// The id of the operation.
/// </summary>
[JsonProperty("I")]
public string Id { get; set; }
/// <summary>
/// The exception that occurs as a result of invoking the hub method.
/// </summary>
[JsonProperty("E", NullValueHandling = NullValueHandling.Ignore)]
public string Error { get; set; }
/// <summary>
/// The stack trace of the exception that occurs as a result of invoking the hub method.
/// </summary>
[JsonProperty("T", NullValueHandling = NullValueHandling.Ignore)]
public string StackTrace { get; set; }
}
}
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
namespace Microsoft.AspNet.SignalR.Hubs
{
public interface IAssemblyLocator
{
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Might be expensive.")]
IList<Assembly> GetAssemblies();
}
}
@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// A server side proxy for the client side hub.
/// </summary>
public interface IClientProxy
{
/// <summary>
/// Invokes a method on the connection(s) represented by the <see cref="IClientProxy"/> instance.
/// </summary>
/// <param name="method">name of the method to invoke</param>
/// <param name="args">argumetns to pass to the client</param>
/// <returns>A task that represents when the data has been sent to the client.</returns>
Task Invoke(string method, params object[] args);
}
}
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Hubs
{
public interface IHub : IDisposable
{
/// <summary>
/// Gets a <see cref="HubCallerContext"/>. Which contains information about the calling client.
/// </summary>
HubCallerContext Context { get; set; }
/// <summary>
/// Gets a dynamic object that represents all clients connected to this hub (not hub instance).
/// </summary>
HubConnectionContext Clients { get; set; }
/// <summary>
/// Gets the <see cref="IGroupManager"/> the hub instance.
/// </summary>
IGroupManager Groups { get; set; }
/// <summary>
/// Called when a new connection is made to the <see cref="IHub"/>.
/// </summary>
Task OnConnected();
/// <summary>
/// Called when a connection reconnects to the <see cref="IHub"/> after a timeout.
/// </summary>
Task OnReconnected();
/// <summary>
/// Called when a connection is disconnected from the <see cref="IHub"/>.
/// </summary>
Task OnDisconnected();
}
}
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Hubs
{
public interface IHubActivator
{
IHub Create(HubDescriptor descriptor);
}
}
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Encapsulates all information about a SignalR connection for an <see cref="IHub"/>.
/// </summary>
public interface IHubConnectionContext
{
dynamic All { get; }
dynamic AllExcept(params string[] excludeConnectionIds);
dynamic Client(string connectionId);
dynamic Group(string groupName, params string[] excludeConnectionIds);
}
}
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Handles parsing incoming requests through the <see cref="HubDispatcher"/>.
/// </summary>
public interface IHubRequestParser
{
/// <summary>
/// Parses the incoming hub payload into a <see cref="HubRequest"/>.
/// </summary>
/// <param name="data">The raw hub payload.</param>
/// <returns>The resulting <see cref="HubRequest"/>.</returns>
HubRequest Parse(string data);
}
}
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Hubs
{
public interface IJavaScriptMinifier
{
string Minify(string source);
}
}
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Hubs
{
public interface IJavaScriptProxyGenerator
{
string GenerateProxy(string serviceUrl);
}
}
@@ -0,0 +1,96 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.SignalR.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class DefaultHubManager : IHubManager
{
private readonly IEnumerable<IMethodDescriptorProvider> _methodProviders;
private readonly IHubActivator _activator;
private readonly IEnumerable<IHubDescriptorProvider> _hubProviders;
public DefaultHubManager(IDependencyResolver resolver)
{
_hubProviders = resolver.ResolveAll<IHubDescriptorProvider>();
_methodProviders = resolver.ResolveAll<IMethodDescriptorProvider>();
_activator = resolver.Resolve<IHubActivator>();
}
public HubDescriptor GetHub(string hubName)
{
HubDescriptor descriptor = null;
if (_hubProviders.FirstOrDefault(p => p.TryGetHub(hubName, out descriptor)) != null)
{
return descriptor;
}
return null;
}
public IEnumerable<HubDescriptor> GetHubs(Func<HubDescriptor, bool> predicate)
{
var hubs = _hubProviders.SelectMany(p => p.GetHubs());
if (predicate != null)
{
return hubs.Where(predicate);
}
return hubs;
}
public MethodDescriptor GetHubMethod(string hubName, string method, IList<IJsonValue> parameters)
{
HubDescriptor hub = GetHub(hubName);
if (hub == null)
{
return null;
}
MethodDescriptor descriptor = null;
if (_methodProviders.FirstOrDefault(p => p.TryGetMethod(hub, method, out descriptor, parameters)) != null)
{
return descriptor;
}
return null;
}
public IEnumerable<MethodDescriptor> GetHubMethods(string hubName, Func<MethodDescriptor, bool> predicate)
{
HubDescriptor hub = GetHub(hubName);
if (hub == null)
{
return null;
}
var methods = _methodProviders.SelectMany(p => p.GetMethods(hub));
if (predicate != null)
{
return methods.Where(predicate);
}
return methods;
}
public IHub ResolveHub(string hubName)
{
HubDescriptor hub = GetHub(hubName);
return hub == null ? null : _activator.Create(hub);
}
public IEnumerable<IHub> ResolveHubs()
{
return GetHubs(predicate: null).Select(hub => _activator.Create(hub));
}
}
}
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.SignalR.Json;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class DefaultParameterResolver : IParameterResolver
{
/// <summary>
/// Resolves a parameter value based on the provided object.
/// </summary>
/// <param name="descriptor">Parameter descriptor.</param>
/// <param name="value">Value to resolve the parameter value from.</param>
/// <returns>The parameter value.</returns>
public virtual object ResolveParameter(ParameterDescriptor descriptor, IJsonValue value)
{
if (descriptor == null)
{
throw new ArgumentNullException("descriptor");
}
if (value == null)
{
throw new ArgumentNullException("value");
}
if (value.GetType() == descriptor.ParameterType)
{
return value;
}
return value.ConvertTo(descriptor.ParameterType);
}
/// <summary>
/// Resolves method parameter values based on provided objects.
/// </summary>
/// <param name="method">Method descriptor.</param>
/// <param name="values">List of values to resolve parameter values from.</param>
/// <returns>Array of parameter values.</returns>
public virtual IList<object> ResolveMethodParameters(MethodDescriptor method, IList<IJsonValue> values)
{
if (method == null)
{
throw new ArgumentNullException("method");
}
return method.Parameters.Zip(values, ResolveParameter).ToArray();
}
}
}
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Hubs
{
public abstract class Descriptor
{
/// <summary>
/// Name of Descriptor.
/// </summary>
public virtual string Name { get; set; }
/// <summary>
/// Flags whether the name was specified.
/// </summary>
public virtual bool NameSpecified { get; set; }
}
}
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Holds information about a single hub.
/// </summary>
public class HubDescriptor : Descriptor
{
/// <summary>
/// Hub type.
/// </summary>
public virtual Type HubType { get; set; }
public string CreateQualifiedName(string unqualifiedName)
{
return Name + "." + unqualifiedName;
}
}
}
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Holds information about a single hub method.
/// </summary>
public class MethodDescriptor : Descriptor
{
/// <summary>
/// The return type of this method.
/// </summary>
public virtual Type ReturnType { get; set; }
/// <summary>
/// Hub descriptor object, target to this method.
/// </summary>
public virtual HubDescriptor Hub { get; set; }
/// <summary>
/// Available method parameters.
/// </summary>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This is supposed to be mutable")]
public virtual IList<ParameterDescriptor> Parameters { get; set; }
/// <summary>
/// Method invocation delegate.
/// Takes a target hub and an array of invocation arguments as it's arguments.
/// </summary>
public virtual Func<IHub, object[], object> Invoker { get; set; }
/// <summary>
/// Attributes attached to this method.
/// </summary>
public virtual IEnumerable<Attribute> Attributes { get; set; }
}
}
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class NullMethodDescriptor : MethodDescriptor
{
private static readonly IEnumerable<Attribute> _attributes = new List<Attribute>();
private static readonly IList<ParameterDescriptor> _parameters = new List<ParameterDescriptor>();
private string _methodName;
public NullMethodDescriptor(string methodName)
{
_methodName = methodName;
}
public override Func<IHub, object[], object> Invoker
{
get
{
return (emptyHub, emptyParameters) =>
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_MethodCouldNotBeResolved, _methodName));
};
}
}
public override IList<ParameterDescriptor> Parameters
{
get { return _parameters; }
}
public override IEnumerable<Attribute> Attributes
{
get { return _attributes; }
}
}
}
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Holds information about a single hub method parameter.
/// </summary>
public class ParameterDescriptor
{
/// <summary>
/// Parameter name.
/// </summary>
public virtual string Name { get; set; }
/// <summary>
/// Parameter type.
/// </summary>
public virtual Type ParameterType { get; set; }
}
}
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
namespace Microsoft.AspNet.SignalR.Hubs
{
internal class HubMethodDispatcher
{
private HubMethodExecutor _executor;
public HubMethodDispatcher(MethodInfo methodInfo)
{
_executor = GetExecutor(methodInfo);
MethodInfo = methodInfo;
}
private delegate object HubMethodExecutor(IHub hub, object[] parameters);
private delegate void VoidHubMethodExecutor(IHub hub, object[] parameters);
public MethodInfo MethodInfo { get; private set; }
public object Execute(IHub hub, object[] parameters)
{
return _executor(hub, parameters);
}
private static HubMethodExecutor GetExecutor(MethodInfo methodInfo)
{
// Parameters to executor
ParameterExpression hubParameter = Expression.Parameter(typeof(IHub), "hub");
ParameterExpression parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
// Build parameter list
List<Expression> parameters = new List<Expression>();
ParameterInfo[] paramInfos = methodInfo.GetParameters();
for (int i = 0; i < paramInfos.Length; i++)
{
ParameterInfo paramInfo = paramInfos[i];
BinaryExpression valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
UnaryExpression valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);
// valueCast is "(Ti) parameters[i]"
parameters.Add(valueCast);
}
// Call method
UnaryExpression instanceCast = (!methodInfo.IsStatic) ? Expression.Convert(hubParameter, methodInfo.ReflectedType) : null;
MethodCallExpression methodCall = Expression.Call(instanceCast, methodInfo, parameters);
// methodCall is "((TController) hub) method((T0) parameters[0], (T1) parameters[1], ...)"
// Create function
if (methodCall.Type == typeof(void))
{
Expression<VoidHubMethodExecutor> lambda = Expression.Lambda<VoidHubMethodExecutor>(methodCall, hubParameter, parametersParameter);
VoidHubMethodExecutor voidExecutor = lambda.Compile();
return WrapVoidAction(voidExecutor);
}
else
{
// must coerce methodCall to match HubMethodExecutor signature
UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));
Expression<HubMethodExecutor> lambda = Expression.Lambda<HubMethodExecutor>(castMethodCall, hubParameter, parametersParameter);
return lambda.Compile();
}
}
private static HubMethodExecutor WrapVoidAction(VoidHubMethodExecutor executor)
{
return delegate(IHub hub, object[] parameters)
{
executor(hub, parameters);
return null;
};
}
}
}
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Describes hub descriptor provider, which provides information about available hubs.
/// </summary>
public interface IHubDescriptorProvider
{
/// <summary>
/// Retrieve all avaiable hubs.
/// </summary>
/// <returns>Collection of hub descriptors.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This call might be expensive")]
IList<HubDescriptor> GetHubs();
/// <summary>
/// Tries to retrieve hub with a given name.
/// </summary>
/// <param name="hubName">Name of the hub.</param>
/// <param name="descriptor">Retrieved descriptor object.</param>
/// <returns>True, if hub has been found</returns>
bool TryGetHub(string hubName, out HubDescriptor descriptor);
}
}
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNet.SignalR.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Describes a hub manager - main point in the whole hub and method lookup process.
/// </summary>
public interface IHubManager
{
/// <summary>
/// Retrieves a single hub descriptor.
/// </summary>
/// <param name="hubName">Name of the hub.</param>
/// <returns>Hub descriptor, if found. Null, otherwise.</returns>
HubDescriptor GetHub(string hubName);
/// <summary>
/// Retrieves all available hubs matching the given predicate.
/// </summary>
/// <returns>List of hub descriptors.</returns>
IEnumerable<HubDescriptor> GetHubs(Func<HubDescriptor, bool> predicate);
/// <summary>
/// Resolves a given hub name to a concrete object.
/// </summary>
/// <param name="hubName">Name of the hub.</param>
/// <returns>Hub implementation instance, if found. Null otherwise.</returns>
IHub ResolveHub(string hubName);
/// <summary>
/// Resolves all available hubs to their concrete objects.
/// </summary>
/// <returns>List of hub instances.</returns>
IEnumerable<IHub> ResolveHubs();
/// <summary>
/// Retrieves a method with a given name on a given hub.
/// </summary>
/// <param name="hubName">Name of the hub.</param>
/// <param name="method">Name of the method to find.</param>
/// <param name="parameters">Method parameters to match.</param>
/// <returns>Descriptor of the method, if found. Null otherwise.</returns>
MethodDescriptor GetHubMethod(string hubName, string method, IList<IJsonValue> parameters);
/// <summary>
/// Gets all methods available to call on a given hub.
/// </summary>
/// <param name="hubName">Name of the hub,</param>
/// <param name="predicate">Optional predicate for filtering results.</param>
/// <returns>List of available methods.</returns>
IEnumerable<MethodDescriptor> GetHubMethods(string hubName, Func<MethodDescriptor, bool> predicate);
}
}
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.SignalR.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Describes a hub method provider that builds a collection of available methods on a given hub.
/// </summary>
public interface IMethodDescriptorProvider
{
/// <summary>
/// Retrieve all methods on a given hub.
/// </summary>
/// <param name="hub">Hub descriptor object.</param>
/// <returns>Available methods.</returns>
IEnumerable<MethodDescriptor> GetMethods(HubDescriptor hub);
/// <summary>
/// Tries to retrieve a method.
/// </summary>
/// <param name="hub">Hub descriptor object</param>
/// <param name="method">Name of the method.</param>
/// <param name="descriptor">Descriptor of the method, if found. Null otherwise.</param>
/// <param name="parameters">Method parameters to match.</param>
/// <returns>True, if a method has been found.</returns>
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "This is a well known pattern for efficient lookup")]
bool TryGetMethod(HubDescriptor hub, string method, out MethodDescriptor descriptor, IList<IJsonValue> parameters);
}
}
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNet.SignalR.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Describes a parameter resolver for resolving parameter-matching values based on provided information.
/// </summary>
public interface IParameterResolver
{
/// <summary>
/// Resolves method parameter values based on provided objects.
/// </summary>
/// <param name="method">Method descriptor.</param>
/// <param name="values">List of values to resolve parameter values from.</param>
/// <returns>Array of parameter values.</returns>
IList<object> ResolveMethodParameters(MethodDescriptor method, IList<IJsonValue> values);
}
}
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class ReflectedHubDescriptorProvider : IHubDescriptorProvider
{
private readonly Lazy<IDictionary<string, HubDescriptor>> _hubs;
private readonly Lazy<IAssemblyLocator> _locator;
public ReflectedHubDescriptorProvider(IDependencyResolver resolver)
{
_locator = new Lazy<IAssemblyLocator>(resolver.Resolve<IAssemblyLocator>);
_hubs = new Lazy<IDictionary<string, HubDescriptor>>(BuildHubsCache);
}
public IList<HubDescriptor> GetHubs()
{
return _hubs.Value
.Select(kv => kv.Value)
.Distinct()
.ToList();
}
public bool TryGetHub(string hubName, out HubDescriptor descriptor)
{
return _hubs.Value.TryGetValue(hubName, out descriptor);
}
protected IDictionary<string, HubDescriptor> BuildHubsCache()
{
// Getting all IHub-implementing types that apply
var types = _locator.Value.GetAssemblies()
.SelectMany(GetTypesSafe)
.Where(IsHubType);
// Building cache entries for each descriptor
// Each descriptor is stored in dictionary under a key
// that is it's name or the name provided by an attribute
var cacheEntries = types
.Select(type => new HubDescriptor
{
NameSpecified = (type.GetHubAttributeName() != null),
Name = type.GetHubName(),
HubType = type
})
.ToDictionary(hub => hub.Name,
hub => hub,
StringComparer.OrdinalIgnoreCase);
return cacheEntries;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "If we throw then it's not a hub type")]
private static bool IsHubType(Type type)
{
try
{
return typeof(IHub).IsAssignableFrom(type) &&
!type.IsAbstract &&
(type.Attributes.HasFlag(TypeAttributes.Public) ||
type.Attributes.HasFlag(TypeAttributes.NestedPublic));
}
catch
{
return false;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "If we throw then we have an empty type")]
private static IEnumerable<Type> GetTypesSafe(Assembly a)
{
try
{
return a.GetTypes();
}
catch
{
return Enumerable.Empty<Type>();
}
}
}
}
@@ -0,0 +1,150 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.SignalR.Json;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class ReflectedMethodDescriptorProvider : IMethodDescriptorProvider
{
private readonly ConcurrentDictionary<string, IDictionary<string, IEnumerable<MethodDescriptor>>> _methods;
private readonly ConcurrentDictionary<string, MethodDescriptor> _executableMethods;
public ReflectedMethodDescriptorProvider()
{
_methods = new ConcurrentDictionary<string, IDictionary<string, IEnumerable<MethodDescriptor>>>(StringComparer.OrdinalIgnoreCase);
_executableMethods = new ConcurrentDictionary<string, MethodDescriptor>(StringComparer.OrdinalIgnoreCase);
}
public IEnumerable<MethodDescriptor> GetMethods(HubDescriptor hub)
{
return FetchMethodsFor(hub)
.SelectMany(kv => kv.Value)
.ToList();
}
/// <summary>
/// Retrieves an existing dictionary of all available methods for a given hub from cache.
/// If cache entry does not exist - it is created automatically by BuildMethodCacheFor.
/// </summary>
/// <param name="hub"></param>
/// <returns></returns>
private IDictionary<string, IEnumerable<MethodDescriptor>> FetchMethodsFor(HubDescriptor hub)
{
return _methods.GetOrAdd(
hub.Name,
key => BuildMethodCacheFor(hub));
}
/// <summary>
/// Builds a dictionary of all possible methods on a given hub.
/// Single entry contains a collection of available overloads for a given method name (key).
/// This dictionary is being cached afterwards.
/// </summary>
/// <param name="hub">Hub to build cache for</param>
/// <returns>Dictionary of available methods</returns>
private static IDictionary<string, IEnumerable<MethodDescriptor>> BuildMethodCacheFor(HubDescriptor hub)
{
return ReflectionHelper.GetExportedHubMethods(hub.HubType)
.GroupBy(GetMethodName, StringComparer.OrdinalIgnoreCase)
.ToDictionary(group => group.Key,
group => group.Select(oload =>
new MethodDescriptor
{
ReturnType = oload.ReturnType,
Name = group.Key,
NameSpecified = (GetMethodAttributeName(oload) != null),
Invoker = new HubMethodDispatcher(oload).Execute,
Hub = hub,
Attributes = oload.GetCustomAttributes(typeof(Attribute), inherit: true).Cast<Attribute>(),
Parameters = oload.GetParameters()
.Select(p => new ParameterDescriptor
{
Name = p.Name,
ParameterType = p.ParameterType,
})
.ToList()
}),
StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Searches the specified <paramref name="hub">Hub</paramref> for the specified <paramref name="method"/>.
/// </summary>
/// <remarks>
/// In the case that there are multiple overloads of the specified <paramref name="method"/>, the <paramref name="parameters">parameter set</paramref> helps determine exactly which instance of the overload should be resolved.
/// If there are multiple overloads found with the same number of matching parameters, none of the methods will be returned because it is not possible to determine which overload of the method was intended to be resolved.
/// </remarks>
/// <param name="hub">Hub to search for the specified <paramref name="method"/> on.</param>
/// <param name="method">The method name to search for.</param>
/// <param name="descriptor">If successful, the <see cref="MethodDescriptor"/> that was resolved.</param>
/// <param name="parameters">The set of parameters that will be used to help locate a specific overload of the specified <paramref name="method"/>.</param>
/// <returns>True if the method matching the name/parameter set is found on the hub, otherwise false.</returns>
public bool TryGetMethod(HubDescriptor hub, string method, out MethodDescriptor descriptor, IList<IJsonValue> parameters)
{
string hubMethodKey = BuildHubExecutableMethodCacheKey(hub, method, parameters);
if (!_executableMethods.TryGetValue(hubMethodKey, out descriptor))
{
IEnumerable<MethodDescriptor> overloads;
if (FetchMethodsFor(hub).TryGetValue(method, out overloads))
{
var matches = overloads.Where(o => o.Matches(parameters)).ToList();
// If only one match is found, that is the "executable" version, otherwise none of the methods can be returned because we don't know which one was actually being targeted
descriptor = matches.Count == 1 ? matches[0] : null;
}
else
{
descriptor = null;
}
// If an executable method was found, cache it for future lookups (NOTE: we don't cache null instances because it could be a surface area for DoS attack by supplying random method names to flood the cache)
if (descriptor != null)
{
_executableMethods.TryAdd(hubMethodKey, descriptor);
}
}
return descriptor != null;
}
private static string BuildHubExecutableMethodCacheKey(HubDescriptor hub, string method, IList<IJsonValue> parameters)
{
string normalizedParameterCountKeyPart;
if (parameters != null)
{
normalizedParameterCountKeyPart = parameters.Count.ToString(CultureInfo.InvariantCulture);
}
else
{
// NOTE: we normalize a null parameter array to be the same as an empty (i.e. Length == 0) parameter array
normalizedParameterCountKeyPart = "0";
}
// NOTE: we always normalize to all uppercase since method names are case insensitive and could theoretically come in diff. variations per call
string normalizedMethodName = method.ToUpperInvariant();
string methodKey = hub.Name + "::" + normalizedMethodName + "(" + normalizedParameterCountKeyPart + ")";
return methodKey;
}
private static string GetMethodName(MethodInfo method)
{
return GetMethodAttributeName(method) ?? method.Name;
}
private static string GetMethodAttributeName(MethodInfo method)
{
return ReflectionHelper.GetAttributeValue<HubMethodNameAttribute, string>(method, a => a.MethodName);
}
}
}
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Dynamic;
using System.Globalization;
namespace Microsoft.AspNet.SignalR.Hubs
{
internal class NullClientProxy : DynamicObject
{
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_UsingHubInstanceNotCreatedUnsupported));
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_UsingHubInstanceNotCreatedUnsupported));
}
}
}
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.SignalR.Hubs;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class NullJavaScriptMinifier : IJavaScriptMinifier
{
[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "This is a singleton")]
public static readonly NullJavaScriptMinifier Instance = new NullJavaScriptMinifier();
public string Minify(string source)
{
return source;
}
}
}
@@ -0,0 +1,125 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// This module is added the the HubPipeline by default.
///
/// Hub level attributes that implement <see cref="IAuthorizeHubConnection"/> such as <see cref="AuthorizeAttribute"/> are applied to determine
/// whether to allow potential clients to receive messages sent from that hub using a <see cref="HubContext"/> or a <see cref="HubConnectionContext"/>
/// All applicable hub attributes must allow hub connection for the connection to be authorized.
///
/// Hub and method level attributes that implement <see cref="IAuthorizeHubMethodInvocation"/> such as <see cref="AuthorizeAttribute"/> are applied
/// to determine whether to allow callers to invoke hub methods.
/// All applicable hub level AND method level attributes must allow hub method invocation for the invocation to be authorized.
///
/// Optionally, this module may be instantiated with <see cref="IAuthorizeHubConnection"/> and <see cref="IAuthorizeHubMethodInvocation"/>
/// authorizers that will be applied globally to all hubs and hub methods.
/// </summary>
public class AuthorizeModule : HubPipelineModule
{
// Global authorizers
private readonly IAuthorizeHubConnection _globalConnectionAuthorizer;
private readonly IAuthorizeHubMethodInvocation _globalInvocationAuthorizer;
// Attribute authorizer caches
private readonly ConcurrentDictionary<Type, IEnumerable<IAuthorizeHubConnection>> _connectionAuthorizersCache;
private readonly ConcurrentDictionary<Type, IEnumerable<IAuthorizeHubMethodInvocation>> _classInvocationAuthorizersCache;
private readonly ConcurrentDictionary<MethodDescriptor, IEnumerable<IAuthorizeHubMethodInvocation>> _methodInvocationAuthorizersCache;
// By default, this module does not include any authorizers that are applied globally.
// This module will always apply authorizers attached to hubs or hub methods
public AuthorizeModule()
: this(globalConnectionAuthorizer: null, globalInvocationAuthorizer: null)
{
}
public AuthorizeModule(IAuthorizeHubConnection globalConnectionAuthorizer, IAuthorizeHubMethodInvocation globalInvocationAuthorizer)
{
// Set global authorizers
_globalConnectionAuthorizer = globalConnectionAuthorizer;
_globalInvocationAuthorizer = globalInvocationAuthorizer;
// Initialize attribute authorizer caches
_connectionAuthorizersCache = new ConcurrentDictionary<Type, IEnumerable<IAuthorizeHubConnection>>();
_classInvocationAuthorizersCache = new ConcurrentDictionary<Type, IEnumerable<IAuthorizeHubMethodInvocation>>();
_methodInvocationAuthorizersCache = new ConcurrentDictionary<MethodDescriptor, IEnumerable<IAuthorizeHubMethodInvocation>>();
}
public override Func<HubDescriptor, IRequest, bool> BuildAuthorizeConnect(Func<HubDescriptor, IRequest, bool> authorizeConnect)
{
return base.BuildAuthorizeConnect((hubDescriptor, request) =>
{
// Execute custom modules first and short circuit if any deny authorization.
if (!authorizeConnect(hubDescriptor, request))
{
return false;
}
// Execute the global hub connection authorizer if there is one next and short circuit if it denies authorization.
if (_globalConnectionAuthorizer != null && !_globalConnectionAuthorizer.AuthorizeHubConnection(hubDescriptor, request))
{
return false;
}
// Get hub attributes implementing IAuthorizeHubConnection from the cache
// If the attributes do not exist in the cache, retrieve them using reflection and add them to the cache
var attributeAuthorizers = _connectionAuthorizersCache.GetOrAdd(hubDescriptor.HubType,
hubType => hubType.GetCustomAttributes(typeof(IAuthorizeHubConnection), inherit: true).Cast<IAuthorizeHubConnection>());
// Every attribute (if any) implementing IAuthorizeHubConnection attached to the relevant hub MUST allow the connection
return attributeAuthorizers.All(a => a.AuthorizeHubConnection(hubDescriptor, request));
});
}
public override Func<IHubIncomingInvokerContext, Task<object>> BuildIncoming(Func<IHubIncomingInvokerContext, Task<object>> invoke)
{
return base.BuildIncoming(context =>
{
// Execute the global method invocation authorizer if there is one and short circuit if it denies authorization.
if (_globalInvocationAuthorizer == null || _globalInvocationAuthorizer.AuthorizeHubMethodInvocation(context, appliesToMethod: false))
{
// Get hub attributes implementing IAuthorizeHubMethodInvocation from the cache
// If the attributes do not exist in the cache, retrieve them using reflection and add them to the cache
var classLevelAuthorizers = _classInvocationAuthorizersCache.GetOrAdd(context.Hub.GetType(),
hubType => hubType.GetCustomAttributes(typeof(IAuthorizeHubMethodInvocation), inherit: true).Cast<IAuthorizeHubMethodInvocation>());
// Execute all hub level authorizers and short circuit if ANY deny authorization.
if (classLevelAuthorizers.All(a => a.AuthorizeHubMethodInvocation(context, appliesToMethod: false)))
{
// If the MethodDescriptor is a NullMethodDescriptor, we don't want to cache it since a new one is created
// for each invocation with an invalid method name. #1801
if (context.MethodDescriptor is NullMethodDescriptor)
{
return invoke(context);
}
// Get method attributes implementing IAuthorizeHubMethodInvocation from the cache
// If the attributes do not exist in the cache, retrieve them from the MethodDescriptor and add them to the cache
var methodLevelAuthorizers = _methodInvocationAuthorizersCache.GetOrAdd(context.MethodDescriptor,
methodDescriptor => methodDescriptor.Attributes.OfType<IAuthorizeHubMethodInvocation>());
// Execute all method level authorizers. If ALL provide authorization, continue executing the invocation pipeline.
if (methodLevelAuthorizers.All(a => a.AuthorizeHubMethodInvocation(context, appliesToMethod: true)))
{
return invoke(context);
}
}
}
// Send error back to the client
return TaskAsyncHelper.FromError<object>(
new NotAuthorizedException(String.Format(CultureInfo.CurrentCulture, Resources.Error_CallerNotAuthorizedToInvokeMethodOn,
context.MethodDescriptor.Name,
context.MethodDescriptor.Hub.Name)));
});
}
}
}
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Interface to be implemented by <see cref="System.Attribute"/>s that can authorize client to connect to a <see cref="IHub"/>.
/// </summary>
public interface IAuthorizeHubConnection
{
/// <summary>
/// Given a <see cref="HubCallerContext"/>, determine whether client is authorized to connect to <see cref="IHub"/>.
/// </summary>
/// <param name="hubDescriptor">Description of the hub client is attempting to connect to.</param>
/// <param name="request">The connection request from the client.</param>
/// <returns>true if the caller is authorized to connect to the hub; otherwise, false.</returns>
bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request);
}
}
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Interface to be implemented by <see cref="System.Attribute"/>s that can authorize the invocation of <see cref="IHub"/> methods.
/// </summary>
public interface IAuthorizeHubMethodInvocation
{
/// <summary>
/// Given a <see cref="IHubIncomingInvokerContext"/>, determine whether client is authorized to invoke the <see cref="IHub"/> method.
/// </summary>
/// <param name="hubIncomingInvokerContext">An <see cref="IHubIncomingInvokerContext"/> providing details regarding the <see cref="IHub"/> method invocation.</param>
/// <param name="appliesToMethod">Indicates whether the interface instance is an attribute applied directly to a method.</param>
/// <returns>true if the caller is authorized to invoke the <see cref="IHub"/> method; otherwise, false.</returns>
bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod);
}
}
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
namespace Microsoft.AspNet.SignalR.Hubs
{
[Serializable]
public class NotAuthorizedException : Exception
{
public NotAuthorizedException() { }
public NotAuthorizedException(string message) : base(message) { }
public NotAuthorizedException(string message, Exception inner) : base(message, inner) { }
protected NotAuthorizedException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
: base(info, context) { }
}
}
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNet.SignalR.Hubs
{
internal class HubInvokerContext : IHubIncomingInvokerContext
{
public HubInvokerContext(IHub hub, StateChangeTracker tracker, MethodDescriptor methodDescriptor, IList<object> args)
{
Hub = hub;
MethodDescriptor = methodDescriptor;
Args = args;
StateTracker = tracker;
}
public IHub Hub
{
get;
private set;
}
public MethodDescriptor MethodDescriptor
{
get;
private set;
}
public IList<object> Args
{
get;
private set;
}
public StateChangeTracker StateTracker
{
get;
private set;
}
}
}
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNet.SignalR.Hubs
{
internal class HubOutgoingInvokerContext : IHubOutgoingInvokerContext
{
public HubOutgoingInvokerContext(IConnection connection, string signal, ClientHubInvocation invocation, IList<string> excludedSignals)
{
Connection = connection;
Signal = signal;
Invocation = invocation;
ExcludedSignals = excludedSignals;
}
public IConnection Connection
{
get;
private set;
}
public ClientHubInvocation Invocation
{
get;
private set;
}
public string Signal
{
get;
private set;
}
public IList<string> ExcludedSignals
{
get;
private set;
}
}
}
@@ -0,0 +1,103 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Hubs
{
internal class HubPipeline : IHubPipeline, IHubPipelineInvoker
{
private readonly Stack<IHubPipelineModule> _modules;
private readonly Lazy<ComposedPipeline> _pipeline;
public HubPipeline()
{
_modules = new Stack<IHubPipelineModule>();
_pipeline = new Lazy<ComposedPipeline>(() => new ComposedPipeline(_modules));
}
public IHubPipeline AddModule(IHubPipelineModule pipelineModule)
{
if (_pipeline.IsValueCreated)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_UnableToAddModulePiplineAlreadyInvoked));
}
_modules.Push(pipelineModule);
return this;
}
private ComposedPipeline Pipeline
{
get { return _pipeline.Value; }
}
public Task<object> Invoke(IHubIncomingInvokerContext context)
{
return Pipeline.Invoke(context);
}
public Task Connect(IHub hub)
{
return Pipeline.Connect(hub);
}
public Task Reconnect(IHub hub)
{
return Pipeline.Reconnect(hub);
}
public Task Disconnect(IHub hub)
{
return Pipeline.Disconnect(hub);
}
public bool AuthorizeConnect(HubDescriptor hubDescriptor, IRequest request)
{
return Pipeline.AuthorizeConnect(hubDescriptor, request);
}
public IList<string> RejoiningGroups(HubDescriptor hubDescriptor, IRequest request, IList<string> groups)
{
return Pipeline.RejoiningGroups(hubDescriptor, request, groups);
}
public Task Send(IHubOutgoingInvokerContext context)
{
return Pipeline.Send(context);
}
private class ComposedPipeline
{
public Func<IHubIncomingInvokerContext, Task<object>> Invoke;
public Func<IHub, Task> Connect;
public Func<IHub, Task> Reconnect;
public Func<IHub, Task> Disconnect;
public Func<HubDescriptor, IRequest, bool> AuthorizeConnect;
public Func<HubDescriptor, IRequest, IList<string>, IList<string>> RejoiningGroups;
public Func<IHubOutgoingInvokerContext, Task> Send;
public ComposedPipeline(Stack<IHubPipelineModule> modules)
{
// This wouldn't look nearly as gnarly if C# had better type inference, but now we don't need the ComposedModule or PassThroughModule.
Invoke = Compose<Func<IHubIncomingInvokerContext, Task<object>>>(modules, (m, f) => m.BuildIncoming(f))(HubDispatcher.Incoming);
Connect = Compose<Func<IHub, Task>>(modules, (m, f) => m.BuildConnect(f))(HubDispatcher.Connect);
Reconnect = Compose<Func<IHub, Task>>(modules, (m, f) => m.BuildReconnect(f))(HubDispatcher.Reconnect);
Disconnect = Compose<Func<IHub, Task>>(modules, (m, f) => m.BuildDisconnect(f))(HubDispatcher.Disconnect);
AuthorizeConnect = Compose<Func<HubDescriptor, IRequest, bool>>(modules, (m, f) => m.BuildAuthorizeConnect(f))((h, r) => true);
RejoiningGroups = Compose<Func<HubDescriptor, IRequest, IList<string>, IList<string>>>(modules, (m, f) => m.BuildRejoiningGroups(f))((h, r, g) => g);
Send = Compose<Func<IHubOutgoingInvokerContext, Task>>(modules, (m, f) => m.BuildOutgoing(f))(HubDispatcher.Outgoing);
}
// IHubPipelineModule could be turned into a second generic parameter, but it would make the above invocations even longer than they currently are.
private static Func<T, T> Compose<T>(IEnumerable<IHubPipelineModule> modules, Func<IHubPipelineModule, T, T> method)
{
// Notice we are reversing and aggregating in one step. (Function composition is associative)
return modules.Aggregate<IHubPipelineModule, Func<T, T>>(x => x, (a, b) => (x => method(b, a(x))));
}
}
}
}
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using Microsoft.AspNet.SignalR.Hubs;
namespace Microsoft.AspNet.SignalR
{
public static class HubPipelineExtensions
{
/// <summary>
/// Requiring Authentication adds an <see cref="AuthorizeModule"/> to the <see cref="IHubPipeline" /> with <see cref="IAuthorizeHubConnection"/>
/// and <see cref="IAuthorizeHubMethodInvocation"/> authorizers that will be applied globally to all hubs and hub methods.
/// These authorizers require that the <see cref="System.Security.Principal.IPrincipal"/>'s <see cref="System.Security.Principal.IIdentity"/>
/// IsAuthenticated for any clients that invoke server-side hub methods or receive client-side hub method invocations.
/// </summary>
/// <param name="pipeline">The <see cref="IHubPipeline" /> to which the <see cref="AuthorizeModule" /> will be added.</param>
public static void RequireAuthentication(this IHubPipeline pipeline)
{
if (pipeline == null)
{
throw new ArgumentNullException("pipeline");
}
var authorizer = new AuthorizeAttribute();
pipeline.AddModule(new AuthorizeModule(globalConnectionAuthorizer: authorizer, globalInvocationAuthorizer: authorizer));
}
}
}
@@ -0,0 +1,311 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Common base class to simplify the implementation of IHubPipelineModules.
/// A module can intercept and customize various stages of hub processing such as connecting, reconnecting, disconnecting,
/// invoking server-side hub methods, invoking client-side hub methods, authorizing hub clients and rejoining hub groups.
/// A module can be activated by calling <see cref="IHubPipeline.AddModule"/>.
/// The combined modules added to the <see cref="IHubPipeline" /> are invoked via the <see cref="IHubPipelineInvoker"/>
/// interface.
/// </summary>
public abstract class HubPipelineModule : IHubPipelineModule
{
/// <summary>
/// Wraps a function that invokes a server-side hub method. Even if a client has not been authorized to connect
/// to a hub, it will still be authorized to invoke server-side methods on that hub unless it is prevented in
/// <see cref="IHubPipelineModule.BuildIncoming"/> by not executing the invoke parameter.
/// </summary>
/// <param name="invoke">A function that invokes a server-side hub method.</param>
/// <returns>A wrapped function that invokes a server-side hub method.</returns>
public virtual Func<IHubIncomingInvokerContext, Task<object>> BuildIncoming(Func<IHubIncomingInvokerContext, Task<object>> invoke)
{
return context =>
{
if (OnBeforeIncoming(context))
{
return invoke(context).OrEmpty()
.Then(result => OnAfterIncoming(result, context))
.Catch(ex => OnIncomingError(ex, context));
}
return TaskAsyncHelper.FromResult<object>(null);
};
}
/// <summary>
/// Wraps a function that is called when a client connects to the <see cref="HubDispatcher"/> for each
/// <see cref="IHub"/> the client connects to. By default, this results in the <see cref="IHub"/>'s
/// OnConnected method being invoked.
/// </summary>
/// <param name="connect">A function to be called when a client connects to a hub.</param>
/// <returns>A wrapped function to be called when a client connects to a hub.</returns>
public virtual Func<IHub, Task> BuildConnect(Func<IHub, Task> connect)
{
return hub =>
{
if (OnBeforeConnect(hub))
{
return connect(hub).OrEmpty().Then(h => OnAfterConnect(h), hub);
}
return TaskAsyncHelper.Empty;
};
}
/// <summary>
/// Wraps a function that is called when a client reconnects to the <see cref="HubDispatcher"/> for each
/// <see cref="IHub"/> the client connects to. By default, this results in the <see cref="IHub"/>'s
/// OnReconnected method being invoked.
/// </summary>
/// <param name="reconnect">A function to be called when a client reconnects to a hub.</param>
/// <returns>A wrapped function to be called when a client reconnects to a hub.</returns>
public virtual Func<IHub, Task> BuildReconnect(Func<IHub, Task> reconnect)
{
return (hub) =>
{
if (OnBeforeReconnect(hub))
{
return reconnect(hub).OrEmpty().Then(h => OnAfterReconnect(h), hub);
}
return TaskAsyncHelper.Empty;
};
}
/// <summary>
/// Wraps a function that is called when a client disconnects from the <see cref="HubDispatcher"/> for each
/// <see cref="IHub"/> the client was connected to. By default, this results in the <see cref="IHub"/>'s
/// OnDisconnected method being invoked.
/// </summary>
/// <param name="disconnect">A function to be called when a client disconnects from a hub.</param>
/// <returns>A wrapped function to be called when a client disconnects from a hub.</returns>
public virtual Func<IHub, Task> BuildDisconnect(Func<IHub, Task> disconnect)
{
return hub =>
{
if (OnBeforeDisconnect(hub))
{
return disconnect(hub).OrEmpty().Then(h => OnAfterDisconnect(h), hub);
}
return TaskAsyncHelper.Empty;
};
}
/// <summary>
/// Wraps a function to be called before a client subscribes to signals belonging to the hub described by the
/// <see cref="HubDescriptor"/>. By default, the <see cref="AuthorizeModule"/> will look for attributes on the
/// <see cref="IHub"/> to help determine if the client is authorized to subscribe to method invocations for the
/// described hub.
/// The function returns true if the client is authorized to subscribe to client-side hub method
/// invocations; false, otherwise.
/// </summary>
/// <param name="authorizeConnect">
/// A function that dictates whether or not the client is authorized to connect to the described Hub.
/// </param>
/// <returns>
/// A wrapped function that dictates whether or not the client is authorized to connect to the described Hub.
/// </returns>
public virtual Func<HubDescriptor, IRequest, bool> BuildAuthorizeConnect(Func<HubDescriptor, IRequest, bool> authorizeConnect)
{
return (hubDescriptor, request) =>
{
if (OnBeforeAuthorizeConnect(hubDescriptor, request))
{
return authorizeConnect(hubDescriptor, request);
}
return false;
};
}
/// <summary>
/// Wraps a function that determines which of the groups belonging to the hub described by the <see cref="HubDescriptor"/>
/// the client should be allowed to rejoin.
/// By default, clients will rejoin all the groups they were in prior to reconnecting.
/// </summary>
/// <param name="rejoiningGroups">A function that determines which groups the client should be allowed to rejoin.</param>
/// <returns>A wrapped function that determines which groups the client should be allowed to rejoin.</returns>
public virtual Func<HubDescriptor, IRequest, IList<string>, IList<string>> BuildRejoiningGroups(Func<HubDescriptor, IRequest, IList<string>, IList<string>> rejoiningGroups)
{
return rejoiningGroups;
}
/// <summary>
/// Wraps a function that invokes a client-side hub method.
/// </summary>
/// <param name="send">A function that invokes a client-side hub method.</param>
/// <returns>A wrapped function that invokes a client-side hub method.</returns>
public virtual Func<IHubOutgoingInvokerContext, Task> BuildOutgoing(Func<IHubOutgoingInvokerContext, Task> send)
{
return context =>
{
if (OnBeforeOutgoing(context))
{
return send(context).OrEmpty().Then(ctx => OnAfterOutgoing(ctx), context);
}
return TaskAsyncHelper.Empty;
};
}
/// <summary>
/// This method is called before the AuthorizeConnect components of any modules added later to the <see cref="IHubPipeline"/>
/// are executed. If this returns false, then those later-added modules will not run and the client will not be allowed
/// to subscribe to client-side invocations of methods belonging to the hub defined by the <see cref="HubDescriptor"/>.
/// </summary>
/// <param name="hubDescriptor">A description of the hub the client is trying to subscribe to.</param>
/// <param name="request">The connect request of the client trying to subscribe to the hub.</param>
/// <returns>true, if the client is authorized to connect to the hub, false otherwise.</returns>
protected virtual bool OnBeforeAuthorizeConnect(HubDescriptor hubDescriptor, IRequest request)
{
return true;
}
/// <summary>
/// This method is called before the connect components of any modules added later to the <see cref="IHubPipeline"/> are
/// executed. If this returns false, then those later-added modules and the <see cref="IHub.OnConnected"/> method will
/// not be run.
/// </summary>
/// <param name="hub">The hub the client has connected to.</param>
/// <returns>
/// true, if the connect components of later added modules and the <see cref="IHub.OnConnected"/> method should be executed;
/// false, otherwise.
/// </returns>
protected virtual bool OnBeforeConnect(IHub hub)
{
return true;
}
/// <summary>
/// This method is called after the connect components of any modules added later to the <see cref="IHubPipeline"/> are
/// executed and after <see cref="IHub.OnConnected"/> is executed, if at all.
/// </summary>
/// <param name="hub">The hub the client has connected to.</param>
protected virtual void OnAfterConnect(IHub hub)
{
}
/// <summary>
/// This method is called before the reconnect components of any modules added later to the <see cref="IHubPipeline"/> are
/// executed. If this returns false, then those later-added modules and the <see cref="IHub.OnReconnected"/> method will
/// not be run.
/// </summary>
/// <param name="hub">The hub the client has reconnected to.</param>
/// <returns>
/// true, if the reconnect components of later added modules and the <see cref="IHub.OnReconnected"/> method should be executed;
/// false, otherwise.
/// </returns>
protected virtual bool OnBeforeReconnect(IHub hub)
{
return true;
}
/// <summary>
/// This method is called after the reconnect components of any modules added later to the <see cref="IHubPipeline"/> are
/// executed and after <see cref="IHub.OnReconnected"/> is executed, if at all.
/// </summary>
/// <param name="hub">The hub the client has reconnected to.</param>
protected virtual void OnAfterReconnect(IHub hub)
{
}
/// <summary>
/// This method is called before the outgoing components of any modules added later to the <see cref="IHubPipeline"/> are
/// executed. If this returns false, then those later-added modules and the client-side hub method invocation(s) will not
/// be executed.
/// </summary>
/// <param name="context">A description of the client-side hub method invocation.</param>
/// <returns>
/// true, if the outgoing components of later added modules and the client-side hub method invocation(s) should be executed;
/// false, otherwise.
/// </returns>
protected virtual bool OnBeforeOutgoing(IHubOutgoingInvokerContext context)
{
return true;
}
/// <summary>
/// This method is called after the outgoing components of any modules added later to the <see cref="IHubPipeline"/> are
/// executed. This does not mean that all the clients have received the hub method invocation, but it does indicate indicate
/// a hub invocation message has successfully been published to a message bus.
/// </summary>
/// <param name="context">A description of the client-side hub method invocation.</param>
protected virtual void OnAfterOutgoing(IHubOutgoingInvokerContext context)
{
}
/// <summary>
/// This method is called before the disconnect components of any modules added later to the <see cref="IHubPipeline"/> are
/// executed. If this returns false, then those later-added modules and the <see cref="IHub.OnDisconnected"/> method will
/// not be run.
/// </summary>
/// <param name="hub">The hub the client has disconnected from.</param>
/// <returns>
/// true, if the disconnect components of later added modules and the <see cref="IHub.OnDisconnected"/> method should be executed;
/// false, otherwise.
/// </returns>
protected virtual bool OnBeforeDisconnect(IHub hub)
{
return true;
}
/// <summary>
/// This method is called after the disconnect components of any modules added later to the <see cref="IHubPipeline"/> are
/// executed and after <see cref="IHub.OnDisconnected"/> is executed, if at all.
/// </summary>
/// <param name="hub">The hub the client has disconnected from.</param>
protected virtual void OnAfterDisconnect(IHub hub)
{
}
/// <summary>
/// This method is called before the incoming components of any modules added later to the <see cref="IHubPipeline"/> are
/// executed. If this returns false, then those later-added modules and the server-side hub method invocation will not
/// be executed. Even if a client has not been authorized to connect to a hub, it will still be authorized to invoke
/// server-side methods on that hub unless it is prevented in <see cref="IHubPipelineModule.BuildIncoming"/> by not
/// executing the invoke parameter or prevented in <see cref="HubPipelineModule.OnBeforeIncoming"/> by returning false.
/// </summary>
/// <param name="context">A description of the server-side hub method invocation.</param>
/// <returns>
/// true, if the incoming components of later added modules and the server-side hub method invocation should be executed;
/// false, otherwise.
/// </returns>
protected virtual bool OnBeforeIncoming(IHubIncomingInvokerContext context)
{
return true;
}
/// <summary>
/// This method is called after the incoming components of any modules added later to the <see cref="IHubPipeline"/>
/// and the server-side hub method have completed execution.
/// </summary>
/// <param name="result">The return value of the server-side hub method</param>
/// <param name="context">A description of the server-side hub method invocation.</param>
/// <returns>The possibly new or updated return value of the server-side hub method</returns>
protected virtual object OnAfterIncoming(object result, IHubIncomingInvokerContext context)
{
return result;
}
/// <summary>
/// This is called when an uncaught exception is thrown by a server-side hub method or the incoming component of a
/// module added later to the <see cref="IHubPipeline"/>. Observing the exception using this method will not prevent
/// it from bubbling up to other modules.
/// </summary>
/// <param name="ex">The exception that was thrown during the server-side invocation.</param>
/// <param name="context">A description of the server-side hub method invocation.</param>
protected virtual void OnIncomingError(Exception ex, IHubIncomingInvokerContext context)
{
}
}
}
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// A description of a server-side hub method invocation originating from a client.
/// </summary>
public interface IHubIncomingInvokerContext
{
/// <summary>
/// A hub instance that contains the invoked method as a member.
/// </summary>
IHub Hub { get; }
/// <summary>
/// A description of the method being invoked by the client.
/// </summary>
MethodDescriptor MethodDescriptor { get; }
/// <summary>
/// The arguments to be passed to the invoked method.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This represents an ordered list of parameter values")]
IList<object> Args { get; }
/// <summary>
/// A key-value store representing the hub state on the client at the time of the invocation.
/// </summary>
StateChangeTracker StateTracker { get; }
}
}
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// A description of a client-side hub method invocation originating from the server.
/// </summary>
public interface IHubOutgoingInvokerContext
{
/// <summary>
/// The <see cref="IConnection"/>, if any, corresponding to the client that invoked the server-side hub method
/// that is invoking the client-side hub method.
/// </summary>
IConnection Connection { get; }
/// <summary>
/// A description of the method call to be made on the client.
/// </summary>
ClientHubInvocation Invocation { get; }
/// <summary>
/// The signal (ConnectionId, hub type name or hub type name + "." + group name) belonging to clients that
/// receive the method invocation.
/// </summary>
string Signal { get; }
/// <summary>
/// The signals (ConnectionId, hub type name or hub type name + "." + group name) belonging to clients that should
/// not receive the method invocation regardless of the <see cref="IHubOutgoingInvokerContext.Signal"/>.
/// </summary>
IList<string> ExcludedSignals { get; }
}
}
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// A collection of modules that can intercept and customize various stages of hub processing such as connecting,
/// reconnecting, disconnecting, invoking server-side hub methods, invoking client-side hub methods, authorizing
/// hub clients and rejoining hub groups.
/// </summary>
public interface IHubPipeline
{
/// <summary>
/// Adds an <see cref="IHubPipelineModule"/> to the hub pipeline. Modules added to the pipeline first will wrap
/// modules that are added to the pipeline later. All modules must be added to the pipeline before any methods
/// on the <see cref="IHubPipelineInvoker"/> are invoked.
/// </summary>
/// <param name="pipelineModule">
/// A module that may intercept and customize various stages of hub processing such as connecting,
/// reconnecting, disconnecting, invoking server-side hub methods, invoking client-side hub methods, authorizing
/// hub clients and rejoining hub groups.
/// </param>
/// <returns>
/// The <see cref="IHubPipeline"/> itself with the newly added module allowing
/// <see cref="IHubPipeline.AddModule"/> calls to be chained.
/// This method mutates the pipeline it is invoked on so it is not necessary to store its result.
/// </returns>
IHubPipeline AddModule(IHubPipelineModule pipelineModule);
}
}
@@ -0,0 +1,76 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// Implementations of this interface are responsible for executing operation required to complete various stages
/// hub processing such as connecting, reconnecting, disconnecting, invoking server-side hub methods, invoking
/// client-side hub methods, authorizing hub clients and rejoining hub groups.
/// </summary>
public interface IHubPipelineInvoker
{
/// <summary>
/// Invokes a server-side hub method.
/// </summary>
/// <param name="context">A description of the server-side hub method invocation.</param>
/// <returns>An asynchronous operation giving the return value of the server-side hub method invocation.</returns>
Task<object> Invoke(IHubIncomingInvokerContext context);
/// <summary>
/// Invokes a client-side hub method.
/// </summary>
/// <param name="context">A description of the client-side hub method invocation.</param>
Task Send(IHubOutgoingInvokerContext context);
/// <summary>
/// To be called when a client connects to the <see cref="HubDispatcher"/> for each <see cref="IHub"/> the client
/// connects to. By default, this results in the <see cref="IHub"/>'s OnConnected method being invoked.
/// </summary>
/// <param name="hub">A <see cref="IHub"/> the client is connected to.</param>
Task Connect(IHub hub);
/// <summary>
/// To be called when a client reconnects to the <see cref="HubDispatcher"/> for each <see cref="IHub"/> the client
/// connects to. By default, this results in the <see cref="IHub"/>'s OnReconnected method being invoked.
/// </summary>
/// <param name="hub">A <see cref="IHub"/> the client is reconnected to.</param>
Task Reconnect(IHub hub);
/// <summary>
/// To be called when a client disconnects from the <see cref="HubDispatcher"/> for each <see cref="IHub"/> the client
/// was connected to. By default, this results in the <see cref="IHub"/>'s OnDisconnected method being invoked.
/// </summary>
/// <param name="hub">A <see cref="IHub"/> the client was disconnected from.</param>
Task Disconnect(IHub hub);
/// <summary>
/// To be called before a client subscribes to signals belonging to the hub described by the <see cref="HubDescriptor"/>.
/// By default, the <see cref="AuthorizeModule"/> will look for attributes on the <see cref="IHub"/> to help determine if
/// the client is authorized to subscribe to method invocations for the described hub.
/// </summary>
/// <param name="hubDescriptor">A description of the hub the client is attempting to connect to.</param>
/// <param name="request">
/// The connect request being made by the client which should include the client's
/// <see cref="System.Security.Principal.IPrincipal"/> User.
/// </param>
/// <returns>true, if the client is authorized to subscribe to client-side hub method invocations; false, otherwise.</returns>
bool AuthorizeConnect(HubDescriptor hubDescriptor, IRequest request);
/// <summary>
/// This method determines which of the groups belonging to the hub described by the <see cref="HubDescriptor"/> the client should be
/// allowed to rejoin.
/// By default, clients that are reconnecting to the server will be removed from all groups they may have previously been a member of,
/// because untrusted clients may claim to be a member of groups they were never authorized to join.
/// </summary>
/// <param name="hubDescriptor">A description of the hub for which the client is attempting to rejoin groups.</param>
/// <param name="request">The reconnect request being made by the client that is attempting to rejoin groups.</param>
/// <param name="groups">
/// The list of groups belonging to the relevant hub that the client claims to have been a member of before the reconnect.
/// </param>
/// <returns>A list of groups the client is allowed to rejoin.</returns>
IList<string> RejoiningGroups(HubDescriptor hubDescriptor, IRequest request, IList<string> groups);
}
}
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// An <see cref="IHubPipelineModule"/> can intercept and customize various stages of hub processing such as connecting,
/// reconnecting, disconnecting, invoking server-side hub methods, invoking client-side hub methods, authorizing hub
/// clients and rejoining hub groups.
/// Modules can be be activated by calling <see cref="IHubPipeline.AddModule"/>.
/// The combined modules added to the <see cref="IHubPipeline" /> are invoked via the <see cref="IHubPipelineInvoker"/>
/// interface.
/// </summary>
public interface IHubPipelineModule
{
/// <summary>
/// Wraps a function that invokes a server-side hub method. Even if a client has not been authorized to connect
/// to a hub, it will still be authorized to invoke server-side methods on that hub unless it is prevented in
/// <see cref="IHubPipelineModule.BuildIncoming"/> by not executing the invoke parameter.
/// </summary>
/// <param name="invoke">A function that invokes a server-side hub method.</param>
/// <returns>A wrapped function that invokes a server-side hub method.</returns>
Func<IHubIncomingInvokerContext, Task<object>> BuildIncoming(Func<IHubIncomingInvokerContext, Task<object>> invoke);
/// <summary>
/// Wraps a function that invokes a client-side hub method.
/// </summary>
/// <param name="send">A function that invokes a client-side hub method.</param>
/// <returns>A wrapped function that invokes a client-side hub method.</returns>
Func<IHubOutgoingInvokerContext, Task> BuildOutgoing(Func<IHubOutgoingInvokerContext, Task> send);
/// <summary>
/// Wraps a function that is called when a client connects to the <see cref="HubDispatcher"/> for each
/// <see cref="IHub"/> the client connects to. By default, this results in the <see cref="IHub"/>'s
/// OnConnected method being invoked.
/// </summary>
/// <param name="connect">A function to be called when a client connects to a hub.</param>
/// <returns>A wrapped function to be called when a client connects to a hub.</returns>
Func<IHub, Task> BuildConnect(Func<IHub, Task> connect);
/// <summary>
/// Wraps a function that is called when a client reconnects to the <see cref="HubDispatcher"/> for each
/// <see cref="IHub"/> the client connects to. By default, this results in the <see cref="IHub"/>'s
/// OnReconnected method being invoked.
/// </summary>
/// <param name="reconnect">A function to be called when a client reconnects to a hub.</param>
/// <returns>A wrapped function to be called when a client reconnects to a hub.</returns>
Func<IHub, Task> BuildReconnect(Func<IHub, Task> reconnect);
/// <summary>
/// Wraps a function that is called when a client disconnects from the <see cref="HubDispatcher"/> for each
/// <see cref="IHub"/> the client was connected to. By default, this results in the <see cref="IHub"/>'s
/// OnDisconnected method being invoked.
/// </summary>
/// <param name="disconnect">A function to be called when a client disconnects from a hub.</param>
/// <returns>A wrapped function to be called when a client disconnects from a hub.</returns>
Func<IHub, Task> BuildDisconnect(Func<IHub, Task> disconnect);
/// <summary>
/// Wraps a function to be called before a client subscribes to signals belonging to the hub described by the
/// <see cref="HubDescriptor"/>. By default, the <see cref="AuthorizeModule"/> will look for attributes on the
/// <see cref="IHub"/> to help determine if the client is authorized to subscribe to method invocations for the
/// described hub.
/// The function returns true if the client is authorized to subscribe to client-side hub method
/// invocations; false, otherwise.
/// </summary>
/// <param name="authorizeConnect">
/// A function that dictates whether or not the client is authorized to connect to the described Hub.
/// </param>
/// <returns>
/// A wrapped function that dictates whether or not the client is authorized to connect to the described Hub.
/// </returns>
Func<HubDescriptor, IRequest, bool> BuildAuthorizeConnect(Func<HubDescriptor, IRequest, bool> authorizeConnect);
/// <summary>
/// Wraps a function that determines which of the groups belonging to the hub described by the <see cref="HubDescriptor"/>
/// the client should be allowed to rejoin.
/// By default, clients will rejoin all the groups they were in prior to reconnecting.
/// </summary>
/// <param name="rejoiningGroups">A function that determines which groups the client should be allowed to rejoin.</param>
/// <returns>A wrapped function that determines which groups the client should be allowed to rejoin.</returns>
Func<HubDescriptor, IRequest, IList<string>, IList<string>> BuildRejoiningGroups(Func<HubDescriptor, IRequest, IList<string>, IList<string>> rejoiningGroups);
}
}
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Microsoft.AspNet.SignalR.Hubs
{
public static class ReflectionHelper
{
private static readonly Type[] _excludeTypes = new[] { typeof(Hub), typeof(object) };
private static readonly Type[] _excludeInterfaces = new[] { typeof(IHub), typeof(IDisposable) };
public static IEnumerable<MethodInfo> GetExportedHubMethods(Type type)
{
if (!typeof(IHub).IsAssignableFrom(type))
{
return Enumerable.Empty<MethodInfo>();
}
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
var allInterfaceMethods = _excludeInterfaces.SelectMany(i => GetInterfaceMethods(type, i));
return methods.Except(allInterfaceMethods).Where(IsValidHubMethod);
}
private static bool IsValidHubMethod(MethodInfo methodInfo)
{
return !(_excludeTypes.Contains(methodInfo.GetBaseDefinition().DeclaringType) ||
methodInfo.IsSpecialName);
}
private static IEnumerable<MethodInfo> GetInterfaceMethods(Type type, Type iface)
{
if (!iface.IsAssignableFrom(type))
{
return Enumerable.Empty<MethodInfo>();
}
return type.GetInterfaceMap(iface).TargetMethods;
}
public static TResult GetAttributeValue<TAttribute, TResult>(ICustomAttributeProvider source, Func<TAttribute, TResult> valueGetter)
where TAttribute : Attribute
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (valueGetter == null)
{
throw new ArgumentNullException("valueGetter");
}
var attributes = source.GetCustomAttributes(typeof(TAttribute), false)
.Cast<TAttribute>()
.ToList();
if (attributes.Any())
{
return valueGetter(attributes[0]);
}
return default(TResult);
}
}
}
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure;
namespace Microsoft.AspNet.SignalR.Hubs
{
public abstract class SignalProxy : DynamicObject, IClientProxy
{
private readonly IList<string> _exclude;
private readonly string _prefix;
protected SignalProxy(Func<string, ClientHubInvocation, IList<string>, Task> send, string signal, string hubName, string prefix, IList<string> exclude)
{
Send = send;
Signal = signal;
HubName = hubName;
_prefix = prefix;
_exclude = exclude;
}
protected Func<string, ClientHubInvocation, IList<string>, Task> Send { get; private set; }
protected string Signal { get; private set; }
protected string HubName { get; private set; }
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
return false;
}
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "The compiler generates calls to invoke this")]
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
result = Invoke(binder.Name, args);
return true;
}
public Task Invoke(string method, params object[] args)
{
var invocation = GetInvocationData(method, args);
string signal = _prefix + HubName + "." + Signal;
return Send(signal, invocation, _exclude);
}
protected virtual ClientHubInvocation GetInvocationData(string method, object[] args)
{
return new ClientHubInvocation
{
Hub = HubName,
Method = method,
Args = args,
Target = Signal
};
}
}
}
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Microsoft.AspNet.SignalR.Hubs
{
/// <summary>
/// A change tracking dictionary.
/// </summary>
public class StateChangeTracker
{
private readonly IDictionary<string, object> _values;
// Keep track of everyting that changed since creation
private readonly IDictionary<string, object> _oldValues = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
public StateChangeTracker()
{
_values = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
public StateChangeTracker(IDictionary<string, object> values)
{
_values = values;
}
public object this[string key]
{
get
{
object result;
_values.TryGetValue(key, out result);
return DynamicDictionary.Wrap(result);
}
set
{
if (!_oldValues.ContainsKey(key))
{
object oldValue;
_values.TryGetValue(key, out oldValue);
_oldValues[key] = oldValue;
}
_values[key] = value;
}
}
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This might be expensive")]
public IDictionary<string, object> GetChanges()
{
var changes = (from key in _oldValues.Keys
let oldValue = _oldValues[key]
let newValue = _values[key]
where !Object.Equals(oldValue, newValue)
select new
{
Key = key,
Value = newValue
}).ToDictionary(p => p.Key, p => p.Value);
return changes.Count > 0 ? changes : null;
}
}
}
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure;
namespace Microsoft.AspNet.SignalR.Hubs
{
public class StatefulSignalProxy : SignalProxy
{
private readonly StateChangeTracker _tracker;
public StatefulSignalProxy(Func<string, ClientHubInvocation, IList<string>, Task> send, string signal, string hubName, string prefix, StateChangeTracker tracker)
: base(send, signal, prefix, hubName, ListHelper<string>.Empty)
{
_tracker = tracker;
}
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "The compiler generates calls to invoke this")]
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_tracker[binder.Name] = value;
return true;
}
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "The compiler generates calls to invoke this")]
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = _tracker[binder.Name];
return true;
}
protected override ClientHubInvocation GetInvocationData(string method, object[] args)
{
return new ClientHubInvocation
{
Hub = HubName,
Method = method,
Args = args,
Target = Signal,
State = _tracker.GetChanges()
};
}
}
}