Merge pull request #39 from NzbDrone/internal-signalr

Internal signalr
pull/4/head
Keivan Beigi 11 years ago
commit 5fd25ff3f3

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<Dictionary>
<Words>
<Recognized>
<Word>Ack</Word>
<Word>Minifier</Word>
<Word>Jsonp</Word>
<Word>Linktionary</Word>
<Word>Scaleout</Word>
<Word>Redis</Word>
<Word>Owin</Word>
<Word>Stringify</Word>
<Word>Unminify</Word>
<Word>Unminified</Word>
<Word>Stateful</Word>
<Word>SignalR</Word>
<Word>Hubservable</Word>
<Word>Sse</Word>
<Word>GitHub</Word>
</Recognized>
</Words>
</Dictionary>

@ -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;
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyCompany("Microsoft Open Technologies, Inc.")]
[assembly: AssemblyCopyright("© Microsoft Open Technologies, Inc. All rights reserved.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyConfiguration("")]
[assembly: ComVisible(false)]
[assembly: CLSCompliant(false)]
[assembly: NeutralResourcesLanguage("en-US")]

@ -0,0 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Reflection;
[assembly: AssemblyVersion("1.1.0")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.3")]

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
//
// To add a suppression to this file, right-click the message in the
// Code Analysis results, point to "Suppress Message", and click
// "In Suppression File".
// You do not need to add suppressions to this file manually.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Strong naming is done on the CI.")]
[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "param", Scope = "resource", Target = "Microsoft.AspNet.SignalR.Resources.resources")]
[assembly: SuppressMessage("Microsoft.Usage", "CA2243:AttributeStringLiteralsShouldParseCorrectly", Justification = "We use semver")]
[assembly: SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Scope = "member", Target = "Microsoft.AspNet.SignalR.Messaging.ScaleoutTaskQueue.#.cctor()", Justification = "The task is cached")]

@ -0,0 +1,227 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="New Rule Set" Description=" " ToolsVersion="11.0">
<Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
<Rule Id="CA1000" Action="Error" />
<Rule Id="CA1001" Action="Error" />
<Rule Id="CA1002" Action="Error" />
<Rule Id="CA1003" Action="Error" />
<Rule Id="CA1004" Action="Error" />
<Rule Id="CA1005" Action="Error" />
<Rule Id="CA1007" Action="Error" />
<Rule Id="CA1008" Action="Error" />
<Rule Id="CA1010" Action="Error" />
<Rule Id="CA1011" Action="Error" />
<Rule Id="CA1012" Action="Error" />
<Rule Id="CA1013" Action="Error" />
<Rule Id="CA1014" Action="Error" />
<Rule Id="CA1016" Action="Error" />
<Rule Id="CA1017" Action="Error" />
<Rule Id="CA1018" Action="Error" />
<Rule Id="CA1019" Action="Error" />
<Rule Id="CA1021" Action="Error" />
<Rule Id="CA1023" Action="Error" />
<Rule Id="CA1024" Action="Error" />
<Rule Id="CA1025" Action="Error" />
<Rule Id="CA1026" Action="Error" />
<Rule Id="CA1027" Action="Error" />
<Rule Id="CA1028" Action="Error" />
<Rule Id="CA1030" Action="Error" />
<Rule Id="CA1031" Action="Error" />
<Rule Id="CA1032" Action="Error" />
<Rule Id="CA1034" Action="Error" />
<Rule Id="CA1035" Action="Error" />
<Rule Id="CA1036" Action="Error" />
<Rule Id="CA1038" Action="Error" />
<Rule Id="CA1039" Action="Error" />
<Rule Id="CA1040" Action="Error" />
<Rule Id="CA1041" Action="Error" />
<Rule Id="CA1043" Action="Error" />
<Rule Id="CA1044" Action="Error" />
<Rule Id="CA1045" Action="Error" />
<Rule Id="CA1046" Action="Error" />
<Rule Id="CA1047" Action="Error" />
<Rule Id="CA1048" Action="Error" />
<Rule Id="CA1049" Action="Error" />
<Rule Id="CA1050" Action="Error" />
<Rule Id="CA1051" Action="Error" />
<Rule Id="CA1052" Action="Error" />
<Rule Id="CA1053" Action="Error" />
<Rule Id="CA1057" Action="Error" />
<Rule Id="CA1058" Action="Error" />
<Rule Id="CA1059" Action="Error" />
<Rule Id="CA1060" Action="Error" />
<Rule Id="CA1061" Action="Error" />
<Rule Id="CA1062" Action="Error" />
<Rule Id="CA1063" Action="Error" />
<Rule Id="CA1064" Action="Error" />
<Rule Id="CA1065" Action="Error" />
<Rule Id="CA1300" Action="Error" />
<Rule Id="CA1301" Action="Error" />
<Rule Id="CA1302" Action="Error" />
<Rule Id="CA1303" Action="Error" />
<Rule Id="CA1304" Action="Error" />
<Rule Id="CA1305" Action="Error" />
<Rule Id="CA1306" Action="Error" />
<Rule Id="CA1307" Action="Error" />
<Rule Id="CA1308" Action="Error" />
<Rule Id="CA1309" Action="Error" />
<Rule Id="CA1400" Action="Error" />
<Rule Id="CA1401" Action="Error" />
<Rule Id="CA1402" Action="Error" />
<Rule Id="CA1403" Action="Error" />
<Rule Id="CA1404" Action="Error" />
<Rule Id="CA1405" Action="Error" />
<Rule Id="CA1406" Action="Error" />
<Rule Id="CA1407" Action="Error" />
<Rule Id="CA1408" Action="Error" />
<Rule Id="CA1409" Action="Error" />
<Rule Id="CA1410" Action="Error" />
<Rule Id="CA1411" Action="Error" />
<Rule Id="CA1412" Action="Error" />
<Rule Id="CA1413" Action="Error" />
<Rule Id="CA1414" Action="Error" />
<Rule Id="CA1415" Action="Error" />
<Rule Id="CA1500" Action="Error" />
<Rule Id="CA1501" Action="Error" />
<Rule Id="CA1502" Action="Error" />
<Rule Id="CA1504" Action="Error" />
<Rule Id="CA1505" Action="Error" />
<Rule Id="CA1506" Action="Error" />
<Rule Id="CA1600" Action="Error" />
<Rule Id="CA1601" Action="Error" />
<Rule Id="CA1700" Action="Error" />
<Rule Id="CA1701" Action="Error" />
<Rule Id="CA1702" Action="Error" />
<Rule Id="CA1703" Action="Error" />
<Rule Id="CA1704" Action="Error" />
<Rule Id="CA1707" Action="Error" />
<Rule Id="CA1708" Action="Error" />
<Rule Id="CA1709" Action="Error" />
<Rule Id="CA1712" Action="Error" />
<Rule Id="CA1713" Action="Error" />
<Rule Id="CA1714" Action="Error" />
<Rule Id="CA1715" Action="Error" />
<Rule Id="CA1716" Action="Error" />
<Rule Id="CA1717" Action="Error" />
<Rule Id="CA1719" Action="Error" />
<Rule Id="CA1720" Action="Error" />
<Rule Id="CA1721" Action="Error" />
<Rule Id="CA1722" Action="Error" />
<Rule Id="CA1724" Action="Error" />
<Rule Id="CA1725" Action="Error" />
<Rule Id="CA1726" Action="Error" />
<Rule Id="CA1800" Action="Error" />
<Rule Id="CA1801" Action="Error" />
<Rule Id="CA1802" Action="Error" />
<Rule Id="CA1804" Action="Error" />
<Rule Id="CA1806" Action="Error" />
<Rule Id="CA1809" Action="Error" />
<Rule Id="CA1810" Action="Error" />
<Rule Id="CA1811" Action="Error" />
<Rule Id="CA1812" Action="Error" />
<Rule Id="CA1813" Action="Error" />
<Rule Id="CA1814" Action="Error" />
<Rule Id="CA1815" Action="Error" />
<Rule Id="CA1819" Action="Error" />
<Rule Id="CA1820" Action="Error" />
<Rule Id="CA1821" Action="Error" />
<Rule Id="CA1822" Action="Error" />
<Rule Id="CA1823" Action="Error" />
<Rule Id="CA1824" Action="Error" />
<Rule Id="CA1900" Action="Error" />
<Rule Id="CA1901" Action="Error" />
<Rule Id="CA1903" Action="Error" />
<Rule Id="CA2000" Action="Error" />
<Rule Id="CA2001" Action="Error" />
<Rule Id="CA2002" Action="Error" />
<Rule Id="CA2003" Action="Error" />
<Rule Id="CA2004" Action="Error" />
<Rule Id="CA2006" Action="Error" />
<Rule Id="CA2100" Action="Error" />
<Rule Id="CA2101" Action="Error" />
<Rule Id="CA2102" Action="Error" />
<Rule Id="CA2103" Action="Error" />
<Rule Id="CA2104" Action="Error" />
<Rule Id="CA2105" Action="Error" />
<Rule Id="CA2106" Action="Error" />
<Rule Id="CA2107" Action="Error" />
<Rule Id="CA2108" Action="Error" />
<Rule Id="CA2109" Action="Error" />
<Rule Id="CA2111" Action="Error" />
<Rule Id="CA2112" Action="Error" />
<Rule Id="CA2114" Action="Error" />
<Rule Id="CA2115" Action="Error" />
<Rule Id="CA2116" Action="Error" />
<Rule Id="CA2117" Action="Error" />
<Rule Id="CA2118" Action="Error" />
<Rule Id="CA2119" Action="Error" />
<Rule Id="CA2120" Action="Error" />
<Rule Id="CA2121" Action="Error" />
<Rule Id="CA2122" Action="Error" />
<Rule Id="CA2123" Action="Error" />
<Rule Id="CA2124" Action="Error" />
<Rule Id="CA2126" Action="Error" />
<Rule Id="CA2130" Action="Error" />
<Rule Id="CA2131" Action="Error" />
<Rule Id="CA2132" Action="Error" />
<Rule Id="CA2133" Action="Error" />
<Rule Id="CA2134" Action="Error" />
<Rule Id="CA2135" Action="Error" />
<Rule Id="CA2136" Action="Error" />
<Rule Id="CA2137" Action="Error" />
<Rule Id="CA2138" Action="Error" />
<Rule Id="CA2139" Action="Error" />
<Rule Id="CA2140" Action="Error" />
<Rule Id="CA2141" Action="Error" />
<Rule Id="CA2142" Action="Error" />
<Rule Id="CA2143" Action="Error" />
<Rule Id="CA2144" Action="Error" />
<Rule Id="CA2145" Action="Error" />
<Rule Id="CA2146" Action="Error" />
<Rule Id="CA2147" Action="Error" />
<Rule Id="CA2149" Action="Error" />
<Rule Id="CA2151" Action="Error" />
<Rule Id="CA2200" Action="Error" />
<Rule Id="CA2201" Action="Error" />
<Rule Id="CA2202" Action="Error" />
<Rule Id="CA2204" Action="Error" />
<Rule Id="CA2205" Action="Error" />
<Rule Id="CA2207" Action="Error" />
<Rule Id="CA2208" Action="Error" />
<Rule Id="CA2210" Action="Error" />
<Rule Id="CA2211" Action="Error" />
<Rule Id="CA2212" Action="Error" />
<Rule Id="CA2213" Action="Error" />
<Rule Id="CA2214" Action="Error" />
<Rule Id="CA2215" Action="Error" />
<Rule Id="CA2216" Action="Error" />
<Rule Id="CA2217" Action="Error" />
<Rule Id="CA2218" Action="Error" />
<Rule Id="CA2219" Action="Error" />
<Rule Id="CA2220" Action="Error" />
<Rule Id="CA2221" Action="Error" />
<Rule Id="CA2222" Action="Error" />
<Rule Id="CA2223" Action="Error" />
<Rule Id="CA2224" Action="Error" />
<Rule Id="CA2225" Action="Error" />
<Rule Id="CA2226" Action="Error" />
<Rule Id="CA2227" Action="Error" />
<Rule Id="CA2228" Action="Error" />
<Rule Id="CA2229" Action="Error" />
<Rule Id="CA2230" Action="Error" />
<Rule Id="CA2231" Action="Error" />
<Rule Id="CA2232" Action="Error" />
<Rule Id="CA2233" Action="Error" />
<Rule Id="CA2234" Action="Error" />
<Rule Id="CA2235" Action="Error" />
<Rule Id="CA2236" Action="Error" />
<Rule Id="CA2237" Action="Error" />
<Rule Id="CA2238" Action="Error" />
<Rule Id="CA2239" Action="Error" />
<Rule Id="CA2240" Action="Error" />
<Rule Id="CA2241" Action="Error" />
<Rule Id="CA2242" Action="Error" />
<Rule Id="CA2243" Action="Error" />
<Rule Id="CA5122" Action="Error" />
</Rules>
</RuleSet>

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(ArtifactsDir)' != ''">
<OutputPath Condition="$(UseBinPath) == ''">$(ArtifactsDir)\$(MSBuildProjectName)</OutputPath>
<OutputPath Condition="$(UseBinPath) == 'true'">$(ArtifactsDir)\$(MSBuildProjectName)\bin</OutputPath>
</PropertyGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)Microsoft.AspNet.SignalR.ruleset</CodeAnalysisRuleSet>
<RunCodeAnalysis Condition="'$(RunCodeAnalysis)' == ''">false</RunCodeAnalysis>
<NoWarn>1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition="'$(RunCodeAnalysis)' == 'true'">
<DefineConstants>$(DefineConstants);CODE_ANALYSIS</DefineConstants>
<VisualStudioVersion>11.0</VisualStudioVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(OS)' != 'Windows_NT'">
<DefineConstants>$(DefineConstants);MONO</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(KeyFile)' != '' And '$(DisableSigning)' != 'true'">
<DefineConstants>$(DefineConstants);SIGNED</DefineConstants>
<SignAssembly>true</SignAssembly>
<DelaySign>true</DelaySign>
<AssemblyOriginatorKeyFile>$(KeyFile)</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup Condition="Exists('$(MSBuildThisFileDirectory)GlobalSuppressions.cs')">
<Compile Include="$(MSBuildThisFileDirectory)GlobalSuppressions.cs">
<Link>GlobalSuppressions.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<CodeAnalysisDictionary Include="$(MSBuildThisFileDirectory)CodeAnalysisDictionary.xml" />
</ItemGroup>
</Project>

