Skip to content

Instantly share code, notes, and snippets.

@pierre3
Last active August 23, 2021 18:35
Show Gist options
  • Save pierre3/8c461799a55820a98ff8 to your computer and use it in GitHub Desktop.
Save pierre3/8c461799a55820a98ff8 to your computer and use it in GitHub Desktop.
Generate SignalR HubProxies for TypeScript
using Microsoft.AspNet.SignalR;
namespace hubProxyGen
{
public class ChatHub : Hub<ChatHubClient>
{
public void Send(string name, string message)
{
Clients.All.AddNewMessageToPage(name, message);
}
}
public interface ChatHubClient
{
void AddNewMessageToPage(string name, string message);
}
}
/// <reference path="../typings/signalr/signalr.d.ts" />
/// <reference path="../typings/jquery/jquery.d.ts" />
interface SignalR {
chatHub : ChatHub;
}
interface ChatHub{
server : ChatHubServer;
client : ChatHubClient;
}
interface ChatHubServer {
send( name:string, message:string ) : JQueryPromise<void>;
}
interface ChatHubClient{
addNewMessageToPage : ( name:string, message:string ) => void;
}
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Runtime" #>
<#@ assembly name="System.Text.Encoding" #>
<#@ assembly name="System.Threading.Tasks" #>
<#@ assembly name="$(SolutionDir)packages\Microsoft.CodeAnalysis.Common.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.dll" #>
<#@ assembly name="$(SolutionDir)packages\Microsoft.CodeAnalysis.CSharp.1.0.0-rc1\lib\net45\Microsoft.CodeAnalysis.CSharp.dll" #>
<#@ assembly name="$(SolutionDir)packages\System.Collections.Immutable.1.1.33-beta\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="Microsoft.CodeAnalysis" #>
<#@ import namespace="Microsoft.CodeAnalysis.CSharp" #>
<#@ import namespace="Microsoft.CodeAnalysis.CSharp.Syntax" #>
<#@ output extension=".d.ts" #>
/// <reference path="../typings/signalr/signalr.d.ts" />
/// <reference path="../typings/jquery/jquery.d.ts" />
<#
var fileName = Host.ResolvePath(@"ChatHub.cs");
var source = File.ReadAllText(fileName);
var syntax = CSharpSyntaxTree.ParseText(source);
var node = syntax.GetRoot();
var generator = new HubProxyGenerator();
generator.Visit(node);
generator.Close();
#>
<#= generator.SignalRDeclaration.ToString() #>
<#= generator.HubDeclaration.ToString() #>
<#= generator.ServerDeclaration.ToString() #>
<#= generator.ClientDeclaration.ToString() #>
<#+
class HubProxyGenerator : CSharpSyntaxWalker
{
public StringBuilder SignalRDeclaration { get; private set; }
public StringBuilder ServerDeclaration { get; private set; }
public StringBuilder HubDeclaration { get; private set; }
public StringBuilder ClientDeclaration { get; private set; }
public HubProxyGenerator()
{
SignalRDeclaration = new StringBuilder();
SignalRDeclaration.AppendLine("interface SignalR {");
ServerDeclaration = new StringBuilder();
HubDeclaration = new StringBuilder();
ClientDeclaration = new StringBuilder();
HubDeclaration = new StringBuilder();
}
public override void VisitClassDeclaration(ClassDeclarationSyntax node)
{
base.VisitClassDeclaration(node);
var isGenericHub = node.BaseList.Types
.Select(t=>t.Type)
.OfType<GenericNameSyntax>()
.Any(s=>s.Identifier.ValueText == "Hub");
if (!isGenericHub) { return; }
//interface SignalR {
// chatHub : ChatHub;
SignalRDeclaration.AppendFormat(" {0} : {1};",
Helper.ToCamelCase(node.Identifier.ValueText), node.Identifier.ValueText);
SignalRDeclaration.AppendLine();
//interface ChatHub {
// server : ChatHubServer;
HubDeclaration.AppendLine("interface " + node.Identifier.ValueText + "{");
HubDeclaration.AppendFormat(" server : {0}Server;", node.Identifier.ValueText);
HubDeclaration.AppendLine();
//interface ChatHubServer {
ServerDeclaration.AppendFormat("interface {0}Server {{", node.Identifier.ValueText);
ServerDeclaration.AppendLine();
{
var mWalker = new MethodWalker(true);
mWalker.Visit(node);
ServerDeclaration.Append(mWalker.Output.ToString());
}
ServerDeclaration.AppendLine("}");
}
public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
{
base.VisitInterfaceDeclaration(node);
//ingerface ChatHub {
// client : ChatHubClient;
HubDeclaration.AppendFormat(" client : {0};",node.Identifier.ValueText);
HubDeclaration.AppendLine();
//interface ChatHubClient {
ClientDeclaration.AppendLine("interface " + node.Identifier.ValueText + "{");
{
var mWalker = new MethodWalker(false);
mWalker.Visit(node);
ClientDeclaration.Append(mWalker.Output.ToString());
}
ClientDeclaration.AppendLine("}");
}
public void Close()
{
SignalRDeclaration.AppendLine("}");
HubDeclaration.AppendLine("}");
}
}
class MethodWalker : CSharpSyntaxWalker
{
private bool isServerSide;
public StringBuilder Output { get; private set; }
public MethodWalker(bool isServerSide)
{
this.isServerSide = isServerSide;
Output = new StringBuilder();
}
public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
{
base.VisitMethodDeclaration(node);
var name = Helper.ToCamelCase(node.Identifier.ValueText);
var parameters = string.Join(", ", node.ParameterList.Parameters
.Select(p => p.Identifier.ValueText + ":" + Helper.GetJSType(p.Type.ToString())));
if (isServerSide)
{
var returns = "JQueryPromise<" + Helper.GetJSType(node.ReturnType.ToString()) + ">";
// send(name : string, message : string) : JQueryPromise<void>;
Output.AppendFormat(" {0}( {1} ) : {2};", name, parameters, returns);
}
else
{
var returns = Helper.GetJSType(node.ReturnType.ToString());
// addNewMessageToPage(name : string, message : string) : JQueryPromise<void>;
Output.AppendFormat(" {0} : ( {1} ) => {2};", name, parameters, returns);
}
Output.AppendLine();
}
}
static class Helper
{
public static string GetJSType(string typeName)
{
typeName = typeName.ToLower();
var numTypes = new[]{
"int", "uint", "short", "ushort", "long",
"ulong", "sbyte","byte", "int32", "uint32",
"int16", "uint16", "int64", "uint64",
"double", "float", "single", "decimal"};
if (typeName == "string")
{
return "string";
}
if (typeName == "boolean" || typeName == "bool")
{
return "boolean";
}
if (typeName == "void")
{
return "void";
}
if (numTypes.Any(t => typeName == t))
{
return "number";
}
return "any";
}
public static string ToCamelCase(string keyWord)
{
return keyWord.Substring(0, 1).ToLower() + keyWord.Substring(1);
}
}
#>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment