Last active
March 18, 2020 16:17
-
-
Save robfe/4583549 to your computer and use it in GitHub Desktop.
T4 template that creates Typescript type definitions for all your Signalr hubs. If you have C# interface named "I<hubName>Client", a TS interface will be generated for the hub's client too.If you turn on XML documentation in your build, XMLDoc comments will be picked up.Licensed with http://www.apache.org/licenses/LICENSE-2.0
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<#@ template debug="true" hostspecific="true" language="C#" #> | |
<#@ output extension=".d.ts" #> | |
<# /* Update this line to match your version of SignalR */ #> | |
<#@ assembly name="$(SolutionDir)\packages\Microsoft.AspNet.SignalR.Core.2.2.0\lib\net45\Microsoft.AspNet.SignalR.Core.dll" #> | |
<# /* Load the current project's DLL to make sure the DefaultHubManager can find things */ #> | |
<#@ assembly name="$(TargetPath)" #> | |
<#@ assembly name="System.Core" #> | |
<#@ assembly name="System.Web" #> | |
<#@ assembly name="System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" #> | |
<#@ assembly name="System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" #> | |
<#@ import namespace="System.IO" #> | |
<#@ import namespace="System.Text.RegularExpressions" #> | |
<#@ import namespace="System.Threading.Tasks" #> | |
<#@ import namespace="Microsoft.AspNet.SignalR" #> | |
<#@ import namespace="Microsoft.AspNet.SignalR.Hubs" #> | |
<#@ import namespace="System.Linq" #> | |
<#@ import namespace="System.Reflection" #> | |
<#@ import namespace="System.Collections.Generic" #> | |
<#@ import namespace="System.Xml.Linq" #> | |
<# | |
var hubmanager = new DefaultHubManager(new DefaultDependencyResolver()); | |
#> | |
// Get signalr.d.ts.ts from https://github.com/borisyankov/DefinitelyTyped (or delete the reference) | |
/// <reference path="signalr.d.ts" /> | |
/// <reference path="jquery.d.ts" /> | |
//////////////////// | |
// available hubs // | |
//////////////////// | |
//#region available hubs | |
interface SignalR { | |
<# | |
foreach (var hub in hubmanager.GetHubs()) | |
{ | |
#> | |
/** | |
* The hub implemented by <#=hub.HubType.FullName#> | |
*/ | |
<#= hub.NameSpecified?hub.Name:FirstCharLowered(hub.Name) #> : <#= hub.HubType.Name #>; | |
<# | |
} | |
#> | |
} | |
//#endregion available hubs | |
/////////////////////// | |
// Service Contracts // | |
/////////////////////// | |
//#region service contracts | |
<# | |
foreach (var hub in hubmanager.GetHubs()) | |
{ | |
var hubType = hub.HubType; | |
Type clientType = ClientType(hubType); | |
#> | |
//#region <#= hub.Name#> hub | |
interface <#= hubType.Name #> { | |
/** | |
* This property lets you send messages to the <#= hub.Name#> hub. | |
*/ | |
server : <#= hubType.Name #>Server; | |
/** | |
* The functions on this property should be replaced if you want to receive messages from the <#= hub.Name#> hub. | |
*/ | |
client : <#= clientType != null?clientType.Name:"any"#>; | |
} | |
<# | |
/* Server type definition */ | |
#> | |
interface <#= hubType.Name #>Server { | |
<# | |
foreach (var method in hubmanager.GetHubMethods(hub.Name )) | |
{ | |
var ps = method.Parameters.Select(x => x.Name+ " : "+GetTypeContractName(x.ParameterType)); | |
var docs = GetXmlDocForMethod(hubType.GetMethod(method.Name)); | |
#> | |
/** | |
* Sends a "<#= FirstCharLowered(method.Name) #>" message to the <#= hub.Name#> hub. | |
* Contract Documentation: <#= docs.Summary #> | |
<# | |
foreach (var p in method.Parameters) | |
{ | |
#> | |
* @param <#=p.Name#> {<#=GetTypeContractName(p.ParameterType)#>} <#=docs.ParameterSummary(p.Name)#> | |
<# | |
} | |
#> | |
* @return {JQueryPromise of <#= GetTypeContractName(method.ReturnType)#>} | |
*/ | |
<#= FirstCharLowered(method.Name) #>(<#=string.Join(", ", ps)#>) : JQueryPromise<<#= GetTypeContractName(method.ReturnType)#>> | |
<# | |
} | |
#> | |
} | |
<# | |
/* Client type definition */ | |
#> | |
<# | |
if (clientType != null) | |
{ | |
#> | |
interface <#= clientType.Name #> | |
{ | |
<# | |
foreach (var method in clientType.GetMethods()) | |
{ | |
var ps = method.GetParameters().Select(x => x.Name+ " : "+GetTypeContractName(x.ParameterType)); | |
var docs = GetXmlDocForMethod(method); | |
#> | |
/** | |
* Set this function with a "function(<#=string.Join(", ", ps)#>){}" to receive the "<#= FirstCharLowered(method.Name) #>" message from the <#= hub.Name#> hub. | |
* Contract Documentation: <#= docs.Summary #> | |
<# | |
foreach (var p in method.GetParameters()) | |
{ | |
#> | |
* @param <#=p.Name#> {<#=GetTypeContractName(p.ParameterType)#>} <#=docs.ParameterSummary(p.Name)#> | |
<# | |
} | |
#> | |
* @return {void} | |
*/ | |
<#= FirstCharLowered(method.Name) #> : (<#=string.Join(", ", ps)#>) => void; | |
<# | |
} | |
#> | |
} | |
<# | |
} | |
#> | |
//#endregion <#= hub.Name#> hub | |
<# | |
} | |
#> | |
//#endregion service contracts | |
//////////////////// | |
// Data Contracts // | |
//////////////////// | |
//#region data contracts | |
<# | |
while(viewTypes.Count!=0) | |
{ | |
var type = viewTypes.Pop(); | |
#> | |
/** | |
* Data contract for <#= type.FullName#> | |
*/ | |
interface <#= GenericSpecificName(type) #> { | |
<# | |
foreach (var property in type.GetProperties(BindingFlags.Instance|BindingFlags.Public|BindingFlags.DeclaredOnly)) | |
{ | |
#> | |
<#= property.Name#> : <#= GetTypeContractName(property.PropertyType)#>; | |
<# | |
} | |
#> | |
} | |
<# | |
} | |
#> | |
//#endregion data contracts | |
<#+ | |
private Stack<Type> viewTypes = new Stack<Type>(); | |
private HashSet<Type> doneTypes = new HashSet<Type>(); | |
private string GetTypeContractName(Type type) | |
{ | |
if (type == typeof (Task)) | |
{ | |
return "void /*task*/"; | |
} | |
if (type.IsArray) | |
{ | |
return GetTypeContractName(type.GetElementType())+"[]"; | |
} | |
if (type.IsGenericType && typeof(Task<>).IsAssignableFrom(type.GetGenericTypeDefinition())) | |
{ | |
return GetTypeContractName(type.GetGenericArguments()[0]); | |
} | |
if (type.IsGenericType && typeof(Nullable<>).IsAssignableFrom(type.GetGenericTypeDefinition())) | |
{ | |
return GetTypeContractName(type.GetGenericArguments()[0]); | |
} | |
if (type.IsGenericType && typeof(List<>).IsAssignableFrom(type.GetGenericTypeDefinition())) | |
{ | |
return GetTypeContractName(type.GetGenericArguments()[0])+"[]"; | |
} | |
switch (type.Name.ToLowerInvariant()) | |
{ | |
case "datetime": | |
return "string"; | |
case "int16": | |
case "int32": | |
case "int64": | |
case "single": | |
case "double": | |
return "number"; | |
case "boolean": | |
return "bool"; | |
case "void": | |
case "string": | |
return type.Name.ToLowerInvariant(); | |
} | |
if (!doneTypes.Contains(type)) | |
{ | |
doneTypes.Add(type); | |
viewTypes.Push(type); | |
} | |
return GenericSpecificName(type); | |
} | |
private string GenericSpecificName(Type type) | |
{ | |
string name = type.Name.Split('`').First(); | |
if (type.IsGenericType) | |
{ | |
name += "<"+string.Join(", ", type.GenericTypeArguments.Select(GenericSpecificName))+">"; | |
} | |
return name; | |
} | |
private string FirstCharLowered(string s) | |
{ | |
return Regex.Replace(s, "^.", x => x.Value.ToLowerInvariant()); | |
} | |
Dictionary<Assembly, XDocument> xmlDocs = new Dictionary<Assembly, XDocument>(); | |
private XDocument XmlDocForAssembly(Assembly a) | |
{ | |
XDocument value; | |
if (!xmlDocs.TryGetValue(a, out value)) | |
{ | |
var path = new Uri(a.CodeBase.Replace(".dll", ".xml")).LocalPath; | |
xmlDocs[a] = value = File.Exists(path) ? XDocument.Load(path) : null; | |
} | |
return value; | |
} | |
private MethodDocs GetXmlDocForMethod(MethodInfo method) | |
{ | |
var xmlDocForHub = XmlDocForAssembly(method.DeclaringType.Assembly); | |
if (xmlDocForHub == null) | |
{ | |
return new MethodDocs(); | |
} | |
var methodName = string.Format("M:{0}.{1}({2})", method.DeclaringType.FullName, method.Name, string.Join(",", method.GetParameters().Select(x => x.ParameterType.FullName))); | |
var xElement = xmlDocForHub.Descendants("member").SingleOrDefault(x => (string) x.Attribute("name") == methodName); | |
return xElement==null?new MethodDocs():new MethodDocs(xElement); | |
} | |
private Type ClientType(Type hubType) | |
{ | |
while (hubType != null && hubType != typeof(Hub)) | |
{ | |
if (hubType.IsGenericType && hubType.GetGenericTypeDefinition() == typeof (Hub<>)) | |
{ | |
return hubType.GetGenericArguments().Single(); | |
} | |
hubType = hubType.BaseType; | |
} | |
return null; | |
} | |
private class MethodDocs | |
{ | |
public MethodDocs() | |
{ | |
Summary = "---"; | |
Parameters = new Dictionary<string, string>(); | |
} | |
public MethodDocs(XElement xElement) | |
{ | |
Summary = ((string) xElement.Element("summary") ?? "").Trim(); | |
Parameters = xElement.Elements("param").ToDictionary(x => (string) x.Attribute("name"), x=>x.Value); | |
} | |
public string Summary { get; set; } | |
public Dictionary<string, string> Parameters { get; set; } | |
public string ParameterSummary(string name) | |
{ | |
if (Parameters.ContainsKey(name)) | |
{ | |
return Parameters[name]; | |
} | |
return ""; | |
} | |
} | |
#> |
3 years later...if anyone wants an alternative to T4 templates, I wrote a command line tool based off the code in this gist:
You can add it as a post-build step which makes build server integration a little easier (also we were having problems with conflicting version of JSON.NET).
webapi vesion of this template?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You must use firstLowerCase for hub property name: