Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
cburgdorf committed Mar 6, 2012
0 parents commit 5aac980
Show file tree
Hide file tree
Showing 24 changed files with 26,073 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .gitignore
@@ -0,0 +1,22 @@
[Oo]bj/
[Bb]in/
*.user
/TestResults
*.vspscc
*.vssscc
deploy
deploy/*
*.suo
*.cache
*.docstates
_ReSharper.*
*.csproj.user
*[Rr]e[Ss]harper.user
_ReSharper.*/
packages/*
artifacts/*
msbuild.log
PublishProfiles/
*.psess
*.vsp
*.csv
Binary file not shown.
Binary file not shown.
Binary file added SignalR.Reactive/Libraries/SignalR.dll
Binary file not shown.
Binary file added SignalR.Reactive/Libraries/SignalR.pdb
Binary file not shown.
20 changes: 20 additions & 0 deletions SignalR.Reactive/SignalR.Reactive.sln
@@ -0,0 +1,20 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignalR.Reactive", "SignalR.Reactive\SignalR.Reactive.csproj", "{2F4D1483-3A2E-4983-8EA4-75243C6759E4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2F4D1483-3A2E-4983-8EA4-75243C6759E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F4D1483-3A2E-4983-8EA4-75243C6759E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F4D1483-3A2E-4983-8EA4-75243C6759E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F4D1483-3A2E-4983-8EA4-75243C6759E4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
62 changes: 62 additions & 0 deletions SignalR.Reactive/SignalR.Reactive/Clientside.cs
@@ -0,0 +1,62 @@
using System;
using System.Linq.Expressions;
using SignalR.Hosting.AspNet;
using SignalR.Hubs;
using SignalR.Infrastructure;

namespace SignalR.Reactive
{
public class Clientside<T>
{
private readonly IObservable<T> _observable;

internal Clientside(IObservable<T> observable)
{
_observable = observable;
}

public IDisposable Observable<THub>(Expression<Func<THub, dynamic>> expression) where THub : Hub, new()
{
return Observable(expression, null);
}

public IDisposable Observable<THub>(Expression<Func<THub, dynamic>> expression, string clientName) where THub : Hub, new()
{
var memberExpression = expression.Body as MemberExpression;
if (memberExpression == null)
{
throw new ArgumentException("'expression' should be a member expression");
}

return Observable<THub>(memberExpression.Member.Name, clientName);
}

public IDisposable Observable<THub>(string eventName) where THub : Hub, new()
{
return Observable<THub>(eventName, null);
}

public IDisposable Observable<THub>(string eventName, string clientName) where THub : Hub, new()
{
var connectionManager = AspNetHost.DependencyResolver.Resolve<IConnectionManager>();

dynamic clients = connectionManager.GetClients<THub>();
clients = string.IsNullOrEmpty(clientName) ? clients : clients[clientName];

return _observable.Subscribe(
x => clients.Invoke("subjectOnNext", new { Data = x, EventName = eventName, Type = "onNext" }),
x => clients.Invoke("subjectOnNext", new { Data = x, EventName = eventName, Type = "onError" }),
() => clients.Invoke("subjectOnNext", new { EventName = eventName, Type = "onCompleted" })
);
}

}

public static class SignalRObservableExtensions
{
public static Clientside<T> ToClientside<T>(this IObservable<T> observable)
{
return new Clientside<T>(observable);
}
}
}
20 changes: 20 additions & 0 deletions SignalR.Reactive/SignalR.Reactive/DependencyResolverExtensions.cs
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SignalR.Hubs;
using SignalR.Infrastructure;

namespace SignalR.Reactive
{
public static class DependencyResolverExtensions
{
public static IDependencyResolver EnableRxSupport(this IDependencyResolver dependencyResolver)
{
var proxyGenerator = new Lazy<RxJsProxyGenerator>(() => new RxJsProxyGenerator(dependencyResolver));
dependencyResolver.Register(typeof(IJavaScriptProxyGenerator), () => proxyGenerator.Value);

return dependencyResolver;
}
}
}
36 changes: 36 additions & 0 deletions SignalR.Reactive/SignalR.Reactive/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// Allgemeine Informationen über eine Assembly werden über die folgenden
// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
// die mit einer Assembly verknüpft sind.
[assembly: AssemblyTitle("SignalR.Reactive")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SignalR.Reactive")]
[assembly: AssemblyCopyright("Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar
// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von
// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest.
[assembly: ComVisible(false)]

// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
[assembly: Guid("126d9d94-4de9-4550-bea6-f63ecb245569")]

// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
//
// Hauptversion
// Nebenversion
// Buildnummer
// Revision
//
// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern
// übernehmen, indem Sie "*" eingeben:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
211 changes: 211 additions & 0 deletions SignalR.Reactive/SignalR.Reactive/RxJsProxyGenerator.cs
@@ -0,0 +1,211 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using SignalR.Hubs;
using SignalR.Infrastructure;

namespace SignalR.Reactive
{
class RxJsProxyGenerator
{
private static readonly Lazy<string> _template = new Lazy<string>(GetTemplate);
private static readonly ConcurrentDictionary<string, string> _scriptCache = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);

private const string ScriptResource = "SignalR.Scripts.hubs.js";

private readonly IHubLocator _hubLocator;
private readonly IJavaScriptMinifier _javascriptMinifier;

public RxJsProxyGenerator(IDependencyResolver resolver) :
this(resolver.Resolve<IHubLocator>(),
resolver.Resolve<IJavaScriptMinifier>())
{
}

public RxJsProxyGenerator(IHubLocator hubLocator, IJavaScriptMinifier javascriptMinifier)
{
_hubLocator = hubLocator;
_javascriptMinifier = javascriptMinifier ?? NullJavaScriptMinifier.Instance;
}

public bool IsDebuggingEnabled { get; set; }

public string GenerateProxy(string serviceUrl)
{
string script;
if (_scriptCache.TryGetValue(serviceUrl, out script))
{
return script;
}

var template = _template.Value;

script = template.Replace("{serviceUrl}", serviceUrl);

var hubs = new StringBuilder();
var first = true;
foreach (var type in _hubLocator.GetHubs())
{
if (!first)
{
hubs.AppendLine(",");
hubs.Append(" ");
}
GenerateType(serviceUrl, hubs, type);
first = false;
}

script = script.Replace("/*hubs*/", hubs.ToString());