@ -0,0 +1,160 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Security.Principal;
using Microsoft.AspNet.SignalR.Hubs;
namespace Microsoft.AspNet.SignalR
{
/// <summary>
/// Apply to Hubs and Hub methods to authorize client connections to Hubs and authorize client invocations of Hub methods.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "MVC and WebAPI don't seal their AuthorizeAttributes")]
public class AuthorizeAttribute : Attribute, IAuthorizeHubConnection, IAuthorizeHubMethodInvocation
{
private string _roles;
private string[] _rolesSplit = new string[0];
private string _users;
private string[] _usersSplit = new string[0];
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Justification = "Already somewhat represented by set-only RequiredOutgoing property.")]
protected bool? _requireOutgoing;
/// <summary>
/// Set to false to apply authorization only to the invocations of any of the Hub's server-side methods.
/// This property only affects attributes applied to the Hub class.
/// This property cannot be read.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Justification = "Must be property because this is an attribute parameter.")]
public bool RequireOutgoing
{
// It is impossible to tell here whether the attribute is being applied to a method or class. This makes
// it impossible to determine whether the value should be true or false when _requireOutgoing is null.
// It is also impossible to have a Nullable attribute parameter type.
get { throw new NotImplementedException(Resources.Error_DoNotReadRequireOutgoing); }
set { _requireOutgoing = value; }
}
/// <summary>
/// Gets or sets the user roles.
/// </summary>
public string Roles
{
get { return _roles ?? String.Empty; }
set
{
_roles = value;
_rolesSplit = SplitString(value);
}
}
/// <summary>
/// Gets or sets the authorized users.
/// </summary>
public string Users
{
get { return _users ?? String.Empty; }
set
{
_users = value;
_usersSplit = SplitString(value);
}
}
/// <summary>
/// Determines 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 (re)connect request from the client.</param>
/// <returns>true if the caller is authorized to connect to the hub; otherwise, false.</returns>
public virtual bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
if (request == null)
{
throw new ArgumentNullException("request");
}
// If RequireOutgoing is explicitly set to false, authorize all connections.
if (_requireOutgoing.HasValue && !_requireOutgoing.Value)
{
return true;
}
return UserAuthorized(request.User);
}
/// <summary>
/// Determines 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>
public virtual bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
if (hubIncomingInvokerContext == null)
{
throw new ArgumentNullException("hubIncomingInvokerContext");
}
// It is impossible to require outgoing auth at the method level with SignalR's current design.
// Even though this isn't the stage at which outgoing auth would be applied, we want to throw a runtime error
// to indicate when the attribute is being used with obviously incorrect expectations.
// We must explicitly check if _requireOutgoing is true since it is a Nullable type.
if (appliesToMethod && (_requireOutgoing == true))
{
throw new ArgumentException(Resources.Error_MethodLevelOutgoingAuthorization);
}
return UserAuthorized(hubIncomingInvokerContext.Hub.Context.User);
}
/// <summary>
/// When overridden, provides an entry point for custom authorization checks.
/// Called by <see cref="AuthorizeAttribute.AuthorizeHubConnection"/> and <see cref="AuthorizeAttribute.AuthorizeHubMethodInvocation"/>.
/// </summary>
/// <param name="user">The <see cref="System.Security.Principal.IPrincipal"/> for the client being authorize</param>
/// <returns>true if the user is authorized, otherwise, false</returns>
protected virtual bool UserAuthorized(IPrincipal user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
if (!user.Identity.IsAuthenticated)
{
return false;
}
if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
{
return false;
}
if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole))
{
return false;
}
return true;
}
private static string[] SplitString(string original)
{
if (String.IsNullOrEmpty(original))
{
return new string[0];
}
var split = from piece in original.Split(',')
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
}
}

