
Towards Full Typing with TypeScript, Swashbuckle, and AutoRest
- Tutorial
Introduction
This article discusses how to implement typed messaging between the Back-End based on the ASP.NET Web API and the Front-End created using TypeScript. This is of particular importance when working on large-scale projects, and even more important if the team is distributed. For example, when Back-End and Front-End developers work from different places, in different time zones, and do not always have the opportunity to contact and discuss something. In this case, tracking changes is painstaking work that can be fraught with many subtle errors.
For the author of the article, as for the person who came to the development of Front-End by WPF and Silverlight, the big problem was the lack of static typing. How many times, instead of adding “2” and “2,” added “2” and “Function returning 2”, or passed a DOM object instead of its jQuery wrapper. The advent of static code analyzers, such as JSLint, somewhat eased the problem, but TypeScript became a real breakthrough, especially in team development.

The essence of the problem
TypeScript is a language that allows static typing to be achieved, although some call it “illusion” ( habrahabr.ru/post/258957 , habrahabr.ru/post/272055 ). Curiously, critics emphasize working with Back-End as a typically insecure scenario.
However, the essence of the problem lies in the fact that when writing Front-End applications in JavaScript before, and in TypeScript at the moment, we do not have such tools for working with metadata and auto-generation of client code, as we had in WCF.
Metadata
If we turn to the experience of WPF + WCF, then everything is pretty good in this regard. Of course, data, generally speaking, always travels in untyped form, but when sent, it remains typed almost to the very end, and only immediately before being sent to the other side are serialized into a string or a binary stream. On the other side, they, again, end up in some kind of client who turns them into typed ones. In order not to write with the hands of such a client and not try to catch multiple errors, metadata exists. In the .NET world, in 90% of cases no work is required at all, either to generate them or to process them. You just write your service, do not forget to add the appropriate endpoint, and get auto-generated metadata. Also, in one click you generate a client and as a result you get an exchange of typed messages.
When developing a Single Page Application in JavaScript / TypeScript, the WCF is replaced by the Web API. At one time, it was somewhat surprising why there was no way to generate metadata for the Web API out of the box (not to consider help-pages metadata). Apparently the answer is that the main recipient of data from the Web API was JavaScript code, in which typing does not make sense. However, now we have not JavaScript but TypeScript, and the desire to operate on typed data is again relevant.
A very popular metadata format now is OpenAPI / Swagger. Not surprisingly, there are opportunities to generate metadata and documentation in this format.
Next, we will demonstrate the process of organizing typed interaction.
In short, we will follow these steps:
- Connect and configure the Swashbuckle library
- Generate documentation / metadata
- We will verify the convenience of storing the generated file in the version control system
- Connect AutoRest
- We generate client models
- We will test them in business.
Swashbuckle
github.com/domaindrivendev/Swashbuckle
First we want to generate metadata.
So, suppose we have a Web API, and in it is a controller that is responsible for working with employees.
///
/// Gets all employees.
///
///
/// Gets the list of all employees.
///
///
/// The list of employees.
///
[Route("api/employees")]
[HttpGet]
public Employee[] GetEmployees()
{
return new[]
{
new Employee { Id = 1, Name = "John Doe" },
new Employee { Id = 2, Name = "Jane Doe" }
};
}
As you can see, an array of typed objects of type Employee is returned. By launching our project, we can request a list of employees:
http: // localhost: 1234 / api / employees
Let's now connect the Swashbuckle library. There are two Swashbuckle.Core and Swashbuckle packages in NuGet. The difference between the two is that the first is the kernel and contains all the code that does the magic, and the second, in turn, is just the add-on that installs a bootstrapper that configures the kernel.
This is written in the documentation github.com/domaindrivendev/Swashbuckle#iis-hosted.
We prefer installing Core and writing the configuration code ourselves, as then it’s more convenient to reuse it.
Let's install it
PM> Install-Package Swashbuckle.Core
register using WebActivatorEx
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(FullyTypedExample.WebApi.SwaggerConfig), "RegisterGlobal")]
and also write a configuration code
///
/// Configures Swagger.
///
///
/// The Swagger configuration.
///
public static void ConfigureSwagger(SwaggerDocsConfig config)
{
config.SingleApiVersion("v1", "FullyTypedExample.WebApi");
config.IncludeXmlComments(GetXmlCommentsPathForControllers());
config.IncludeXmlComments(GetXmlCommentsPathForModels());
config.GroupActionsBy(apiDescription => apiDescription.ActionDescriptor.ControllerDescriptor.ControllerName);
config.OrderActionGroupsBy(Comparer.Default);
config.PrettyPrint();
}
Everything is pretty simple here: first we set the version and title of our API. Next, we say that you need to enable xml comments for controllers and models. We adjust the order and grouping of the action inside the swagger-document. I would also like to mention the PrettyPrint option. It enables JSON formatting for a swagger document. This option is useful in order to store documentation in the version control system in the future and easily view its changes using any diff viewer.
Now you can start the project and see the Swagger interface.
http: // localhost: 1234 / swagger

