The purpose of the article: to give a couple of ideas for automation, and maybe even a working tool for creating T4-pigs for solving typical problems produced with classes / interfaces in work.
It’s a little concrete: there are some very simple, well-algorithmized, everyday tasks - from the DTO-class to write Model, from Model - ViewModel, sample Tests or object transformation to Json. Tasks that fall perfectly on the mechanisms of T4, but which are long or inconvenient to use. The developer usually stands at a crossroads: complete the task or write a script, which,
perhaps , will help to accomplish it.
Sometimes the script seems too complicated, sometimes the person is stopped by the idea that the script is in fact not a functional part of the project, but a script one-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 work. And the question arises - how to convey the possibilities of T4 for the whole team, not
forcing it to penetrate, but
providing an opportunity to penetrate. At the same time, support should be minimal. Regular T4 scripts need support (to be used again, it should be dragged along with the developer for the projects, at some point should be “filed” for a specific task). And this one wants to avoid.
Ideally, I suppose, the process should consist of pressing a magic button that generates the necessary blank automatically, depending on the required scenario. And its developer can quickly modify it to practical applicability in the context of a specific situation. Those. in fact, I propose to work on the disadvantages of using code generation scripts. Quality and time as the main criteria, because all the
"magic" can be divided into 3 components:
- Template generation of a separate 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 a top level object.
Often this happens, most of the DTO object falls into the model with minimally simple changes. The object of the transport layer does not carry any logic, only data - and the model in the minimum version must at least copy some of its properties, imposing access rights to them. If we apply this to the MVVM layers, this is expressed in the transformation:

Total 2 transformations. At the output of the first transformation, the decision must be refined by man, introducing logic. Then another transformation, creating a higher step.
I think
CopyType * +
AutoMapper can provide similar functionality. Aspects can help (for the implementation of standard reactions). And in simple cases it is better to use them. But, my personal opinion, if mapping ceases to be trivial, or debugging turns into Code Hell - it is necessary to simplify objects, working primarily on improving the readability of the code and the mechanisms that hide important functionality under the hood here are bad helpers. Again, this is IMHO.
For such simple transformations, you need the help of the
T4 toolbox (thanks to
Oleg V. Sych ) and the script, which in the primitive is the following:

Simple, but there are a few "
bad " moments to use.
1. To use the CodeElement / CodeClass / CodeProperty type, you should leave a reference to the EnvDTE library at the place of use, which is not combined with the project's needs (the library belongs to the VisualStudio environment, it will be very strange to add and clean up the links).
2. The source for conversion by this script must be specified explicitly.
3. In general, the issue with the availability of fields in the generated code is not solved. But, to tell the truth, I never decided this question, having given it to the developers, judging that it was easier to erase setters than to make an accessibility scheme for each type. This saves a lot of time, simplifies the script, but reduces the quality of the output.
We can fix the first two shortcomings by going to the second part:
Part 2. Integration into the development process.
You can integrate into the development process painlessly with the help of several tools. It would be very nice to use something like the Resharper context menu, which on a specific class gives a drop-down list of possible scripts, but I have not found a way to create such a plugin, I will be glad if someone tells. However, the plug-in to the studio itself is quite realistic.
To do this, you will need to install the Visual Studio SDK and a little time in order to figure out how to integrate into the IDE from Microsoft.
The Wizard does most of the work, so I’ll not cover this process, but I’ll focus on 2 important files: .vsct and MenuCommandsPackage.
In the first one, it is necessary to describe the flat menu building structure in xml format, where each button is represented as:
<Button guid="guidMenuAndCommandsCmdSet" id="cmdCommand9" priority="0x309" type="Button"> <Parent guid="guidMenuAndCommandsCmdSet" id="MyMenuGroup"/> <CommandFlag>DynamicVisibility</CommandFlag> <CommandFlag>DefaultInvisible</CommandFlag> <CommandFlag>TextChanges</CommandFlag> <Strings> <ButtonText>T4 Command</ButtonText> </Strings> </Button>
Those. In .vsct, we describe the maximum number of possible menu items (hereinafter referred to as
slots ) into which we will “insert” the transformation files. Since At the stage of installing the plug-in, we do not know how many scripts we actually need, take them N and make them invisible. And now in the MenuCommandsPackage by the number of scripts we form teams, assign slots to 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, in the Menu, at each launch, the number of scripts is inserted, which is in the Package Environment.ScriptDirectoryFullPath folder (up to N) and the button that opens this folder.
The scripts are read in realTime so they can be edited and immediately applied.
on practice...... it is better to have in addition to the constantly used scripts one template in txt format and one .tt, which can be freely edited at any time and run with any content.
Template is:

a stub in which all supported parameters that a plugin in the studio can find on the current open file and provide them as input parameters to the script (for partial classes) are set in advance.
The entire output from the transformation (warnings and errors) is redirected to the studio output, and the result of the transformation itself is transferred to the clipboard. In fact, the possibilities make it possible to add them to the project, but for an abstract conversion task this may not be appropriate, although it is probably worth throwing into separate files. Well, the buffer, my personal opinion, is just a neutral territory.
Thus, we got rid of the direct referense on EnvDTE inside the project, hooking up scripted scripts to the IDE and provided a minimally convenient interaction interface.
Part 3. Validation of the result.
It should be understood that the meaning of this extension 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 stitch to the task, improving the output, up to the code compiled with the move. But the minimum condition of use is at least time savings. Those. costs to write yourself without T4, should be more than applied and applied. Well, the plugin is only a small assistant in this business, which slightly reduces the
“price” of T4.
Sources on Github:
CodeGenerationExtentionUseful links:
Declarative Codesnippet Automation with T4 TemplatesProject Metadata Generation using T4CopyType * is a ReSharper feature. It simply creates a duplicate of the type.