@ -0,0 +1,61 @@
// 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.Configuration
{
internal static class ConfigurationExtensions
{
public const int MissedTimeoutsBeforeClientReconnect = 2;
public const int HeartBeatsPerKeepAlive = 2;
public const int HeartBeatsPerDisconnectTimeout = 6;
/// <summary>
/// The amount of time the client should wait without seeing a keep alive before trying to reconnect.
/// </summary>
public static TimeSpan? KeepAliveTimeout(this IConfigurationManager config)
{
if (config.KeepAlive != null)
{
return TimeSpan.FromTicks(config.KeepAlive.Value.Ticks * MissedTimeoutsBeforeClientReconnect);
}
else
{
return null;
}
}
/// <summary>
/// The interval between successively checking connection states.
/// </summary>
public static TimeSpan HeartbeatInterval(this IConfigurationManager config)
{
if (config.KeepAlive != null)
{
return TimeSpan.FromTicks(config.KeepAlive.Value.Ticks / HeartBeatsPerKeepAlive);
}
else
{
// If KeepAlives are disabled, have the heartbeat run at the same rate it would if the KeepAlive was
// kept at the default value.
return TimeSpan.FromTicks(config.DisconnectTimeout.Ticks / HeartBeatsPerDisconnectTimeout);
}
}
/// <summary>
/// The amount of time a Topic should stay in memory after its last subscriber is removed.
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
public static TimeSpan TopicTtl(this IConfigurationManager config)
{
// If the deep-alive is disabled, don't take it into account when calculating the topic TTL.
var keepAliveTimeout = config.KeepAliveTimeout() ?? TimeSpan.Zero;
// Keep topics alive for twice as long as we let connections to reconnect. (The DisconnectTimeout)
// Also add twice the keep-alive timeout since clients might take a while to notice they are disconnected.
// This should be a very conservative estimate for how long we must wait before considering a topic dead.
return TimeSpan.FromTicks((config.DisconnectTimeout.Ticks + keepAliveTimeout.Ticks) * 2);
}
}
}

