-
-
Save robfe/4583549 to your computer and use it in GitHub Desktop.
<#@ 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 ""; | |
} | |
} | |
#> |
I think that fixed it! Many thanks.
For what it's worth, using the Tangible T4 editor, I do get a blue squiggly on this line:
<#@ assembly name="$(TargetPath)" #>
And the associated error says:
The assembly $(TargetPath) could not be loaded. There was an exceptiong during load: A dependency could not be found!
But it does in fact generate the appropriate TS file, which is very helpful. Many thanks!
Yeah I get a squiggly from ForTea as well - but t4 itself can handle expanding the variable - it replaces it with the output dll of the current project.
Awesome template, great work! I made a slightly modified version that takes into account JsonProperty attributes :)
You must use firstLowerCase for hub property name:
//#region available hubs
interface SignalR {
<#
foreach (var hub in hubmanager.GetHubs())
{
#>
/**
* The hub implemented by <#=hub.HubType.FullName#>
*/
<#= FirstCharLowered(hub.Name) #> : <#= hub.HubType.Name #>;
<#
}
#>
}
//#endregion available hubs
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?
Actually, I've managed to replicate & fix the problem and have updated this Gist (see line 14), let me know if it works for you now...