if (!IsDebuggingEnabled)
{
script = _javascriptMinifier.Minify(script);
}

_scriptCache.TryAdd(serviceUrl, script);

return script;
}

private void GenerateType(string serviceUrl, StringBuilder sb, Type type)
{
// Get public instance methods declared on this type only
var methods = GetMethods(type);
var members = methods.Select(m => m.Name).ToList();

// Get observable properties declared on this type
var observableProperties = ReflectionHelper.GetExportedHubObservables(type);

members.Add("namespace");
members.Add("ignoreMembers");
members.Add("callbacks");

sb.AppendFormat("{0}: {{", GetHubName(type)).AppendLine();
sb.AppendFormat(" _: {{").AppendLine();
sb.AppendFormat(" hubName: '{0}',", type.FullName ?? "null").AppendLine();
sb.AppendFormat(" ignoreMembers: [{0}],", Commas(members, m => "'" + Json.CamelCase(m) + "'")).AppendLine();
sb.AppendLine(" connection: function () { return signalR.hub; }");
sb.AppendFormat(" }}");
if (methods.Any() || observableProperties.Any())
{
sb.Append(",").AppendLine();
}
else
{
sb.AppendLine();
}

bool firstMethod = true;

foreach (var method in methods)
{
if (!firstMethod)
{
sb.Append(",").AppendLine();
}
GenerateMethod(serviceUrl, sb, type, method);
firstMethod = false;
}

if (observableProperties.Any() && methods.Any())
{
sb.Append(",").AppendLine();
}

bool firstProperty = true;

foreach (var observableProperty in observableProperties)
{
if (!firstProperty)
{
sb.Append(",").AppendLine();
}
GenerateRxSubject(sb, type, observableProperty);
firstProperty = false;
}

sb.AppendLine();
sb.Append(" }");
}

protected virtual string GetHubName(Type type)
{
return ReflectionHelper.GetAttributeValue<HubNameAttribute, string>(type, a => a.HubName) ?? Json.CamelCase(type.Name);
}

protected virtual IEnumerable<MethodInfo> GetMethods(Type type)
{
// Pick the overload with the minimum number of arguments
return from method in ReflectionHelper.GetExportedHubMethods(type)
group method by method.Name into overloads
let oload = (from overload in overloads
orderby overload.GetParameters().Length
select overload).FirstOrDefault()
select oload;
}

private void GenerateMethod(string serviceUrl, StringBuilder sb, Type type, MethodInfo method)
{
var parameters = method.GetParameters();
var parameterNames = parameters.Select(p => p.Name).ToList();
parameterNames.Add("callback");
sb.AppendLine();
sb.AppendFormat(" {0}: function ({1}) {{", GetMethodName(method), Commas(parameterNames)).AppendLine();
sb.AppendFormat(" return serverCall(this, \"{0}\", $.makeArray(arguments));", method.Name).AppendLine();
sb.Append(" }");
}

private void GenerateRxSubject(StringBuilder sb, Type type, PropertyInfo property)
{
Json.CamelCase(property.Name);
var hubName = Json.CamelCase(type.Name);
sb.AppendFormat(" subject : new Rx.Subject(),");
sb.AppendLine();
sb.AppendFormat(" subjectOnNext: function(value) {{ signalR.{0}.subject.onNext(value); }},", hubName);
sb.AppendLine();
sb.AppendFormat(" getObservable: function (eventName) {{ ").AppendLine();
sb.AppendFormat(" return Rx.Observable.create(function (obs) {{ ").AppendLine();
sb.AppendFormat(" var disposable = signalR.{0}.subject ", hubName).AppendLine();
sb.AppendFormat(" .asObservable() ").AppendLine();
sb.AppendFormat(" .where(function (x) {{ return x.EventName.toLowerCase() === eventName.toLowerCase(); }}) ").AppendLine();
sb.AppendFormat(" .subscribe(function (x) {{ ").AppendLine();
sb.AppendFormat(" if (x.Type === 'onNext') obs.onNext(x.Data); ").AppendLine();
sb.AppendFormat(" if (x.Type === 'onError') obs.onError(x.Data); ").AppendLine();
sb.AppendFormat(" if (x.Type === 'onCompleted') obs.onCompleted(); ").AppendLine();
sb.AppendFormat(" }}); ").AppendLine();
sb.AppendFormat(" return disposable.dispose; ").AppendLine();
sb.AppendFormat(" }}); ").AppendLine();
sb.AppendFormat(" }} ").AppendLine();
}

private static string GetMethodName(MethodInfo method)
{
return ReflectionHelper.GetAttributeValue<HubMethodNameAttribute, string>(method, a => a.MethodName) ?? Json.CamelCase(method.Name);
}

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 GetTemplate()
{
using (Stream resourceStream = typeof(DefaultJavaScriptProxyGenerator).Assembly.GetManifestResourceStream(ScriptResource))
{
using (var reader = new StreamReader(resourceStream))
{
return reader.ReadToEnd();
}
}
}
}
}

0 comments on commit 5aac980

Please sign in to comment.