@ -0,0 +1,89 @@
// 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.Configuration
{
public class DefaultConfigurationManager : IConfigurationManager
{
// The below effectively sets the minimum heartbeat to once per second.
// if _minimumKeepAlive != 2 seconds, update the ArguementOutOfRanceExceptionMessage below
private static readonly TimeSpan _minimumKeepAlive = TimeSpan.FromSeconds(2);
// if _minimumKeepAlivesPerDisconnectTimeout != 3, update the ArguementOutOfRanceExceptionMessage below
private const int _minimumKeepAlivesPerDisconnectTimeout = 3;
// if _minimumDisconnectTimeout != 6 seconds, update the ArguementOutOfRanceExceptionMessage below
private static readonly TimeSpan _minimumDisconnectTimeout = TimeSpan.FromTicks(_minimumKeepAlive.Ticks * _minimumKeepAlivesPerDisconnectTimeout);
private bool _keepAliveConfigured;
private TimeSpan? _keepAlive;
private TimeSpan _disconnectTimeout;
public DefaultConfigurationManager()
{
ConnectionTimeout = TimeSpan.FromSeconds(110);
DisconnectTimeout = TimeSpan.FromSeconds(30);
DefaultMessageBufferSize = 1000;
}
// TODO: Should we guard against negative TimeSpans here like everywhere else?
public TimeSpan ConnectionTimeout
{
get;
set;
}
public TimeSpan DisconnectTimeout
{
get
{
return _disconnectTimeout;
}
set
{
if (value < _minimumDisconnectTimeout)
{
throw new ArgumentOutOfRangeException("value", Resources.Error_DisconnectTimeoutMustBeAtLeastSixSeconds);
}
if (_keepAliveConfigured)
{
throw new InvalidOperationException(Resources.Error_DisconnectTimeoutCannotBeConfiguredAfterKeepAlive);
}
_disconnectTimeout = value;
_keepAlive = TimeSpan.FromTicks(_disconnectTimeout.Ticks / _minimumKeepAlivesPerDisconnectTimeout);
}
}
public TimeSpan? KeepAlive
{
get
{
return _keepAlive;
}
set
{
if (value < _minimumKeepAlive)
{
throw new ArgumentOutOfRangeException("value", Resources.Error_KeepAliveMustBeGreaterThanTwoSeconds);
}
if (value > TimeSpan.FromTicks(_disconnectTimeout.Ticks / _minimumKeepAlivesPerDisconnectTimeout))
{
throw new ArgumentOutOfRangeException("value", Resources.Error_KeepAliveMustBeNoMoreThanAThirdOfTheDisconnectTimeout);
}
_keepAlive = value;
_keepAliveConfigured = true;
}
}
public int DefaultMessageBufferSize
{
get;
set;
}
}
}

@ -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;
namespace Microsoft.AspNet.SignalR.Configuration
{
/// <summary>
/// Provides access to server configuration.
/// </summary>
public interface IConfigurationManager
{
/// <summary>
/// Gets or sets a <see cref="TimeSpan"/> representing the amount of time to leave a connection open before timing out.
/// </summary>
TimeSpan ConnectionTimeout { get; set; }
/// <summary>
/// Gets or sets a <see cref="TimeSpan"/> representing the amount of time to wait after a connection goes away before raising the disconnect event.
/// </summary>
TimeSpan DisconnectTimeout { get; set; }
/// <summary>
/// Gets or sets a <see cref="TimeSpan"/> representing the amount of time between send keep alive messages.
/// If enabled, this value must be at least two seconds. Set to null to disable.
/// </summary>
TimeSpan? KeepAlive { get; set; }
/// <summary>
/// Gets of sets the number of messages to buffer for a specific signal.
/// </summary>
int DefaultMessageBufferSize { get; set; }
}
}

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR
{
public class ConnectionConfiguration
{
// Resolver isn't set to GlobalHost.DependencyResolver in the ctor because it is lazily created.
private IDependencyResolver _resolver;
/// <summary>
/// The dependency resolver to use for the hub connection.
/// </summary>
public IDependencyResolver Resolver
{
get { return _resolver ?? GlobalHost.DependencyResolver; }
set { _resolver = value; }
}
/// <summary>
/// Determines if browsers can make cross domain requests to SignalR endpoints.
/// </summary>
public bool EnableCrossDomain { get; set; }
}
}

@ -0,0 +1,62 @@
// 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.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure;
namespace Microsoft.AspNet.SignalR
{
public static class ConnectionExtensions
{
/// <summary>
/// Sends a message to all connections subscribed to the specified signal. An example of signal may be a
/// specific connection id.
/// </summary>
/// <param name="connection">The connection</param>
/// <param name="connectionId">The connectionId to send to.</param>
/// <param name="value">The value to publish.</param>
/// <param name="excludeConnectionIds">The list of connection ids to exclude</param>
/// <returns>A task that represents when the broadcast is complete.</returns>
public static Task Send(this IConnection connection, string connectionId, object value, params string[] excludeConnectionIds)
{
if (connection == null)
{
throw new ArgumentNullException("connection");
}
if (string.IsNullOrEmpty(connectionId))
{
throw new ArgumentException(Resources.Error_ArgumentNullOrEmpty, "connectionId");
}
var message = new ConnectionMessage(PrefixHelper.GetConnectionId(connectionId),
value,
PrefixHelper.GetPrefixedConnectionIds(excludeConnectionIds));
return connection.Send(message);
}
/// <summary>
/// Broadcasts a value to all connections, excluding the connection ids specified.
/// </summary>
/// <param name="connection">The connection</param>
/// <param name="value">The value to broadcast.</param>
/// <param name="excludeConnectionIds">The list of connection ids to exclude</param>
/// <returns>A task that represents when the broadcast is complete.</returns>
public static Task Broadcast(this IConnection connection, object value, params string[] excludeConnectionIds)
{
if (connection == null)
{
throw new ArgumentNullException("connection");
}
var message = new ConnectionMessage(connection.DefaultSignal,
value,
PrefixHelper.GetPrefixedConnectionIds(excludeConnectionIds));
return connection.Send(message);
}
}
}

@ -0,0 +1,51 @@
// 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.Infrastructure;
namespace Microsoft.AspNet.SignalR
{
/// <summary>
/// A message sent to one more connections.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Messags are never compared, just used as data.")]
public struct ConnectionMessage
{
/// <summary>
/// The signal to this message should be sent to. Connections subscribed to this signal
/// will receive the message payload.
/// </summary>
public string Signal { get; private set; }
/// <summary>
/// The payload of the message.
/// </summary>
public object Value { get; private set; }
/// <summary>
/// Represents a list of signals that should be used to filter what connections
/// receive this message.
/// </summary>
public IList<string> ExcludedSignals { get; private set; }
public ConnectionMessage(string signal, object value)
: this(signal, value, ListHelper<string>.Empty)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ConnectionMessage"/> class.
/// </summary>
/// <param name="signal">The signal</param>
/// <param name="value">The payload of the message</param>
/// <param name="excludedSignals">The signals to exclude.</param>
public ConnectionMessage(string signal, object value, IList<string> excludedSignals)
: this()
{
Signal = signal;
Value = value;
ExcludedSignals = excludedSignals;
}
}
}