Next you can look at the swagger document itself in the form of JSON.
http: // localhost: 1234 / swagger / docs / v1
Now we need to add the generated documentation to the version control system. Since Swashbuckle uses the Microsoft IApiExplorer under the hood, in order to generate a swagger file, you will definitely need to run the Web API (more on this here github.com/domaindrivendev/Swashbuckle/issues/559 ). That is, every time you want to generate documentation, you will have to run the Web API and manually copy swagger / docs to a file. Of course, I want something more automated.
We solved this by launching the Web API as a self-hosted application, sending a non-endpoint swagger request and writing the response to a file. It was just useful to reuse the Swashbuckle configuration code. It all looks something like this:
///
/// Generate Swagger JSON document.
///
///
/// The file path where to write the generated document.
///
private static void GenerateSwaggerJson(string filePath)
{
// Start OWIN host
using (TestServer server = TestServer.Create())
{
HttpResponseMessage response = server.CreateRequest("/swagger/docs/v1").GetAsync().Result;
string result = response.Content.ReadAsStringAsync().Result;
string path = Path.GetFullPath(filePath);
File.WriteAllText(path, result);
}
}
Let's run it all now:
nuget.exe restore "..\FullyTypedExample.sln"
"C:\Program Files (x86)\MSBuild\12.0\bin\MSBuild.exe" "..\FullyTypedExample.WebApi.SelfHosted\FullyTypedExample.WebApi.SelfHosted.proj" /v:minimal
"..\FullyTypedExample.WebApi.SelfHosted\bin\Debug\FullyTypedExample.WebApi.SelfHosted.exe" --swagger "swagger.json"
In total, we received a swagger document in the form of a JSON file and put it in the version control system. Now Front-End developers from our distributed team can easily track changes in endpoints. Let's see how it looks.
Suppose we added a new action to get an employee by his identifier.
///
/// Gets employee by id.
///
///
/// The employee id.
///
///
/// Gets the employee by specified id.
///
///
/// The .
///
[Route("api/employees/{employeeId:int}")]
public Employee GetEmployeeById(int employeeId)
{
return this.GetEmployees().SingleOrDefault(x => x.Id == employeeId);
}
And regenerated swagger.json. Let's see what has changed.

As you can see, documentation has appeared for this action, which can be easily seen using the diff viewer. Thanks to the PrettyPrint option, it is formatted and easy to read.
AutoRest
So, the first part of our task is completed - we have metadata. How now to generate the client part, i.e. types of data (received from the server) on the client side?
I must say that you can generate the code itself to request a Web API, it’s just a little more complicated and requires more time-consuming work on configuring code generators or writing your own. Also, a lot depends on which libraries (be it jQuery, SuperAgent or even the new experimental Fetch API developer.mozilla.org/en/docs/Web/API/Fetch_API ) and approaches (Promises, Rx, etc.) you use in your client code.
The following options are available for code generation:
1. Swagger Code Generator github.com/swagger-api/swagger-codegen
The official tool from the Swagger team, written in Java and requires the appropriate infrastructure. It can also run in Docker. True, the generation of JavaScript, and especially TypeScript, is missing in it. Although if you need to generate code, for example, in Java, this is your choice. He did not suit us for obvious reasons.
2. Swagger JS library github.com/swagger-api/swagger-js
Also official tool from the Swagger team. Already warmer. It is written in JS and generates JS code accordingly. Installed via npm or bower. Infrastructure is suitable for us, but alas, this type generation is not there.
3. Swagger to JS & Typescript Codegen github.com/wcandillon/swagger-js-codegen
The project was published a little later than we began to develop this approach. Perhaps in the near future this will be the most suitable solution.
4. Write your
5. AutoRest github.com/Azure/autorest
Lastly, AutoRest is from the Azure Microsoft team. Now the current version is 0.15.0, and frankly it is not clear whether they consider it to be a full-fledged release or not, but Pre marks, as in the previous ones, are not observed. In general, everything is simple here, we installed and immediately generated * .d.ts files, which we needed.
So, let's go through the final leg of our journey with this tool.
We connect AutoRest through NuGet:
PM> Install-Package AutoRest
The package is not put into any specific project, a link to it is added for the entire solution.
The package has a console application AutoRest.exe, which, in fact, performs the generation. To run we use the following script
nuget.exe restore "..\FullyTypedExample.sln"
"..\packages\AutoRest.0.15.0\tools\AutoRest.exe" -Input "swagger.json" -CodeGenerator NodeJS
move "Generated\models\index.d.ts" "..\FullyTypedExample.HtmlApp\models.d.ts"
At the input we submit our previously generated swagger.json, and at the output we get models \ index.d.ts - a file with models. Copy it to the client project.
Now in TypeScript we have the following model description:
/**
* @class
* Initializes a new instance of the Employee class.
* @constructor
* Represents the employee.
* @member {number} id Gets or sets the employee identifier.
*
* @member {string} name Gets or sets the employee name.
*
*/
export interface Employee {
id: number;
name: string;
}
Let's try it in action:
public makeRequest() {
this.repository.getEmployees()
.then((employees) => {
// Generate html using tempalte string
this.table.innerHTML = employees.reduce((acc, x) => {
return `${acc}${x.id}${x.name}`;
}, '');
});
}
Here we refer to the id and name model fields. We intentionally lowered the implementation of the request to the server, because it, as we have already said, may depend on the chosen libraries and approaches.
If we try to access an age field that does not exist, our TS code will not compile. If the field that we accessed earlier disappears in the API, our code will not compile again. If new fields are added, we will see it right away, using the same diff. In addition, we automatically receive JSDoc documentation based on metadata. In general, all the charms of static typing are obvious.
ResponseType
Interestingly, if necessary, for documentation, you can specify a different type than the one that is returned. For example, this can be useful if you have legacy code working with untyped DataSets; either if you return IHttpActionResult from the controllers. Without affecting the implementation of methods, we can mark them with the ResponseType attribute and develop special types
///
/// Gets all departments.
///
///
/// Gets the list of all departments.
///
///
/// The list of departments.
///
[Route("api/departments")]
[HttpGet]
[ResponseType(typeof(DepartmentsResponse))]
public DataSet GetDepartments()
{
var dataTable = new DataTable("Departments");
dataTable.Columns.Add("Id", typeof(int));
dataTable.Columns.Add("Name", typeof(string));
dataTable.Rows.Add(1, "IT");
dataTable.Rows.Add(2, "Sales");
var dataSet = new DataSet();
dataSet.Tables.Add(dataTable);
return dataSet;
}
to get typed models on the client side
/**
* @class
* Initializes a new instance of the Department class.
* @constructor
* Represents the department.
* @member {number} id Gets or sets the department identifier.
*
* @member {string} name Gets or sets the department name.
*
*/
export interface Department {
id: number;
name: string;
}
Problems
Firstly, the size of the models.d.ts file increases over time. We have not yet dealt with splitting it into several subfiles, but this will undoubtedly need to be done.
There may also be a problem with incorrect generation of field names if non-standard notation is used, for example, if underscores are used. The LAST_NAME field from C # code is generated in lagT_NAME in Swagger and in lasTNAME in TypeScrpt.
///
/// Gets or sets the last name.
///
[Required]
// ReSharper disable once InconsistentNaming
public string LAST_NAME { get; set; }
"lasT_NAME": {
"description": "Gets or sets the last name.",
"type": "string"
}
export interface Employee {
id: number;
name: string;
firstName: string;
lasTNAME: string;
}
Note that most of the minor issues are easily resolved using the configuration and are not worth a separate mention.
Conclusion
This approach allowed us to organize a typed message exchange. At the same time, he provided typing of client models, reduced the likelihood of discrepancies between client and server code, and made tracking changes in APIs and models easier. A nice bonus was the convenient manual testing of the API with a built-in REST client and the ability to generate payload on the fly according to the scheme. Using this approach has also helped improve the interaction of Back-End and Front-End developers.
A working example can be seen here.
github.com/EBTRussia/fully-typed-example