-
Notifications
You must be signed in to change notification settings - Fork 4
Recipes
Steve Leigh edited this page Dec 10, 2018
·
6 revisions
The basic idea is to add a console project to your application which does the following:
- Uses reflection to get all the API Controllers
- Uses reflection to find all the parameter and return values for the public methods in these controllers
- Does some filtering and transforming to ensure only relevant stuff is being included
- Uses TypeLite to grab the relevant inputs/outputs
- Uses EnumGenie to emit all the enums and helpers
public class Program
{
public static void Main()
{
var apiTypes = typeof(FooController).Assembly.GetExportedTypes()
.Where(t => t.IsSubclassOf(typeof(ApiController)))
.SelectMany(type => type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod))
.SelectMany(ParameterAndReturnTypes)
.SelectMany(Unwrap)
.Where(t => !t.IsPrimitive && t != typeof(string))
.Where(t => !t.Namespace.StartsWith("System.")) // Customize this bit to suit your app
.Distinct()
.OrderBy(t => t.FullName) // Makes output better for diff
.ToList();
var typeScriptFluent = TypeScript.Definitions()
.WithIndentation(" ")
.WithModuleNameFormatter(tsModule => "Api")
.WithConvertor<DateTimeOffset>(obj => "string") // You may need to add more of these
.WithConvertor<Guid>(obj => "string")
.WithMemberFormatter(mf =>
{
// Hack to ignore statics - limitation of TypeLite
if ((mf.MemberInfo as PropertyInfo)?.GetGetMethod().IsStatic ?? false)
return $"// Ignore static: {mf.Name}";
// Hack to mark nullables as such- limitation of TypeLite
var suffix = ((mf.MemberInfo as PropertyInfo)?.PropertyType.IsNullable() ?? false) ? "?" : "";
return $"{char.ToLower(mf.Name[0])}{mf.Name.Substring(1)}{suffix}";
})
.AsConstEnums(false);
foreach (var type in apiTypes)
{
typeScriptFluent = typeScriptFluent.For(type);
}
var tsModel = typeScriptFluent.ModelBuilder.Build();
File.WriteAllText("api.d.ts", typeScriptFluent.Generate());
new EnumGenie.EnumGenie()
.SourceFrom.List(tsModel.Enums.Select(e => e.Type))
.WriteTo.File("enums.ts", cfg => cfg.TypeScript())
.Write();
}
private static IEnumerable<Type> ParameterAndReturnTypes(MethodInfo method)
{
return method.GetParameters().Select(p => p.ParameterType)
.Concat(new[] { method.ReturnType })
.Distinct();
}
private static IEnumerable<Type> Unwrap(Type type)
{
return type.IsGenericType ? UnwrapGeneric(type)
: type.IsArray ? UnwrapArray(type)
: new[] { type };
}
private static IEnumerable<Type> UnwrapArray(Type type)
{
return Unwrap(type.GetElementType());
}
private static IEnumerable<Type> UnwrapGeneric(Type type)
{
return type.GenericTypeArguments.SelectMany(Unwrap);
}
}
Have you tried Typescriptr? This snippet has a little hack from the default configuration to write enum definitions to the same file as numbers, so the output is compatible with Enum Genie.
public class Program
{
public static void Main()
{
var generator = TypeScriptGenerator.CreateDefault()
.WithEnumFormatter(EnumFormatter.ValueNumberEnumFormatter, (type, style) => type.Name)
.WithTypeMembers(MemberType.PropertiesAndFields);
var typesToGenerate =
typeof(IAmWebApi).Assembly.ExportedTypes
.Where(type => type.GetCustomAttribute<ClientTypeAttribute>() != null);
var result = generator.Generate(typesToGenerate);
using(var fs = File.Create("types.d.ts"))
using(var tw = new StreamWriter(fs)) {
tw.Write(result.Enums.Replace("enum ", "declare enum ") + result.Types);
}
new EnumGenie.EnumGenie()
.SourceFrom.Assembly(typeof(IAmWebApi).Assembly)
.WriteTo.File("enums.ts", cfg => cfg.TypeScript())
.Write();
}
}