T4 to help on the example of MVVM

  • Tutorial
 The purpose of the article: to give a couple of ideas for automation, and maybe even a working tool for creating T4-blanks for solving typical problems produced with classes / interfaces in work.
 A bit of specificity: there are very simple, well-algorithmized, everyday tasks — write Model from the DTO class, ViewModel from Model, type Tests, or transforming an object into Json. Tasks that fit perfectly on the T4 mechanisms, but which are long or inconvenient to use. A developer usually stands at a crossroads: complete a task or write a script that may help to complete it.
 Sometimes the script seems too complicated, sometimes the person is stopped by the idea that the script is essentially not a functional part of the project, but a script-at-a-time. And comparing the complexity of the linear problem with the unexplained - the developer often refuses the second way. But the more experience he has, the less he likes linear labor. And the question arises - how to convey the capabilities of T4 for the whole team, not forcing it to delve into, but providing an opportunity to delve into. Moreover, support should be minimal. Conventional T4 scripts needs support (to be used again, it must be carried along with the project developer, at some point it must be “filed” for a specific task). And I want to avoid this.

 Ideally, I suppose the process should consist of pressing a magic button that automatically generates the necessary blank, depending on the scenario you want. And its developer will already be able to quickly finalize to practical applicability in the context of a specific situation. Those. in fact, I propose to work on the shortcomings of the features of using code generation scripts. Quality and time are the main criteria, because all the "magic" can be divided into 3 components:

  • Template generation of a single script.
  • Easy integration into the development process.
  • Validation of the result.

 Now in order:

Part 1. T4 dream generation.



 The easiest way is to show the whole process on a specific task. MVVM, the journey of an entity from a service object to an object of higher levels.

 It often happens that most of the DTO object falls into the model with minimal simple changes. The object of the transport layer does not carry any logic in itself, only data - and the model in minimal execution should at least copy part of its properties, imposing access rights to them. If you apply this to MVVM layers, this translates into transformation:

image

Total 2 transformations. At the output of the first transformation, the solution must be finalized by a person, introducing logic. Then another transformation, creating an already higher rung.
 I think CopyType * + AutoMapper can provide similar functionality .. Aspects can also help (for implementing standard reactions). And in simple cases it is better to use them. But, my personal opinion is, if mapping ceases to be trivial, or debugging turns into Code Hell, you need to simplify objects, working primarily to improve code readability and mechanisms that hide important helpers under the hood here. I repeat, this is IMHO.

 For such simple transformations, you will need the help of the T4 toolbox (thanks to Oleg V. Sych ) and a script in the primitive that is the following:



 Simple, but there are a few " bad " points to use.

 1. In order to use the CodeElement / CodeClass / CodeProperty type, in the place of use you need to leave a link to the EnvDTE library that does not combine with the needs of the project (the library belongs to the VisualStudio environment, it will be very strange to add and clean up the links for yourself).
 2. The source for conversion by this script must be specified explicitly.
 3. In general, the issue of the availability of fields in the generated code is not resolved. But, to tell you the truth, I still didn’t solve this issue, leaving it to the developers, reasoning that it was easier to erase setters than to make an accessibility scheme for each type. This greatly saves time, simplifies the script, but reduces the quality of the "output".

 The first two flaws we can fix by moving on to the second part:

Part 2. Integration into the development process.



 Integration into the development process is painlessly possible with the help of several tools. It would be very nice to use something like a Resharper context menu, which on a particular class gives a drop-down list of possible scripts, but I did not find a way to create such a plug-in, I will be glad if someone tells me. However, creating a plug-in for the studio itself is quite realistic.

 This will require installing the Visual Studio SDK and a little time in order to figure out how to integrate into the Microsoft IDE.
Wizard does most of the work, so I won’t cover this process, but I will focus on 2 important files: .vsct and MenuCommandsPackage.

 In the first one, it is necessary to describe the flat structure of building the menu in xml format, where each button is represented as



 Those. in .vsct we describe the maximum number of possible menu items (hereinafter referred to as slots ) into which we will “embed” conversion files. Because at the stage of installing the plugin, we don’t know how many scripts we actually need, we take them N and make them invisible. And now, in MenuCommandsPackage, according to the number of scripts, we form the commands, put the slots in correspondence with them and display:

for (int index = 0; index < files.Length; index++)
           {
               string file = files[index];
               try
               {
                   var id = new CommandID(GuidList.guidMenuAndCommandsCmdSet, _slots[index]);
                   var command = new DynamicScriptCommand(id, file, GetCurrentClassFileName, OutputCommandString) { Visible = true };
                   mcs.AddCommand(command);
               }
               catch (Exception exception)
               {
                   OutputCommandString(string.Format("Can't add t4 file {0}. Exception: {1}", file, exception.GetType()));
               }
           }


 Thus, each time you start the menu, the number of scripts is inserted that is in the PackageEnvironment.ScriptDirectoryFullPath folder (up to N) and the button that opens this folder.
Scripts are read in realTime so they can be edited and applied immediately.
on practice...
... it is better to have, in addition to constantly used scripts, one template in txt format and one .tt, which you can freely edit at any time and run with any content.

Template is: a


stub in which all supported parameters are pre-set that the plugin can find in the studio on the current open file and provide them as parameters for entering the script (for partial classes.
 All output from the transformation (warnings and errors) is redirected to the studio output, and the result of the transformation itself to the clipboard.In fact, the possibilities allow you to add them to the project, but for the abstract conversion task this may be wrong, although it’s just to throw in separate files zhno worth it. Well, the buffer is my personal opinion, a neutral territory.

 So we got rid of direct referense on EnvDTE within the project, trailer scenario scripts to IDE and provide a minimum-friendly interface interaction.

Part 3. Validation of the result.



 You need to understand that the meaning of this expansion is to do not work, but a blank for work. The quality of this disc can vary greatly depending on the task and the script.
Scripts, over time, will be attached to the task, improving the output, up to the code compiling on the fly. But the minimum condition for use is at least time saving. Those. write the costs yourself without T4, should be more than apply and predict. Well, the plugin is just a small assistant in this matter, which slightly reduces the “price” of T4.

 Sources on Github:
CodeGenerationExtention
 Links: Declarative CodeSnippet Automation's with T4 Templates by Project Generation The Metadata using the T4 CopyType *



- ReSharper feature, just creates a duplicate type.

Also popular now: