Click here to Skip to main content
15,884,628 members
Articles / Programming Languages / Typescript
Tip/Trick

Generating TypeScript from .NET Assemblies for Use with the Knockout.js Mapping Plug-in

Rate me:
Please Sign up or sign in to vote.
4.67/5 (6 votes)
15 Apr 2014CPOL2 min read 21.5K   7   4
The combination of C#, TypeScript, Knockout, and the mapping plug-in creates a problem. I need a tool to read my server-side C# classes and create client side TypeScript definitions

Introduction

Some parts of my Knockout ViewModels are objects that have been returned from my WebApi controllers and transformed by The Knockout mapping plug-in. So how can I get TypeScript type checking at design time when these objects are dynamically created observables?

The mapping plug-in takes JSON data and dynamically converts it to nested collections of Knockout observables. It's not going to write any TypeScript definition files for me.

This combination of C#, TypeScript, Knockout, and the mapping plug-in creates a problem. I need a tool to read my server-side C# classes and create corresponding client-side TypeScript definitions. But as an extra complication, they have to be Knockout observables, not normal TypeScript types.

Background

I found that there are some tools that will read the metadata from a .NET assembly and generate corresponding TypeScript definitions. But none of them solved the extra complication of generating Knockout observables.

I put together a basic console app that uses reflection to read an assembly and generate the TypeScript definition file for the data transfer objects (DTOs) I am returning to my Knockout ViewModel.

Using the Code

First, I wrote two custom attributes and added them to my project:

C#
public class TypeScriptKnockout
{
    // use this attribute on C# classes for which you want to generate
    // a TypeScript class with Knockout Observable properties
    public class GenerateTypeScript : Attribute
    {
        public override string ToString()
        {
            return "TypeScriptKnockout.GenerateTypeScript";
        }
    }

    public class NotObservable : Attribute
    {
        public override string ToString()
        {
            return "TypeScriptKnockout.NotObservable";
        }
    }
}

Then I use the first attribute, GenerateTypeScript, on any classes I want to generate TypeScript for.

C#
[TypeScriptKnockout.GenerateTypeScript]
public class MappingsViewModel : ISwisSecurityViewModel
{
    public bool UpdateMode { get; set; }
    public string Version { get; set; }
    public string ServerName { get; set; }
    public string Environment { get; set; }

    public List<Profile> Profiles { get; set; }
    public Profile SelectedProfile { get; set; }

    public string ApplyToOtherEnvironment { get; set; }

    [TypeScriptKnockout.NotObservable]
    public GroupMappings GroupMappings { get; set; }
}

I use the second one, NotObservable, to indicate any properties that I do not want to be observable. This will sometimes be necessary, since the Knockout mapping plug-in only makes the "leaf nodes" of any data structure observable (so any classes that contain more classes are marked as "not observable").

I build my project and then run the command line tool. I specify my assembly name, and the namespace I wish to search for classes.

TypeScriptKnockout Security.dll Security.Models.ViewModels 

The tool reads the assembly, looks for classes and interfaces. It reads the properties and creates a TypeScript property with a matching observable type:

C#
switch (typeName)
{
    case "Int32":
        return notObservable ? "number" : "KnockoutObservable<number>";

    case "String":
        return notObservable ? "string" : "KnockoutObservable<string>";

    case "DateTime":
        return notObservable ? "Date" : "KnockoutObservable<Date>";

The generated types will be of KnockoutObservable<T>, which is defined in the type definition file for Knockout.js, available from Definitely Typed.

The console app generates a file with all the interfaces for the namespace you specify:

JavaScript
 class MappingsViewModel implements ISwisSecurityViewModel {
    UpdateMode: KnockoutObservable<boolean>;
    Version: KnockoutObservable<string>;
    ServerName: KnockoutObservable<string>;
    Environment: KnockoutObservable<string>;
    Profiles: KnockoutObservableArray<Profile>;
    SelectedProfile: KnockoutObservable<Profile>;
    ApplyToOtherEnvironment: KnockoutObservable<string>;
    GroupMappings: GroupMappings;
} 

Now include the generated definition file and you will get type checking at design time, and intellisense. As you can see, Visual Studio knows SelectedProfile is of type KnockoutObservable<Profile>

Image 1

This console app method is pretty basic, and it would be nicer if it were called by the build script. But it works well enough.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I live my life on the teachings of the Ten Minute Podcast.

Comments and Discussions

 
QuestionIs this a preferred approach to achieve this? Pin
Member 1229526029-Jan-16 1:21
Member 1229526029-Jan-16 1:21 
GeneralMy vote of 1 Pin
Member 79678489-Sep-14 6:22
Member 79678489-Sep-14 6:22 
QuestionSample Source Code Pin
Perry15-Apr-14 10:34
Perry15-Apr-14 10:34 
AnswerRe: Sample Source Code Pin
CoffeeAndDonuts15-Apr-14 11:18
CoffeeAndDonuts15-Apr-14 11:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.