I wrote a small but useful library for TypeScript and ASP.NET MVC lovers. I would very much like to tell you about it - maybe some developer on the aforementioned combination of technologies (and possibly the whole team) will make life much easier. There is no time to write full documentation in English. It generally needs to be approached intelligently, with feeling, plainly and constellation. So I decided to write an article on Habrahabr, and right now, under the cut, I will do a brief overview and show you what magic you can do with this thing. Welcome.
Lyrical introduction
In general, the idea of ​​tight integration of the Back-End and Front-End on the .NET stack, which in turn even resulted in an attempt to write a whole translator from C # in JavaScript, has long and firmly aggravated me. I won't say that no results were achieved - my translator successfully translated several C # classes into JavaScript and eventually went into hibernation and rethinking the architecture. Someday I will come back to him. Required.
But, nevertheless, the current tasks in the projects had to somehow be solved. And the current tasks in almost any web project are, on the whole, quite typical and, I’m not afraid of that word, boring. Now, I, with your permission, abstract from any complicated, but long-understood matters, such as using Automapper, picking up an IoC container and rustling in the database using EF / NH / something, and switch closer to the frontend. So - at the junction of the backend and the frontend, there are also many boring and typical tasks (sarcasm). Specifically, server requests for JSON with data, their display and the performance of all sorts of different AJAX operations. Reinforced.Typings (and that is how I called my little help) will bring a bit of fun to this realm of dejection, simplifying typical tasks, getting rid of the template code and a bit more consistency.
Since Microsoft gave us TypeScript, writing client-side JavaScript has become much more comfortable. TypeScript brought a sense of typability and precompilability to where it was missing. If you have not tried it yet, be sure to try it (this is not an advertisement, no). You can, of course, argue a lot on the question of “to be or not to be” TypeScript in your particular project, but let's skip the discussion and move on “- Closer to the body! - as Guy de Maupassant said”.
')
Practical example
So, let's consider a simple but fairly common example - you need to make a request to the server, get information about the order and display it in some way on a page in the browser.
What do we usually do to solve this problem? Right. We make the POCO order model and the controller method, which will return its instance wrapped in JSON. Here they are, our heroes (as you understand, I will remove the extra code to save space):
Modelkapublic class OrderViewModel { public string ItemName { get; set; } public int Quantity { get; set; } public decimal Subtotal { get; set; } public bool IsPaid { get; set; } public string ClientName { get; set; } public string Address { get; set; } }
Controller method public ActionResult GetOrder(int orderId) { var orderVm = new OrderViewModel() {
Here everything is more or less clear and comments, I think, are superfluous. Let's switch to the client. To be extremely clear, I will use jQuery for ajax requests, but if necessary, you can replace it with something of your own. As before, I omit the unnecessary glue code, as well as creating a view, a TypeScript file, connecting it to the village, installing jQuery from NuGet - you can do this without me. I emphasize the very essence (once again I remind you that this code is in TypeScript):
TypeScript code private btnRequest_click() { $.ajax({ url: '/Home/GetOrder?orderId=10', success:this.handleResponse }); } private handleResponse(data: any) { var text = `${data.ClientName}, ${data.Address} (${data.ItemName}, ${data.Quantity})`; $('#divInfo').text(text); }
Everything is beautiful here. Except that fundamentally from JavaScript, this construct is no different. We get a piece of JSON from the server, in which some object and we, counting on the fact that it has the fields ClientName, Address and others - output it to a div. It does not sound very stable. If any unfortunate junior removes from the ViewModel and C # code, say, the ClientName field (or renames it for junior refactoring), then all the places on the frontend that use this design will turn into detonators and wait the arrival of the tester. Well, or end user-a - here it is someone as lucky. What to do? The answer is obvious - since we use TypeScript, then we can write the tapping for this particular ViewModel and rewrite the code like this:
private handleResponse(data: IOrderViewModel) { var text = `${data.ClientName}, ${data.Address} (${data.ItemName}, ${data.Quantity})`; $('#divInfo').text(text); }
Yes, now it has become somewhat more comfortable for us - we have insured against access to an undeclared field. But the situation with the junior renaming the field personally does not allow me to sleep. Yes, and writing taiping for all ViewModel-her ... hands ... at night ... And if there are hundreds of them in the project? And if thousands? Perspective, frankly, so-so.
This is where Reinforced.Typings comes into play and the solution of the problem begins radically in a different way. So, open the PM-console (well, or anyone is comfortable - you can do it through the graphical interface) and set:
PM > Install-Package Reinforced.Typings
We notice in the project root a new Reinforced.Typings.settings.xml file. It is documented in sufficient detail and I do not see any point in rewriting everything stated in it here (unless of course
someone from the audience is not so bad with English), giving it to the habra people. I will change just one parameter - this is the path to the file where the generated tapping will go. In my project, he is like this:
<RtTargetFile>$(ProjectDir)Scripts\app\Server.ts</RtTargetFile>
After that, I go to the code of the model and add just two lines of code - using Reinforced.Typings.Attributes and the
[TsInterface] attribute over the model itself. Like this:
using Reinforced.Typings.Attributes; [TsInterface] public class OrderViewModel {
After that, I rebuild the project (I rebuild it) and manually add it to Scripts \ app \ generated according to the Server.ts configuration. He is already in the specified folder - just not added to the project. Let's open Server.ts and see what is in it:
You see? Wonderful. Now the task of writing taipings for the ViewModel to her entire project does not seem such a terrible thing, does it? And the deletion-renaming of properties ViewModel is no longer a tragedy: at the next build of the project, the typings will be regenerated and the TypeScript code, which is tied to them, will simply cease to be assembled, which you can see for yourself.
I think that a practical demonstration of the main possibility can be completed on this, leaving more complicated examples for the following articles and moving on to a dramatic conclusion.
Dramatic conclusion and a little bit about this and that
Reinforced.Typings actually supports a lot of things. Here is a short list:
- Automatic taiping of delegates, heirs of IEnumerable and IDictionary (if you use them as properties)
- Taipings for enums and classes (although it cannot automatically translate the body of methods into TypeScript. But this can be done independently - see below)
- Tiping with complex types - Reinforced.Typings understands that you are using a different exported type in the class and automatically puts in the place the full-qualified type name used. Otherwise, tactfully use any
- You can scatter the generated code across different files (class-per-file) using the appropriate configuration parameter
- You can add /// <reference ...> to the generated files using the [TsReference] assembly attribute. And in the case of class-per-file, reference is automatically added to neighboring files.
- You can generate .d.ts-files instead of the usual .ts-code (there are some differences in the syntax)
- Cherry on the cake - each attribute has a CodeGeneratorType property, in which you can specify the heir type from Reinforced.Typings.Generators.ITsCodeGenerator <> (alternatively, inherit from the existing generator) and make your own generation of template TypeScript-code for everything, anything . In this way, you can get to the automatic creation of knockout ViewModels for it directly from the server ViewModel code. In the project at my current place of work, I overloaded the code generator for the controller actions and thus generated js-ny glue code for many methods, calling the corresponding controller method with the specified parameters. These methods return q-shny promise (just because I love Q.js). This is what I will tell in the next post.
Of the minuses:
Automatically generate the body methods of classes Reinforced.Typings can not - it works through Reflection . Well, I would also like to note some problems in the situation when server entities represent the correct TypeScript code, but the already generated code contains a semantic error (for example, a field has been deleted). Due to the features of TypeScript assembly (it is going to be the very first in the entire project), you will not be able to rebuild the project and generate correct taipings that will correct the error until you correct the error manually. But I'm working on it. The magic of MSBuild works wonders.
Also, according to the project, as mentioned above, there is very little documentation (this article, yes readme on github). BUT! XMLDOC is very detailed and the settings file is commented. So, I think for the first time should be enough. And there I’m already enrolling a student technician and doing a normal
book Reinforced.Typings Best Practices documentation in a wiki format.
In general, that's all for today. If there are any questions - write in the comments. I will try to answer.
Reference to the project:
Reinforced.Typings on GithubReinforced.Typings on NuGetFull code of the considered exampleUPD :
The second article on Reinforced.Typings, which provides much more details