How to reuse existing C# class definitions in TypeScript projects
JavascriptTypescriptJavascript Problem Overview
I am just going to start use TypeScript
in my HTML client project which belongs to a MVC project with a entity framework domain model already there. I want my two projects (client side and server side) totally separated as two teams will work on this... JSON and REST is used to communicate objects back and forth.
Of course, my domain
objects on the client side should match the objects on the server side. In the past, I have normally done this manually. Is there a way to reuse my C# class definitions (specially of the POJO
classes in my domain model) to create the corresponding classes in TypeScript"?
Javascript Solutions
Solution 1 - Javascript
There is not currently anything that will map C# to TypeScript. If you have a lot of POCOs or you think they might change often, you could create a converter - something simple along the lines of...
public class MyPoco {
public string Name { get; set; }
}
To
export class MyPoco {
public Name: string;
}
There is also a discussion on Codeplex about auto-generating from C#.
Just to keep things updated, TypeLite can generate TypeScript interfaces from C#:
Solution 2 - Javascript
Web Essentials allow to compile C# files to TypeScript .d.ts
files on save. Then you could reference the definitions from your .ts
files.
Solution 3 - Javascript
If you use vscode you can use my extension csharp2ts which does exactly that.
You just select the pasted C# code and run the Convert C# to TypeScript
command from the command palette
A conversion example:
public class Person
{
/// <summary>
/// Primary key
/// </summary>
public int Id { get; set; }
/// <summary>
/// Person name
/// </summary>
public string Name { get; set; }
}
to
export interface Person
{
/**Primary key */
Id : number;
/**Person name */
Name : string;
}
Solution 4 - Javascript
TypeLite and T4TSs above both looked good, just picked one, TypeLite, forked it to get support for
- ValueTypes,
- Nullables
- camelCasing (TypeScript root doc uses camels, and this goes too nice together with C#)
- public fields (love clean and readable POCOs, also makes it easy for the C# Compiler)
- disable module generation
Then I needed C# interfaces and thought it is time to bake my own thing and wrote a simple T4 script that just does what I need. It also includes Enums. No repo required, just < 100 lines of T4.
Usage
No library, no NuGet, just this plain simple T4 file - use "add item" in Visual Studio and choose any T4 template. Then paste this into the file. Adapt every line with "ACME" in it. For every C# class add a line
<#= Interface<Acme.Duck>() #>
Order matters, any known type will be used in follwing interfaces. If you use only interfaces, the file extension can be .d.ts, for enums you need a .ts file, since a variable is instantiated.
Customisation
Hack the script.
<#@ template debug="true" hostSpecific="true" language="C#" #>
<#@ output extension=".ts" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ assembly name="$(TargetDir)ACME.Core.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Linq" #>
<#= Interface<Acme.Bunny>() #>
<#= Interface<Acme.Duck>() #>
<#= Interface<Acme.Birdy>() #>
<#= Enums<Acme.CarrotGrade>() #>
<#= Interface<Acme.LinkParticle>() #>
<#+
List<Type> knownTypes = new List<Type>();
string Interface<T>()
{
Type t = typeof(T);
var sb = new StringBuilder();
sb.AppendFormat("interface {0} {{\n", t.Name);
foreach (var mi in GetInterfaceMembers(t))
{
sb.AppendFormat(" {0}: {1};\n", this.ToCamelCase(mi.Name), GetTypeName(mi));
}
sb.AppendLine("}");
knownTypes.Add(t);
return sb.ToString();
}
IEnumerable<MemberInfo> GetInterfaceMembers(Type type)
{
return type.GetMembers(BindingFlags.Public | BindingFlags.Instance)
.Where(mi => mi.MemberType == MemberTypes.Field || mi.MemberType == MemberTypes.Property);
}
string ToCamelCase(string s)
{
if (string.IsNullOrEmpty(s)) return s;
if (s.Length < 2) return s.ToLowerInvariant();
return char.ToLowerInvariant(s[0]) + s.Substring(1);
}
string GetTypeName(MemberInfo mi)
{
Type t = (mi is PropertyInfo) ? ((PropertyInfo)mi).PropertyType : ((FieldInfo)mi).FieldType;
return this.GetTypeName(t);
}
string GetTypeName(Type t)
{
if(t.IsPrimitive)
{
if (t == typeof(bool)) return "bool";
if (t == typeof(char)) return "string";
return "number";
}
if (t == typeof(decimal)) return "number";
if (t == typeof(string)) return "string";
if (t.IsArray)
{
var at = t.GetElementType();
return this.GetTypeName(at) + "[]";
}
if(typeof (System.Collections.IEnumerable).IsAssignableFrom(t))
{
var collectionType = t.GetGenericArguments()[0]; // all my enumerables are typed, so there is a generic argument
return GetTypeName(collectionType) + "[]";
}
if (Nullable.GetUnderlyingType(t) != null)
{
return this.GetTypeName(Nullable.GetUnderlyingType(t));
}
if(t.IsEnum) return "number";
if(knownTypes.Contains(t)) return t.Name;
return "any";
}
string Enums<T>() // Enums<>, since Enum<> is not allowed.
{
Type t = typeof(T);
var sb = new StringBuilder();
int[] values = (int[])Enum.GetValues(t);
sb.AppendLine("var " + t.Name + " = {");
foreach(var val in values)
{
var name = Enum.GetName(typeof(T), val);
sb.AppendFormat("{0}: {1},\n", name, val);
}
sb.AppendLine("}");
return sb.ToString();
}
#>
The next level of the script will be to create the service interface from the MVC JsonController class.
Solution 5 - Javascript
Here's my approach to solving it. Declare your C# classes with an attribute and .d.ts-files will be generated (using T4 transforms). There's a package on nuget and the source is available on github. I'm still working on the project, but the support is pretty extensive.
Solution 6 - Javascript
If you're using Visual Studio, add the Typewriter extension.
Update
With Web Essentials installed in VS 2015, you can right-click the class file, then > Web Essentials > Create Typescript Intellisense File from the context menu.
Solution 7 - Javascript
Try Reinforced.Typings framework. Seems that it solves your problem.
-
Install it from NuGet
-
Navigate to your POCO and add
[TsInterface]
attribute above itusing Reinforced.Typings.Attributes; namespace YourNamespace { [TsInterface] public class YourPoco { public int YourNumber { get;set; } public string YourString { get;set; } public List<string> YourArray { get;set; } public Dictionary<int, object> YourDictionary { get;set; } } }
-
Rebuild your project
-
Find out generated TypeScript code in
%Your_Project_Directory%/Scripts/project.ts
file and add it to project manuallymodule YourNamespace { export interface IYourPoco { YourNumber: number; YourString: string; YourArray: string[]; YourDictionary: { [key: int]: any }; } }
-
Do the same for all your POCOs and reference
project.ts
in your other TypeScript code.
See more details in documentation wiki
Solution 8 - Javascript
I have created a small utility that can generate TypeScript interfaces from C# classes. Is available as a NuGet package. Detailed documentation can be found on the project webpage.
Solution 9 - Javascript
Please have a look at this library Typewriter
It converts not only classes, enums, interfaces etc, but also the api-controllers, which is simply awesome..
Plus it does as this as soon as you save the source .cs file, so I don't have to trigger any external tool. Save .cs and you get updated .ts
Solution 10 - Javascript
I have a little solution which uses T4 templates (view source).
You go from any CLR POCO:
public class Parent : Person
{
public string Name { get; set; }
public bool? IsGoodParent { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
To a TypeScript interface:
///<reference path="Child.d.ts" />
///<reference path="Person.d.ts" />
interface Parent extends Person {
Name : string;
IsGoodParent? : bool;
Children : Child[];
}
Solution 11 - Javascript
You can use the open-source project NSwag: In the GUI, you can select .NET class from an existing .NET DLL and generate the TypeScript interface for it.
The project also provides command line tools and support for T4 templates as well as generation of client code for Web API controllers...
Solution 12 - Javascript
You can also use this: https://github.com/pankleks/TypeScriptBuilder
This small library generates TypeScript type definition based on C# types. Use it directly in your backend C# project to generate code for your frontend TypeScript project. You can also wrtie small console app, to generate code by pre-build tools.
Works on Full & NET Core framework!
Install by nuget:
Install-Package TypeScriptBuilder
Supported features
- Resolving type dependency
- Generics
- Type inheritance
- Namespaces (modules)
- Enums
- Nullable types
- Dictionary converison (to strong type TS indexed objects)
- Set of code generation control attributes
any
for types that can't be converted
More description: https://github.com/pankleks/TypeScriptBuilder/blob/master/README.md
Solution 13 - Javascript
If you need to create a separate file for every generated TypeScript class/interface (i.e. in a "single class per file" manner), you can try TypeGen. It's a tool you can use from the Package Manager Console to generate TypeScript files based on your C# classes/enums. It currently supports:
- exporting enums; exporting POCOs as TS classes or interfaces
- inheritance
- generic types
- collection / nested collection types
plus some additional features. It's also open source (you can check it out on github).
Solution 14 - Javascript
How about the other way around?
Check out erecruit TypeScript Translator. It comes with ready to go C# support, but is actually template-based (uses Nunjucks for rendering), which means it can generate anything else - VB.NET, F#, C++, XML, SQL - whatever you can encode with a template.
Works as a .NET console program, NodeJS program (for those not on Windows), or as a Visual Studio extension, complete with generate-on-save functionality. And includes MSBuild support, just to make your build server happy. :-)
Solution 15 - Javascript
Guys have a look at https://github.com/reinforced/**Reinforced.Typings**. I have been playing with typelite and t4 templates for last few days and ended up with this project. It is super-simple and works like a charm. Just get the package, modify the configuration file (it is like for 10 seconds) and build. All gets done automatically without any issues. Bless the author!
The bad thing about T4 templates is that once you build from VS the scanned assemblies are locked and you must restart VS (how silly is that?). There are some workaround in T4 Toolbox + some VS cleaning directives but none of these worked for me.
Solution 16 - Javascript
I like the citykid solution. I had extend it a little bit. So, solution also is based on codegeneration technique with T4 templates.
It can generate common TypeScript types and ambient declarations.
It supports inheritance and interface implementations.
Supports generics, arrays and lists as type fields.
Also it translates to TypeScript types that doesn't explicitly mentioned in configuration (for example we import type A, and in TS output you can find some other types: types of the fields, base types and interfaces).
You can also override type's name.
Enums are also supported.
Usage example (you can find it in the project repository):
// set extension of the generated TS file
<#@ output extension=".d.ts" #>
// choose the type of TS import TsMode.Ambient || TsMode.Class
<# var tsBuilder = new TsBuilder(TsMode.Ambient); #>
// reference assembly with the c# types to be transformed
<#@ assembly name="$(SolutionDir)artifacts\...\CsT4Ts.Tests.dll" #>
// reference namespaces
<#@ import namespace="CsT4Ts.Tests" #>
<#
//add types to processing
tsBuilder.ConsiderType(typeof(PresetDTOBase), "PresetBase");
tsBuilder.ConsiderType(typeof(PresetDTO), "Preset");
tsBuilder.ConsiderType(typeof(TestInterface<,>));
#>
// include file with transformation algorithms
<#@ include file="CsT4Ts.t4" #>
And you will get an output
//CsT4Ts.Tests.PresetDTOBase => PresetBase
// CsT4Ts.Tests.PresetDTO => Preset
// CsT4Ts.Tests.TestInterface`2 => TestInterface
// CsT4Ts.Tests.TestEnum => TestEnum
declare class PresetBase
{
PresetId: string;
Title: string;
InterviewDate: string;
}
declare class Preset extends PresetBase
{
QuestionsIds: string[];
}
declare interface TestInterface<TA, TB>
{
A: string;
B: number;
C: TestEnum;
D: TestEnum[];
E: number[];
F: TA;
G: TB[];
}
declare enum TestEnum
{
Foo = 10,
Boo = 100
}
Check full solution here: https://bitbucket.org/chandrush/cst4ts
Solution 17 - Javascript
You can also use Bridge.net. From version 1.7 it supports generation of TypeScript definitions for C# types. See http://bridge.net/docs/generate-typescript-definitions/
Solution 18 - Javascript
I also liked @citykid's answer a lot, so I extended it to do a whole namespace at a time. Just put the POCO classes into the namespace, and rebuild T4 templates. I wish I know how to generate separate files for each, but that not the end of the world.
You need to reference the .DLL files in the top part (where the classes are that you want), and you need to mention the namespaces. All lines to edit are marked with ACME. Major cudos to @citykid, appreciate it!
<#@ template debug="true" hostSpecific="true" language="C#" #>
<#@ output extension=".ts" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ assembly name="$(TargetDir)YOUR_DLL_NAME_HERE_ACME.dll" #>
<#@ assembly name="$(TargetDir)YOUR_OTHER_DLL_NAME_HERE_ACME.dll" #>
<#@ assembly name="$(TargetDir)YOUR_OTHER_DLL_NAME_HERE_ACME.dll" #>
<#@ assembly name="$(TargetDir)YOUR_OTHER_DLL_NAME_HERE_ACME.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Reflection" #>
<#= Process("My.Very.Special.Namespace.ACME") #>
<#= Process("My.Other.Very.Special.Namespace.ACME") #>
<#= Process("My.Other.Very.Special.Namespace.ACME") #>
<#= Process("My.Other.Very.Special.Namespace.ACME") #>
<#+
List<Type> knownTypes = new List<Type>();
string Process(string nameSpace) {
var allass = AppDomain.CurrentDomain.GetAssemblies();
var ss = "";
foreach (var ass in allass)
{
ss += ProcessAssembly(ass, nameSpace);
}
return ss;
}
string ProcessAssembly(Assembly asm, string nameSpace) {
try {
Type[] types;
try
{
types = asm.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
types = e.Types;
}
var s = "";
foreach (var t in types.Where(t => t != null))
{
try {
if (String.Equals(t.Namespace, nameSpace, StringComparison.Ordinal))
{
s += InterfaceOfType(t);
}
} catch (Exception e)
{
}
}
return s;
}
catch (Exception ee2) {
return "// ERROR LOADING TYPES: " + ee2;
}
}
string InterfaceOfType(Type T)
{
Type t = T;
var sb = new StringBuilder();
sb.AppendFormat("interface {0} {{\r\n", t.Name);
foreach (var mi in GetInterfaceMembers(t))
{
sb.AppendFormat(" {0}: {1};\r\n", this.ToCamelCase(mi.Name), GetTypeName(mi));
}
sb.AppendLine("}");
knownTypes.Add(t);
return sb.ToString();
}
string Interface<T>()
{
Type t = typeof(T);
var sb = new StringBuilder();
sb.AppendFormat("interface {0} {{\n", t.Name);
foreach (var mi in GetInterfaceMembers(t))
{
sb.AppendFormat(" {0}: {1};\r\n", this.ToCamelCase(mi.Name), GetTypeName(mi));
}
sb.AppendLine("}");
knownTypes.Add(t);
return sb.ToString();
}
IEnumerable<MemberInfo> GetInterfaceMembers(Type type)
{
return type.GetMembers(BindingFlags.Public | BindingFlags.Instance)
.Where(mi => mi.MemberType == MemberTypes.Field || mi.MemberType == MemberTypes.Property);
}
string ToCamelCase(string s)
{
if (string.IsNullOrEmpty(s)) return s;
if (s.Length < 2) return s.ToLowerInvariant();
return char.ToLowerInvariant(s[0]) + s.Substring(1);
}
string GetTypeName(MemberInfo mi)
{
Type t = (mi is PropertyInfo) ? ((PropertyInfo)mi).PropertyType : ((FieldInfo)mi).FieldType;
return this.GetTypeName(t);
}
string GetTypeName(Type t)
{
if(t.IsPrimitive)
{
if (t == typeof(bool)) return "boolean";
if (t == typeof(char)) return "string";
return "number";
}
if (t == typeof(decimal)) return "number";
if (t == typeof(string)) return "string";
if (t.IsArray)
{
var at = t.GetElementType();
return this.GetTypeName(at) + "[]";
}
if(typeof (System.Collections.IEnumerable).IsAssignableFrom(t))
{
var collectionType = t.GetGenericArguments()[0]; // all my enumerables are typed, so there is a generic argument
return GetTypeName(collectionType) + "[]";
}
if (Nullable.GetUnderlyingType(t) != null)
{
return this.GetTypeName(Nullable.GetUnderlyingType(t));
}
if(t.IsEnum) return "number";
if(knownTypes.Contains(t)) return t.Name;
return "any";
}
string Enums<T>() // Enums<>, since Enum<> is not allowed.
{
Type t = typeof(T);
var sb = new StringBuilder();
int[] values = (int[])Enum.GetValues(t);
sb.AppendLine("var " + t.Name + " = {");
foreach(var val in values)
{
var name = Enum.GetName(typeof(T), val);
sb.AppendFormat("{0}: {1},\r\n", name, val);
}
sb.AppendLine("}");
return sb.ToString();
}
#>
Solution 19 - Javascript
My solution was to write a small codegen util that simply takes a project assembly (and refering assemblies) and start scanning types that are involved in the interaction between typescript and c#. This util outputs both javascript as d.ts ... The tool is called in the post-build event ... works like a charm!
Solution 20 - Javascript
If interested, you can use TypedRpc. Its purpose is not only to create the interfaces in TypeScript, but to create all communication with the service in .Net using the JsonRpc protocol.
Example for a class in server:
[TypedRpc.TypedRpcHandler]
public class RpcServerExample
{
public String HelloWorld()
{
return "Hello World!";
}
}
Usage of generated TypeScript code:
/// <reference path="Scripts/TypedRpc.ts" />
let rpc: TypedRpc.RpcServerExample = new TypedRpc.RpcServerExample();
var callback = function(data, jsonResponse) {
console.log(data);
};
rpc.HelloWorld().done(callback).fail(callback);
Check out https://github.com/Rodris/TypedRpc for other examples of how to use it.
Solution 21 - Javascript
ASP.NET Web API Client Generators may be more handy, less overhead than swagger toolchain and others during SDLC.
While programmers generally use WebApiClientGen to generate client API codes, this project also provides POCO2TS.exe, a command line program that generates TypsScript interfaces from POCO classes. You may use either Poco2ts.exe or the poco2ts component to integrate the code generation with your build pipeline.
Solution 22 - Javascript
If you want to convert it through node.js then you can use this package(csharp-to-typescript). https://www.npmjs.com/package/csharp-to-typescript
sourcode: https://github.com/YuvrajSagarRana/csharp-to-typescript
eg:
// After installation, import the package.
var { CsharpToTs, getConfiguration } = require("csharp-to-typescript");
// Use CsharpToTs to convert your source code a. Method one give your source code as string:
const sourceCodeInString = `public class Address
{
public int Id {get; set;}
public string Street { get; set; }
public string City { get; set; }
}`
var outputTypescript = CsharpToTs(sourceCodeInString, getConfiguration());
console.log(outputTypescript);
// Output is
export class Address
{
Id: number;
Street: string;
City: string;
}
Solution 23 - Javascript
If you are using VSCode, you could take a look at that extension C# to TypeScript
See how I can convert it from C# code in just a click. Of course, not all the classes will be converted such as the factory, but it is good enough for the client-side. We just need the shape of data. Sometimes If I need to use the visitor pattern, I will decorate with additional methods that I need. It took only like 5 seconds to do so. Comparing with one minute above, I could say it is a big win.
See more detail in my blog post
Solution 24 - Javascript
I wrote a feature request about this on the Developer Community page:
As you can see in the answers here there are many projects that tries to solve this but unfortunately many of these projects are single developer projects that stops being supported along the way. I think it would be really neat if Microsoft maintained one of these projects instead, creating a .NET
to TypeScript
generator.
Among others, some are still maintained but relies heavily on a single developer:
TypeLite:
https://bitbucket.org/LukasKabrt/typelite/src/default/
TypeScript Definition Generator:
https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TypeScriptDefinitionGenerator
Typewriter:
https://github.com/frhagn/Typewriter
TypeGen:
https://github.com/jburzynski/TypeGen
Reinforced.Typings:
https://github.com/reinforced/Reinforced.Typings
My tip would be to choose a project that you from the start expect to stop being maintained and is prepared to switch to something that is currently maintained. I would go for something that uses annotations and cross my fingers I could replace simply that tag with something else if that project stops being supported.
Solution 25 - Javascript
I use a small custom DSL that generates both TypeScript and C# DTO/POCO classes. The DSL looks like the following:
output CSharp
output TypeScript
namespace MyLibrary.Logic.DataModel.DTOs
class Something
property int ID
property string Name
property DateTime DateTime
property int ForeignKeyID
This way I have a DTO model that has a single source (the DSL) and sits between the two C# and TypeScript logic models.
I then write both a C# and TypeScript DTOFactory class that has methods responsible for converting between DTOs and Logic Model objects. (And part of the DTO generation are serialization methods that convert to an anonymous JSON-suitable type for serialization or for storing in IndexedDB.)
By doing this, as soon as there is a model change on either side, I get compilation errors until both sides match again.