@ -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;
namespace Microsoft.AspNet.SignalR
{
public class Cookie
{
public Cookie(string name, string value)
: this(name, value, String.Empty, String.Empty)
{
}
public Cookie(string name, string value, string domain, string path)
{
Name = name;
Value = value;
Domain = domain;
Path = path;
}
public string Name { get; private set; }
public string Domain { get; private set; }
public string Path { get; private set; }
public string Value { get; private set; }
}
}

@ -0,0 +1,231 @@
// 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.Globalization;
using System.Linq;
using System.Threading;
using Microsoft.AspNet.SignalR.Configuration;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.AspNet.SignalR.Infrastructure;
using Microsoft.AspNet.SignalR.Json;
using Microsoft.AspNet.SignalR.Messaging;
using Microsoft.AspNet.SignalR.Tracing;
using Microsoft.AspNet.SignalR.Transports;
namespace Microsoft.AspNet.SignalR
{
public class DefaultDependencyResolver : IDependencyResolver
{
private readonly Dictionary<Type, IList<Func<object>>> _resolvers = new Dictionary<Type, IList<Func<object>>>();
private readonly HashSet<IDisposable> _trackedDisposables = new HashSet<IDisposable>();
private int _disposed;
[SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "It's easiest")]
public DefaultDependencyResolver()
{
RegisterDefaultServices();
// Hubs
RegisterHubExtensions();
}
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "The resolver is the class that does the most coupling by design.")]
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The resolver disposes dependencies on Dispose.")]
private void RegisterDefaultServices()
{
var traceManager = new Lazy<TraceManager>(() => new TraceManager());
Register(typeof(ITraceManager), () => traceManager.Value);
var serverIdManager = new ServerIdManager();
Register(typeof(IServerIdManager), () => serverIdManager);
var serverMessageHandler = new Lazy<IServerCommandHandler>(() => new ServerCommandHandler(this));
Register(typeof(IServerCommandHandler), () => serverMessageHandler.Value);
var newMessageBus = new Lazy<IMessageBus>(() => new MessageBus(this));
Register(typeof(IMessageBus), () => newMessageBus.Value);
var stringMinifier = new Lazy<IStringMinifier>(() => new StringMinifier());
Register(typeof(IStringMinifier), () => stringMinifier.Value);
var serializer = new Lazy<JsonNetSerializer>();
Register(typeof(IJsonSerializer), () => serializer.Value);
var transportManager = new Lazy<TransportManager>(() => new TransportManager(this));
Register(typeof(ITransportManager), () => transportManager.Value);
var configurationManager = new DefaultConfigurationManager();
Register(typeof(IConfigurationManager), () => configurationManager);
var transportHeartbeat = new Lazy<TransportHeartbeat>(() => new TransportHeartbeat(this));
Register(typeof(ITransportHeartbeat), () => transportHeartbeat.Value);
var connectionManager = new Lazy<ConnectionManager>(() => new ConnectionManager(this));
Register(typeof(IConnectionManager), () => connectionManager.Value);
var ackHandler = new Lazy<AckHandler>();
Register(typeof(IAckHandler), () => ackHandler.Value);
var perfCounterWriter = new Lazy<PerformanceCounterManager>(() => new PerformanceCounterManager(this));
Register(typeof(IPerformanceCounterManager), () => perfCounterWriter.Value);
var protectedData = new DefaultProtectedData();
Register(typeof(IProtectedData), () => protectedData);
}
private void RegisterHubExtensions()
{
var methodDescriptorProvider = new Lazy<ReflectedMethodDescriptorProvider>();
Register(typeof(IMethodDescriptorProvider), () => methodDescriptorProvider.Value);
var hubDescriptorProvider = new Lazy<ReflectedHubDescriptorProvider>(() => new ReflectedHubDescriptorProvider(this));
Register(typeof(IHubDescriptorProvider), () => hubDescriptorProvider.Value);
var parameterBinder = new Lazy<DefaultParameterResolver>();
Register(typeof(IParameterResolver), () => parameterBinder.Value);
var activator = new Lazy<DefaultHubActivator>(() => new DefaultHubActivator(this));
Register(typeof(IHubActivator), () => activator.Value);
var hubManager = new Lazy<DefaultHubManager>(() => new DefaultHubManager(this));
Register(typeof(IHubManager), () => hubManager.Value);
var proxyGenerator = new Lazy<DefaultJavaScriptProxyGenerator>(() => new DefaultJavaScriptProxyGenerator(this));
Register(typeof(IJavaScriptProxyGenerator), () => proxyGenerator.Value);
var requestParser = new Lazy<HubRequestParser>();
Register(typeof(IHubRequestParser), () => requestParser.Value);
var assemblyLocator = new Lazy<DefaultAssemblyLocator>(() => new DefaultAssemblyLocator());
Register(typeof(IAssemblyLocator), () => assemblyLocator.Value);
// Setup the default hub pipeline
var dispatcher = new Lazy<IHubPipeline>(() => new HubPipeline().AddModule(new AuthorizeModule()));
Register(typeof(IHubPipeline), () => dispatcher.Value);
Register(typeof(IHubPipelineInvoker), () => dispatcher.Value);
}
public virtual object GetService(Type serviceType)
{
if (serviceType == null)
{
throw new ArgumentNullException("serviceType");
}
IList<Func<object>> activators;
if (_resolvers.TryGetValue(serviceType, out activators))
{
if (activators.Count == 0)
{
return null;
}
if (activators.Count > 1)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_MultipleActivatorsAreaRegisteredCallGetServices, serviceType.FullName));
}
return Track(activators[0]);
}
return null;
}
public virtual IEnumerable<object> GetServices(Type serviceType)
{
IList<Func<object>> activators;
if (_resolvers.TryGetValue(serviceType, out activators))
{
if (activators.Count == 0)
{
return null;
}
return activators.Select(Track).ToList();
}
return null;
}
public virtual void Register(Type serviceType, Func<object> activator)
{
IList<Func<object>> activators;
if (!_resolvers.TryGetValue(serviceType, out activators))
{
activators = new List<Func<object>>();
_resolvers.Add(serviceType, activators);
}
else
{
activators.Clear();
}
activators.Add(activator);
}
public virtual void Register(Type serviceType, IEnumerable<Func<object>> activators)
{
if (activators == null)
{
throw new ArgumentNullException("activators");
}
IList<Func<object>> list;
if (!_resolvers.TryGetValue(serviceType, out list))
{
list = new List<Func<object>>();
_resolvers.Add(serviceType, list);
}
else
{
list.Clear();
}
foreach (var a in activators)
{
list.Add(a);
}
}
private object Track(Func<object> creator)
{
object obj = creator();
if (_disposed == 0)
{
var disposable = obj as IDisposable;
if (disposable != null)
{
lock (_trackedDisposables)
{
if (_disposed == 0)
{
_trackedDisposables.Add(disposable);
}
}
}
}
return obj;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (Interlocked.Exchange(ref _disposed, 1) == 0)
{
lock (_trackedDisposables)
{
foreach (var d in _trackedDisposables)
{
d.Dispose();
}
_trackedDisposables.Clear();
}
}
}
}
public void Dispose()
{
Dispose(true);
}
}
}

