Introduction
This article discusses how to implement typed messaging between the Back-End based on ASP.NET Web API and Front-End created using TypeScript. This is of particular importance when working on bulk projects, and more importantly, 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, change tracking is a painstaking job that can be fraught with many subtle errors.
For the author of the article, as for the person who came to the development of the 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 emergence of static code analyzers, such as JSLint, somewhat eased the problem, but TypeScript was a real breakthrough, especially in team development.

')
The essence of the problem
TypeScript is a language that allows you to achieve static typing, although some people call it “illusion” (
habrahabr.ru/post/258957 ,
habrahabr.ru/post/272055 ). It is curious that critics emphasize working with the Back-End as a
typically non-secure 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 present time, we do not have such tools for working with metadata and auto-generating client code, as we once had in WCF.
Metadata
If we look at the experience of WPF + WCF, then everything is quite good in this respect. Of course, the data, generally speaking, always travel in an untyped form, but when sent, it remains typed almost to the very end, and only immediately before sending to the other side are serialized into a string or binary stream. On the other side, again, they end up in some kind of customer 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, and there is metadata. In the .NET world, in 90% of cases, no work is required at all, neither for their generation, nor for their processing. 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 Single Page Application on JavaScript / TypeScript, the Web API replaces WCF. At one time, it was somewhat surprising why there is no way to generate metadata for the Web API out of the box (not to consider the same help-pages as metadata). Apparently the answer is that the main recipient of the data from the Web API was JavaScript code, in which typing does not make sense. However, we now have not JavaScript but TypeScript, and the desire to operate with typed data again becomes relevant.
A very popular metadata format is now 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 complete the following steps:
- Let's connect and configure the Swashbuckle library
- Generate documentation / metadata
- Make sure that the generated file is stored in the version control system
- Connect AutoRest
- Generate client models
- Let's test them in action.
Swashbuckle
github.com/domaindrivendev/SwashbuckleFirst we want to generate metadata.
So, suppose we have a Web API, and in it is a controller responsible for working with employees.
As you can see, an array of typed objects of type Employee is returned. Having launched our project, we can request a list of employees:
http: // localhost: 1234 / api / employeesLet's connect the Swashbuckle library now. 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 the bootstrapper that configures the kernel.
This is written in the documentation
github.com/domaindrivendev/Swashbuckle#iis-hostedWe prefer installing Core and writing the configuration code ourselves, since it is then more convenient to reuse.
Let's install it
PM> Install-Package Swashbuckle.Core
register with WebActivatorEx
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(FullyTypedExample.WebApi.SwaggerConfig), "RegisterGlobal")]
and also write the configuration code
Everything is quite simple here: first we install the version and title of our API. Further we say that it is necessary to include xml-comments for controllers and models. Adjust the order and grouping of action inside the swagger document. Separately, I would like to mention the PrettyPrint option. It includes JSON formatting for the swagger document. This option is useful in order to further store the documentation in the version control system and easily view its changes using any diff viewer.
Now you can run the project and see the Swagger interface.
http: // localhost: 1234 / swagger
Nearby you can look at the swagger document itself in the form of JSON.
http: // localhost: 1234 / swagger / docs / v1Now we need to add the generated documentation to the version control system. Since Swashbuckle uses IApiExplorer under the hood, in order to generate a swagger file you will have to launch the Web API (for more information, see
github.com/domaindrivendev/Swashbuckle/issues/559 ). That is, each time you want to generate documentation, you have to run the Web API and copy swagger / docs to a file manually. Of course, I want something more automated.
We solved this by launching the Web API in the form of a self-hosted application, sending the request to the endpoint swagger and not writing the response to the file. This is where it was useful to reuse the Swashbuckle configuration code. It looks like this:
Let's run all this 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"
So we got the swagger document as a JSON file and put it into the version control system. Now Front-End developers from our distributed team can easily track changes in endpoint s. Let's see what it looks like.
Suppose we added a new action to get an employee by his ID.
And re-generated swagger.json. Let's see what has changed

As you can see, for this action documentation appeared, 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. data types (obtained from the server) on the client side?
I must say that you can generate the code itself for requesting 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.
For code generation, the following options exist:
1. Swagger Code Generator github.com/swagger-api/swagger-codegenThe 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 themes TypeScript is absent. Although if you need to generate code, for example, in Java - this is your choice. For us, he did not fit for obvious reasons.
2. Swagger JS library github.com/swagger-api/swagger-jsAlso an official tool from the Swagger team. Already warmer. Written in JS and generates JS code, respectively. Installed via npm or bower. The infrastructure is suitable for us, but alas, there is not the type generation itself.
3. Swagger to JS & Typescript Codegen github.com/wcandillon/swagger-js-codegenThe project was published a little later than we began to develop this approach. Perhaps in the near future this will be the most appropriate solution.
4. Write your bike code generator. In general, why not? But for a start, we decided that we would try AutoRest, and if it does not take off, or does not suit us with opportunities, we’ll write our own, with blackjack and ... Well, you understand.
5.
AutoRest github.com/Azure/autorestAnd finally, Microsoft's Azure AutoRest team. Now the current version is 0.15.0, and frankly it is not clear if this is considered to be a full release from them or not, but the Pre mark, like the previous ones, is not observed. In general, everything is simple, we installed and immediately generated * .d.ts files that we needed.
So let's go through the final part of our journey with this tool.
We connect AutoRest via NuGet:
PM> Install-Package AutoRest
The package is not placed in any particular project, a link to it is added for the entire solution.
<?xml version="1.0" encoding="utf-8"?> <packages> <package id="AutoRest" version="0.15.0" /> </packages>
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 entrance we submit our previously generated swagger.json, and at the output we get models \ index.d.ts - a file with models. We copy it in the client project.
Now in TypeScript we have the following model description:
export interface Employee { id: number; name: string; }
Let's try it out in the case:
public makeRequest() { this.repository.getEmployees() .then((employees) => {
Here we refer to the id and name model fields. We intentionally lowered the request to the server because as we have said, it may depend on the libraries and approaches chosen.
If we try to access the age field, which does not exist, our TS code will not compile. If the field we addressed earlier disappears in the API, our code will not compile again. If new fields are added, we will immediately see it using the same diff. In addition, we automatically receive JSDoc documentation based on metadata. In general, all the delights of static typing is obvious.
ResponseType
Interestingly, if necessary, for documentation, you can specify a different type than the one that is returned. For example, this may be useful if you have legacy code that works with untyped DataSets; or if you return the IHttpActionResult from the controllers. Without affecting the implementation of the methods, we can mark them with the ResponseType attribute and develop special types
to get typed models on the client side
export interface Department { id: number; name: string; }
Problems
First, the size of the models.d.ts file increases over time. While we have not engaged in splitting it into several subfiles, but it certainly will need to be done.
There may also be a problem with the incorrect generation of field names if non-standard notation is used, for example, if underscores are used. The LAST_NAME field from the C # code is generated in Swagger as lasT_NAME, and in TypeScrpt as lasTNAME.
"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 minor problems are easily solved using the configuration and are not worth special mention.
Conclusion
This approach allowed us to organize the exchange of typed messages. At the same time, he provided for the typing of client models, reduced the likelihood of discrepancies between client and server code, made it easier to track changes in the API and models. A nice bonus was the convenient manual testing API with a built-in REST client and the ability to generate payload on the fly according to the scheme. Using this approach also helped to improve the interaction of Back-End and Front-End developers.
A working example can be seen here.
github.com/EBTRussia/fully-typed-example