-
-
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 hubs3 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...