@ -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.Linq;
using System.Threading;
using Microsoft.AspNet.SignalR.Infrastructure;
namespace Microsoft.AspNet.SignalR
{
public static class DependencyResolverExtensions
{
public static T Resolve<T>(this IDependencyResolver resolver)
{
if (resolver == null)
{
throw new ArgumentNullException("resolver");
}
return (T)resolver.GetService(typeof(T));
}
public static object Resolve(this IDependencyResolver resolver, Type type)
{
if (resolver == null)
{
throw new ArgumentNullException("resolver");
}
if (type == null)
{
throw new ArgumentNullException("type");
}
return resolver.GetService(type);
}
public static IEnumerable<T> ResolveAll<T>(this IDependencyResolver resolver)
{
if (resolver == null)
{
throw new ArgumentNullException("resolver");
}
return resolver.GetServices(typeof(T)).Cast<T>();
}
public static IEnumerable<object> ResolveAll(this IDependencyResolver resolver, Type type)
{
if (resolver == null)
{
throw new ArgumentNullException("resolver");
}
if (type == null)
{
throw new ArgumentNullException("type");
}
return resolver.GetServices(type);
}
}
}

@ -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 Microsoft.AspNet.SignalR.Configuration;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.AspNet.SignalR.Infrastructure;
namespace Microsoft.AspNet.SignalR
{
/// <summary>
/// Provides access to default host information.
/// </summary>
public static class GlobalHost
{
private static readonly Lazy<IDependencyResolver> _defaultResolver = new Lazy<IDependencyResolver>(() => new DefaultDependencyResolver());
private static IDependencyResolver _resolver;
/// <summary>
/// Gets or sets the the default <see cref="IDependencyResolver"/>
/// </summary>
public static IDependencyResolver DependencyResolver
{
get
{
return _resolver ?? _defaultResolver.Value;
}
set
{
_resolver = value;
}
}
/// <summary>
/// Gets the default <see cref="IConfigurationManager"/>
/// </summary>
public static IConfigurationManager Configuration
{
get
{
return DependencyResolver.Resolve<IConfigurationManager>();
}
}
/// <summary>
/// Gets the default <see cref="IConnectionManager"/>
/// </summary>
public static IConnectionManager ConnectionManager
{
get
{
return DependencyResolver.Resolve<IConnectionManager>();
}
}
/// <summary>
///
/// </summary>
public static IHubPipeline HubPipeline
{
get
{
return DependencyResolver.Resolve<IHubPipeline>();
}
}
}
}

@ -0,0 +1,117 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure;
using Microsoft.AspNet.SignalR.Messaging;
namespace Microsoft.AspNet.SignalR
{
/// <summary>
/// The default <see cref="IGroupManager"/> implementation.
/// </summary>
public class GroupManager : IConnectionGroupManager
{
private readonly IConnection _connection;
private readonly string _groupPrefix;
/// <summary>
/// Initializes a new instance of the <see cref="GroupManager"/> class.
/// </summary>
/// <param name="connection">The <see cref="IConnection"/> this group resides on.</param>
/// <param name="groupPrefix">The prefix for this group. Either a <see cref="Microsoft.AspNet.SignalR.Hubs.IHub"/> name or <see cref="PersistentConnection"/> type name.</param>
public GroupManager(IConnection connection, string groupPrefix)
{
if (connection == null)
{
throw new ArgumentNullException("connection");
}
_connection = connection;
_groupPrefix = groupPrefix;
}
/// <summary>
/// Sends a value to the specified group.
/// </summary>
/// <param name="groupName">The name of the group.</param>
/// <param name="value">The value to send.</param>
/// <param name="excludeConnectionIds">The list of connection ids to exclude</param>
/// <returns>A task that represents when send is complete.</returns>
public Task Send(string groupName, object value, params string[] excludeConnectionIds)
{
if (string.IsNullOrEmpty(groupName))
{
throw new ArgumentException((Resources.Error_ArgumentNullOrEmpty), "groupName");
}
var qualifiedName = CreateQualifiedName(groupName);
var message = new ConnectionMessage(qualifiedName,
value,
PrefixHelper.GetPrefixedConnectionIds(excludeConnectionIds));
return _connection.Send(message);
}
/// <summary>
/// Adds a connection to the specified group.
/// </summary>
/// <param name="connectionId">The connection id to add to the group.</param>
/// <param name="groupName">The name of the group</param>
/// <returns>A task that represents the connection id being added to the group.</returns>
public Task Add(string connectionId, string groupName)
{
if (connectionId == null)
{
throw new ArgumentNullException("connectionId");
}
if (groupName == null)
{
throw new ArgumentNullException("groupName");
}
var command = new Command
{
CommandType = CommandType.AddToGroup,
Value = CreateQualifiedName(groupName),
WaitForAck = true
};
return _connection.Send(connectionId, command);
}
/// <summary>
/// Removes a connection from the specified group.
/// </summary>
/// <param name="connectionId">The connection id to remove from the group.</param>
/// <param name="groupName">The name of the group</param>
/// <returns>A task that represents the connection id being removed from the group.</returns>
public Task Remove(string connectionId, string groupName)
{
if (connectionId == null)
{
throw new ArgumentNullException("connectionId");
}
if (groupName == null)
{
throw new ArgumentNullException("groupName");
}
var command = new Command
{
CommandType = CommandType.RemoveFromGroup,
Value = CreateQualifiedName(groupName),
WaitForAck = true
};
return _connection.Send(connectionId, command);
}
private string CreateQualifiedName(string groupName)
{
return _groupPrefix + "." + groupName;
}
}
}

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Hosting
{
public static class HostConstants
{
/// <summary>
/// The host should set this if they need to enable debug mode
/// </summary>
public static readonly string DebugMode = "debugMode";
/// <summary>
/// The host should set this is web sockets can be supported
/// </summary>
public static readonly string SupportsWebSockets = "supportsWebSockets";
/// <summary>
/// The host should set this if the web socket url is different
/// </summary>
public static readonly string WebSocketServerUrl = "webSocketServerUrl";
public static readonly string ShutdownToken = "shutdownToken";
public static readonly string InstanceName = "instanceName";
}
}

