ES6 Class Add ServiceStack Reference

In addition to TypeScript support for generating typed Data Transfer Objects (DTOs), JavaScript is now supported in the form of JSDoc annotated typed ES6 classes that can be referenced natively from JavaScript Modules.

Reference directly in JavaScript Modules

Unlike TypeScript, the JavaScript ES6 class DTOs can be referenced directly in a browser as-is, removing the need to keep your DTOs in sync with extra tooling by direct referencing them in a JavaScript Module:

<script type="module">
import { Hello } from '/types/mjs'
</script>

Then to make typed API Requests from web pages, you need only need to reference an ES Module (.mjs) build of the dependency-free @servicestack/client library which can be sourced directly from a npm CDN:

<script type="module">
import { JsonServiceClient } from 'https://unpkg.com/@servicestack/client@2/dist/servicestack-client.min.mjs'
import { Hello } from '/types/mjs'

const client = new JsonServiceClient()

const api = client.api(new Hello({ name:'World' }))
if (api.succeeded) {
    console.log(api.response)
}
</script>

Import Maps

Although we recommend using an importmap to specify where to load @servicestack/client from, e.g:

<script async src="https://ga.jspm.io/npm:es-module-shims@1.6.3/dist/es-module-shims.js"></script><!--safari-->
<script type="importmap">
{
    "imports": {
        "@servicestack/client":"https://unpkg.com/@servicestack/client@2/dist/servicestack-client.min.mjs"
    }
}
</script>

ImportMap in Razor Pages or MVC

Razor Pages or MVC projects can use @Html.ImportMap() in _Layout.cshtml to use different builds for development and production, e.g:

@if (Context.Request.Headers.UserAgent.Any(x => x.Contains("Safari") && !x.Contains("Chrome")))
{
    <script async src="https://ga.jspm.io/npm:es-module-shims@1.6.3/dist/es-module-shims.js"></script>
}
@Html.ImportMap(new()
{
    ["@servicestack/client"] = ("/js/servicestack-client.mjs", "/js/servicestack-client.min.mjs"),
})

Usage

This lets us reference the @servicestack/client package name in our source code instead of its physical location:

<input type="text" id="txtName">
<div id="result"></div>
<script type="module">
import { JsonServiceClient, $1, on } from '@servicestack/client'
import { Hello } from '/types/mjs'

const client = new JsonServiceClient()
on('#txtName', {
    async keyup(el) {
        const api = await client.api(new Hello({ name:el.target.value }))
        $1('#result').innerHTML = api.response.result
    }
})
</script>

Enable static analysis and intelli-sense

For better IDE intelli-sense during development, save the annotated Typed DTOs to disk with the x dotnet tool:

x mjs

Then reference it instead to enable IDE static analysis when calling Typed APIs from JavaScript:

import { Hello } from '/js/dtos.mjs'
client.api(new Hello({ name }))

To also enable static analysis for @servicestack/client, install the dependency-free library as a dev dependency:

npm install -D @servicestack/client

Where only its TypeScript definitions are used by the IDE during development to enable its type-checking and intelli-sense.

Alternative (without .NET): npx get-dtos

Alternatively API consumers can use npx get-dtos to Add/Update ServiceStack References without needing .NET installed, where any command starting with:

x <lang>

Can be replaced with:

npx get-dtos <lang>

To instead Add / Update ServiceStack references using the npm get-dtos package.

Rich intelli-sense support

Where you'll be able to benefit from rich intelli-sense support in smart IDEs like Rider for both the client library:

As well as your App's server generated DTOs:

Add ServiceStack Reference

A new ServiceStack reference containing the APIs typed DTOs can be added using the BaseUrl of the ServiceStack App, e.g:

x mjs https://localhost:5001

Update ServiceStack References

All existing ServiceStack References can later be updated with:

x mjs

DTO Customization Options

In most cases you'll just use the generated JavaScript DTO's as-is, however you can further customize how the DTOs are generated by overriding the default options.

The header in the generated DTOs show the different options JavaScript types support with their defaults. Default values are shown with the comment prefix of //. To override a value, remove the // and specify the value to the right of the :. Any uncommented value will be sent to the server to override any server defaults.

The DTO comments allows for customizations for how DTOs are generated. The default options that were used to generate the DTOs are repeated in the header comments of the generated DTOs, options that are preceded by a TypeScript comment // are defaults from the server, any uncommented value will be sent to the server to override any server defaults.

/* Options:
Date: 2023-02-08 13:13:28
Version: 6.60
Tip: To override a DTO option, remove "//" prefix before updating
BaseUrl: https://blazor.web-templates.io

//AddServiceStackTypes: True
//AddDocAnnotations: True
//AddDescriptionAsComments: True
//IncludeTypes: 
//ExcludeTypes: 
//DefaultImports: 
*/

We'll go through and cover each of the above options to see how they affect the generated DTO's:

Change Default Server Configuration

The above defaults are also overridable on the ServiceStack Server by modifying the NativeTypesFeature Plugin, e.g:

//Server example in CSharp
var nativeTypes = this.GetPlugin<NativeTypesFeature>();
nativeTypes.MetadataTypesConfig.AddDescriptionAsComments = false;
...

We'll go through and cover each of the above options to see how they affect the generated DTO's:

IncludeTypes

Is used as a Whitelist to specify only the types you would like to have code-generated:

/* Options:
IncludeTypes: Hello, HelloResponse

Will only generate Hello and HelloResponse DTOs:

export class Hello {
    /** @param {‎{name?:string}‎} [init] */
    constructor(init) { Object.assign(this, init) }
    /** @type {string} */
    name;
    getTypeName() { return 'Hello' }
    getMethod() { return 'POST' }
    createResponse() { return new HelloResponse() }
}

export class HelloResponse {
    /** @param {‎{result?:string,responseStatus?:ResponseStatus}‎} [init] */
    constructor(init) { Object.assign(this, init) }
    /** @type {string} */
    result;
    /** @type {ResponseStatus} */
    responseStatus;
}

Include Generic Types

Use .NET's Type Name to include Generic Types, i.e. the Type name separated by the backtick followed by the number of generic arguments, e.g:

IncludeTypes: IReturn`1,MyPair`2

Include Request DTO and its dependent types

You can include a Request DTO and all its dependent types with a .* suffix on the Request DTO, e.g:

/* Options:
IncludeTypes: GetTechnology.*

Which will include the GetTechnology Request DTO, the GetTechnologyResponse Response DTO and all Types that they both reference.

Include All Types within a C# namespace

If your DTOs are grouped into different namespaces they can be all included using the /* suffix, e.g:

/* Options:
IncludeTypes: MyApp.ServiceModel.Admin/*

This will include all DTOs within the MyApp.ServiceModel.Admin C# namespace.

Include All Services in a Tag Group

Services grouped by Tag can be used in the IncludeTypes where tags can be specified using braces in the format {tag} or {tag1,tag2,tag3}, e.g:

/* Options:
IncludeTypes: {web,mobile}

Or individually:

/* Options:
IncludeTypes: {web},{mobile}

ExcludeTypes

Is used as a Blacklist to specify which types you would like excluded from being generated:

/* Options:
ExcludeTypes: GetTechnology,GetTechnologyResponse

Will exclude GetTechnology and GetTechnologyResponse DTOs from being generated.

Cache

When using /types/mjs directly from a script tag, the server will cache the result by default when not running in DebugMode.

This caching process can be disabled if required by using ?cache=false.