How to reuse existing C# class definitions in TypeScript projects

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"?

Answers:

Answer

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#:

http://type.litesolutions.net/

Answer

Web Essentials allow to compile C# files to TypeScript .d.ts files on save. Then you could reference the definitions from your .ts files.

enter image description here

Answer

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.

Answer

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 enter image description here 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;
}
Answer

Try Reinforced.Typings framework. Seems that it solves your problem.

  1. Install it from NuGet
  2. Navigate to your POCO and add [TsInterface] attribute above it

    using 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; }
        }
    }
    
  3. Rebuild your project
  4. Find out generated TypeScript code in %Your_Project_Directory%/Scripts/project.ts file and add it to project manually

    module YourNamespace {
        export interface IYourPoco
        {
            YourNumber: number;
            YourString: string;
            YourArray: string[];
            YourDictionary: { [key: int]: any };
        }
    }
    
  5. Do the same for all your POCOs and reference project.ts in your other TypeScript code.

See more details in documentation wiki

Answer

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

Answer

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[];
}
Answer

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. :-)

Answer

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.

Answer

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

Answer

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

Answer

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/

Answer

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();
    }
#>
Answer

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!

Answer

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.

Answer

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.

Answer

If you're using Visual Studio, add the Typewriter extension.

Visual Studio Gallery

Website/Documentation

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.

Answer

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.

Answer

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

Answer

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

Answer

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.

Answer

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;
}
Answer

If you are using VSCode, you could take a look at that extension C# to TypeScript

Convert C# class to TypeScript interface

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

Answer

I wrote a feature request about this on the Developer Community page:

https://developercommunity.visualstudio.com/idea/1153873/reuse-existing-net-classes-for-typescript-definiti.html

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.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.