@ -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;
using System.Collections.Generic;
using System.Security.Principal;
namespace Microsoft.AspNet.SignalR.Hosting
{
public class HostContext
{
public IRequest Request { get; private set; }
public IResponse Response { get; private set; }
public IDictionary<string, object> Items { get; private set; }
public HostContext(IRequest request, IResponse response)
{
Request = request;
Response = response;
Items = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
}
}

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Threading;
namespace Microsoft.AspNet.SignalR.Hosting
{
public static class HostContextExtensions
{
public static T GetValue<T>(this HostContext context, string key)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
object value;
if (context.Items.TryGetValue(key, out value))
{
return (T)value;
}
return default(T);
}
public static bool IsDebuggingEnabled(this HostContext context)
{
return context.GetValue<bool>(HostConstants.DebugMode);
}
public static bool SupportsWebSockets(this HostContext context)
{
// The server needs to implement IWebSocketRequest for websockets to be supported.
// It also needs to set the flag in the items collection.
return context.GetValue<bool>(HostConstants.SupportsWebSockets) &&
context.Request is IWebSocketRequest;
}
public static string WebSocketServerUrl(this HostContext context)
{
return context.GetValue<string>(HostConstants.WebSocketServerUrl);
}
public static CancellationToken HostShutdownToken(this HostContext context)
{
return context.GetValue<CancellationToken>(HostConstants.ShutdownToken);
}
public static string InstanceName(this HostContext context)
{
return context.GetValue<string>(HostConstants.InstanceName);
}
}
}

@ -0,0 +1,52 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Threading;
using Microsoft.AspNet.SignalR.Infrastructure;
namespace Microsoft.AspNet.SignalR.Hosting
{
public static class HostDependencyResolverExtensions
{
public static void InitializeHost(this IDependencyResolver resolver, string instanceName, CancellationToken hostShutdownToken)
{
if (resolver == null)
{
throw new ArgumentNullException("resolver");
}
if (String.IsNullOrEmpty(instanceName))
{
throw new ArgumentNullException("instanceName");
}
// Initialize the performance counters
resolver.InitializePerformanceCounters(instanceName, hostShutdownToken);
// Dispose the dependency resolver on host shut down (cleanly)
resolver.InitializeResolverDispose(hostShutdownToken);
}
private static void InitializePerformanceCounters(this IDependencyResolver resolver, string instanceName, CancellationToken hostShutdownToken)
{
var counters = resolver.Resolve<IPerformanceCounterManager>();
if (counters != null)
{
counters.Initialize(instanceName, hostShutdownToken);
}
}
private static void InitializeResolverDispose(this IDependencyResolver resolver, CancellationToken hostShutdownToken)
{
// TODO: Guard against multiple calls to this
// When the host triggers the shutdown token, dispose the resolver
hostShutdownToken.Register(state =>
{
((IDependencyResolver)state).Dispose();
},
resolver,
useSynchronizationContext: false);
}
}
}

@ -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;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Hosting
{
/// <summary>
/// Represents a connection to the client.
/// </summary>
public interface IResponse
{
/// <summary>
/// Gets a cancellation token that represents the client's lifetime.
/// </summary>
CancellationToken CancellationToken { get; }
/// <summary>
/// Gets or sets the content type of the response.
/// </summary>
string ContentType { get; set; }
/// <summary>
/// Writes buffered data.
/// </summary>
/// <param name="data">The data to write to the buffer.</param>
void Write(ArraySegment<byte> data);
/// <summary>
/// Flushes the buffered response to the client.
/// </summary>
/// <returns>A task that represents when the data has been flushed.</returns>
Task Flush();
/// <summary>
/// Closes the connection to the client.
/// </summary>
/// <returns>A task that represents when the connection is closed.</returns>
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "End", Justification = "Ends the response thus the name is appropriate.")]
Task End();
}
}

@ -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.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Hosting
{
/// <summary>
/// Represents a web socket.
/// </summary>
public interface IWebSocket
{
/// <summary>
/// Invoked when data is sent over the websocket
/// </summary>
Action<string> OnMessage { get; set; }
/// <summary>
/// Invoked when the websocket gracefully closes
/// </summary>
Action<bool> OnClose { get; set; }
/// <summary>
/// Invoked when there is an error
/// </summary>
Action<Exception> OnError { get; set; }
/// <summary>
/// Sends data over the websocket.
/// </summary>
/// <param name="value">The value to send.</param>
/// <returns>A <see cref="Task"/> that represents the send is complete.</returns>
Task Send(string value);
/// <summary>
/// Sends a chunk of data over the websocket ("endOfMessage" flag set to false.)
/// </summary>
/// <param name="message"></param>
/// <returns>A <see cref="Task"/> that represents the send is complete.</returns>
Task SendChunk(ArraySegment<byte> message);
/// <summary>
/// Sends a zero byte data chunk with the "endOfMessage" flag set to true.
/// </summary>
/// <returns>A <see cref="Task"/> that represents the flush is complete.</returns>
Task Flush();
}
}

@ -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.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Hosting
{
public interface IWebSocketRequest : IRequest
{
/// <summary>
/// Accepts an websocket request using the specified user function.
/// </summary>
/// <param name="callback">The callback that fires when the websocket is ready.</param>
Task AcceptWebSocketRequest(Func<IWebSocket, Task> callback);
}
}

@ -0,0 +1,52 @@
// 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.Hosting
{
/// <summary>
/// Responsible for creating <see cref="PersistentConnection"/> instances.
/// </summary>
public class PersistentConnectionFactory
{
private readonly IDependencyResolver _resolver;
/// <summary>
/// Creates a new instance of the <see cref="PersistentConnectionFactory"/> class.
/// </summary>
/// <param name="resolver">The dependency resolver to use for when creating the <see cref="PersistentConnection"/>.</param>
public PersistentConnectionFactory(IDependencyResolver resolver)
{
if (resolver == null)
{
throw new ArgumentNullException("resolver");
}
_resolver = resolver;
}
/// <summary>
/// Creates an instance of the specified type using the dependency resolver or the type's default constructor.
/// </summary>
/// <param name="connectionType">The type of <see cref="PersistentConnection"/> to create.</param>
/// <returns>An instance of a <see cref="PersistentConnection"/>. </returns>
public PersistentConnection CreateInstance(Type connectionType)
{
if (connectionType == null)
{
throw new ArgumentNullException("connectionType");
}
var connection = (_resolver.Resolve(connectionType) ??
Activator.CreateInstance(connectionType)) as PersistentConnection;
if (connection == null)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_IsNotA, connectionType.FullName, typeof(PersistentConnection).FullName));
}
return connection;
}
}
}

@ -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.Hosting
{
internal static class RequestExtensions
{
/// <summary>
/// Gets a value from the QueryString, and if it's null or empty, gets it from the Form instead.
/// </summary>
public static string QueryStringOrForm(this IRequest request, string key)
{
var value = request.QueryString[key];
if (String.IsNullOrEmpty(value))
{
value = request.Form[key];
}
return value;
}
}
}

@ -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;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Hosting
{
/// <summary>
/// Extension methods for <see cref="IResponse"/>.
/// </summary>
public static class ResponseExtensions
{
/// <summary>
/// Closes the connection to a client with optional data.
/// </summary>
/// <param name="response">The <see cref="IResponse"/>.</param>
/// <param name="data">The data to write to the connection.</param>
/// <returns>A task that represents when the connection is closed.</returns>
public static Task End(this IResponse response, string data)
{
if (response == null)
{
throw new ArgumentNullException("response");
}
var bytes = Encoding.UTF8.GetBytes(data);
response.Write(new ArraySegment<byte>(bytes, 0, bytes.Length));
return response.End();
}
}
}

