slovely / TypeScriptDefinitionsGenerator

Generate TypeScript .d.ts files for your server classes, plus optionally create client-side interfaces for WebAPI / SignalR server implementations
MIT License
10 stars 1 forks source link
typescript

Build status Nuget

TypeScriptDefinitionsGenerator

This is a solution enabling TypeScript definitions of your server-side code to be generated quickly and automatically on each build. Ensures that renaming a class/property on the server doesn't break your client-side code at runtime.
Supports WebAPI and SignalR method return/parameter types automatically, or tell it where the classes you want to convert are and it'll do them.

Features

Setup

1 - Because we want the build to fail if something on the server doesn't match with something on the client, we need to have the server build first - this means that you WebAPI / SignalR hubs need to be moved into a separate assembly, which you then reference from your web project.
Normally this 'just works' as WebAPI/SignalR find controllers/hubs in any references assemblies.

2 - Install the TypeScriptDefinitionsGenerator nuget package to your web project.

3 - Update the TsGenerator.props file added by the nuget package.

4 - Include the generated files in your project and ensure enums.ts and actions.ts are output into your HTML (e.g. include in your BundleConfig).

5 - The default generation of WebAPI methods requires JQuery to be present, as it using $.ajax for calling the server.

6 - You must define a variable in your javascript called 'rootPath' that points to the website route, something like this:

        <script type="text/javascript">
            var rootPath = '@Url.Content("~/")';
        </script>
TsGenerator.props Options
TsGenApiMethodStyle / TsGenWebApiMethods

Setting TsGenWebApiMethods to true will search your assemblies for WebAPI actions and generate TypeScript methods so you can call them in a type-safe way.
The default format of the call will be like this:

    // Example c# Web API method
    [HttpGet]
    public Person LoadPerson(int id, string type) { .... }

    // Generated TS (output to actions.ts)
    //AUTOGEN START
    module Api {
        export class Person{
            public static search(id: number, name: string, ajaxOptions: JQueryAjaxSettings = null): JQueryPromise<Person> {
                return ServiceCaller.get("api/person/search/" + id + "?name=" + name, null, ajaxOptions);
        }
    }
    //AUTOGEN END

    // Now you can use this like this:
    Api.Person.loadPerson(42, "Dave").done(response => alert(response.Name));

By default, ServiceCaller.ts will also be output and will use JQuery AJAX methods to make the calls to the server. It supports [HttpPost]/[HttpGet] attributes as well as understanding whether parameters are part of the URL (route parameters), request body ([FromBody]) or QueryString (everything else). If you do not use JQuery you can set TsGenSuppressDefaultServiceCaller=true, and provide your own implementation instead - although currently actions.ts will still return JQueryPromise results. When calling the methods, you can optionally override AJAX settings by supplying a standard JQueryAjaxSettings object.

By setting TsGenWebApiMethods=Aurelia, the generated methods will use the Aurelia HttpClient instead of JQuery. The output will then be something like this:

    //AUTOGEN START
    import {autoinject} from "aurelia-dependency-injection";
    import {HttpClient, json} from "aurelia-fetch-client";

    @autoinject
    export class Person {
        constructor(private http: HttpClient) {
        }

        public search(id: number, name: string, ajaxOptions: RequestInit = null): PromiseLike<Person> {
            const options: RequestInit = { 
                method: "get", 
                body: null
            };
            if (ajaxOptions) Object.assign(options, ajaxOptions);
            return this.http.fetch("api/person/search/" + id + "?name=" + name, options)
              .then(response => (response && response.status!==204) ? response.json() : null);
        }
    }
    //AUTOGEN END

    // Now in your Aurelia models, you can inject this in like this:
    import Actions = require("../server/actions");

    @autoinject
    export class PersonViewModel {
        constructor(personCtrl: Actions.Person) {
            personCtrl.search(1, "Joe").then(result => alert(result.Name));
        }
    }

The generation currently only works with actions using the {controller}/{action}/{id} route format, or if the method has a custom [RouteAttribute].

Multiple Assemblies

When using multiple assemblies, to avoid a whole world of pain, ensure that all any dependencies shared between the assemblies are the same version (e.g. if you reference JSON.NET v9.0.0 in one project, make sure the other project references v9.0.0 as well).

ASPNET CORE v2

Since v.1.0.57, very early support for ASP.Net Core (v2 only) is included. The instructions for running it are the same as above, but a couple of additional steps are required:

NOTE: Because there is no way to know if an action is MVC or WebAPI in Core, the generator assumes actions that return IActionResult are for returning MVC views and will be excluded. Also, there is no support for API methods that don't have the route: api/{controller}/{action} which is obviously pretty limiting unfortunately.

Thanks

Massive thanks go to Lukas Kabrt for his wonderful TypeLite library which does the bulk of the TypeScript generation. I am pleased that I was able to contribute the first version of generics support to his project while making this project.

Also thanks to Murat Girgin whose work on SRTS provided inspiration for the SignalR hub generation (indeed an earlier version used a hacked version of this library).