How to reuse existing C# class definitions in TypeScript projects

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

Solutions/Answers:

Solution 1:

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/

Solution 2:

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

Solution 3:

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

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

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.

Solution 6:

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