@ -0,0 +1,75 @@
// 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.Threading.Tasks;
using Microsoft.AspNet.SignalR.Hubs;
namespace Microsoft.AspNet.SignalR
{
/// <summary>
/// Provides methods that communicate with SignalR connections that connected to a <see cref="Hub"/>.
/// </summary>
public abstract class Hub : IHub
{
protected Hub()
{
Clients = new HubConnectionContext();
Clients.All = new NullClientProxy();
Clients.Others = new NullClientProxy();
Clients.Caller = new NullClientProxy();
}
/// <summary>
///
/// </summary>
public HubConnectionContext Clients { get; set; }
/// <summary>
/// Provides information about the calling client.
/// </summary>
public HubCallerContext Context { get; set; }
/// <summary>
/// The group manager for this hub instance.
/// </summary>
public IGroupManager Groups { get; set; }
/// <summary>
/// Called when a connection disconnects from this hub instance.
/// </summary>
/// <returns>A <see cref="Task"/></returns>
public virtual Task OnDisconnected()
{
return TaskAsyncHelper.Empty;
}
/// <summary>
/// Called when the connection connects to this hub instance.
/// </summary>
/// <returns>A <see cref="Task"/></returns>
public virtual Task OnConnected()
{
return TaskAsyncHelper.Empty;
}
/// <summary>
/// Called when the connection reconnects to this hub instance.
/// </summary>
/// <returns>A <see cref="Task"/></returns>
public virtual Task OnReconnected()
{
return TaskAsyncHelper.Empty;
}
protected virtual void Dispose(bool disposing)
{
}
public void Dispose()
{
Dispose(true);
}
}
}

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR
{
public class HubConfiguration : ConnectionConfiguration
{
/// <summary>
/// Determines whether JavaScript proxies for the server-side hubs should be auto generated at {Path}/hubs.
/// Defaults to true.
/// </summary>
public bool EnableJavaScriptProxies { get; set; }
/// <summary>
/// Determines whether detailed exceptions thrown in Hub methods get reported back the invoking client.
/// Defaults to false.
/// </summary>
public bool EnableDetailedErrors { get; set; }
public HubConfiguration()
{
EnableJavaScriptProxies = true;
}
}
}

@ -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()
};
}
}
}

@ -0,0 +1,24 @@
// 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
{
/// <summary>
/// A communication channel for a <see cref="PersistentConnection"/> and its connections.
/// </summary>
public interface IConnection
{
/// <summary>
/// The main signal for this connection. This is the main signalr for a <see cref="PersistentConnection"/>.
/// </summary>
string DefaultSignal { get; }
/// <summary>
/// Sends a message to connections subscribed to the signal.
/// </summary>
/// <param name="message">The message to send.</param>
/// <returns>A task that returns when the message has be sent.</returns>
Task Send(ConnectionMessage message);
}
}

@ -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.Threading.Tasks;
namespace Microsoft.AspNet.SignalR
{
/// <summary>
/// Manages groups for a connection and allows sending messages to the group.
/// </summary>
public interface IConnectionGroupManager : IGroupManager
{
/// <summary>
/// Sends a value to the specified group.
/// </summary>
/// <param name="groupName">The name of the group.</param>
/// <param name="value">The value to send.</param>
/// <param name="excludeConnectionIds">The list of connection ids to exclude</param>
/// <returns>A task that represents when send is complete.</returns>
Task Send(string groupName, object value, params string[] excludeConnectionIds);
}
}

@ -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.Collections.Generic;
namespace Microsoft.AspNet.SignalR
{
public interface IDependencyResolver : IDisposable
{
object GetService(Type serviceType);
IEnumerable<object> GetServices(Type serviceType);
void Register(Type serviceType, Func<object> activator);
void Register(Type serviceType, IEnumerable<Func<object>> activators);
}
}

@ -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.Threading.Tasks;
namespace Microsoft.AspNet.SignalR
{
/// <summary>
/// Manages groups for a connection.
/// </summary>
public interface IGroupManager
{
/// <summary>
/// Adds a connection to the specified group.
/// </summary>
/// <param name="connectionId">The connection id to add to the group.</param>
/// <param name="groupName">The name of the group</param>
/// <returns>A task that represents the connection id being added to the group.</returns>
Task Add(string connectionId, string groupName);
/// <summary>
/// Removes a connection from the specified group.
/// </summary>
/// <param name="connectionId">The connection id to remove from the group.</param>
/// <param name="groupName">The name of the group</param>
/// <returns>A task that represents the connection id being removed from the group.</returns>
Task Remove(string connectionId, string groupName);
}
}

@ -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;
using Microsoft.AspNet.SignalR.Hubs;
namespace Microsoft.AspNet.SignalR
{
/// <summary>
/// Provides access to information about a <see cref="IHub"/>.
/// </summary>
public interface IHubContext
{
/// <summary>
/// Encapsulates all information about a SignalR connection for an <see cref="IHub"/>.
/// </summary>
IHubConnectionContext Clients { get; }
/// <summary>
/// Gets the <see cref="IGroupManager"/> the hub.
/// </summary>
IGroupManager Groups { get; }
}
}

@ -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
{
/// <summary>
/// Provides access to information about a <see cref="PersistentConnection" />.
/// </summary>
public interface IPersistentConnectionContext
{
/// <summary>
/// Gets the <see cref="IConnection" /> for the <see cref="PersistentConnection" />.
/// </summary>
IConnection Connection { get; }
/// <summary>
/// Gets the <see cref="IConnectionGroupManager" /> for the <see cref="PersistentConnection" />.
/// </summary>
IConnectionGroupManager Groups { get; }
}
}

@ -0,0 +1,51 @@
// 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.Collections.Specialized;
using System.Security.Principal;
using System.Threading.Tasks;
namespace Microsoft.AspNet.SignalR
{
/// <summary>
/// Represents a SignalR request
/// </summary>
public interface IRequest
{
/// <summary>
/// Gets the url for this request.
/// </summary>
Uri Url { get; }
/// <summary>
/// Gets the querystring for this request.
/// </summary>
NameValueCollection QueryString { get; }
/// <summary>
/// Gets the headers for this request.
/// </summary>
NameValueCollection Headers { get; }
/// <summary>
/// Gets the form for this request.
/// </summary>
NameValueCollection Form { get; }
/// <summary>
/// Gets the cookies for this request.
/// </summary>
IDictionary<string, Cookie> Cookies { get; }
/// <summary>
/// Gets security information for the current HTTP request.
/// </summary>
IPrincipal User { get; }
/// <summary>
/// Gets state for the current HTTP request.
/// </summary>
IDictionary<string, object> Items { get; }
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save