In my previous post I talked about code generation of DTOs using TSQL script. In this post I will talk about more general solution. I will show how to easily build DSL processor using Visual Studio T4 templates. I will be using XML based DSL.
Source code used in this post can be found on the Github. Please feel free to use it on your projects.
In general that there are two types of T4 templates:
An approach we are currently using is an XML based DSL file where all the commands are defined:
At the top of the template there are input DSL file paths. The paths are relative to the project root.
For example to generate the most simple Equals method you can use just this:
ToString is also quite simple to generate:
One of the greatest benefits of using T4 Design time templates for code generation is flexibility. On the other hand editing very long T4 template can be quite challenging. So if you are using some better approach then please share your ideas.
Source code used in this post can be found on the Github. Please feel free to use it on your projects.
T4 Text Templates in Visual Studio
There is a lot of information on the web about T4 text templates so I will just mention some key points. In general T4 templates are very similar to traditional ASPX templates. The major difference is that a # sing is used instead of %:
Current time is <#= DateTime.Now.ToShortTimeString() #>
Unfortunately Visual Studio doesn't have a built-in syntax highlighting and code completion. Luckily there are third party extensions available. If you're a ReSharper user then you can use a very nice ForTea extension. It's open source and can be installed using ReSharper Extension Manager.In general that there are two types of T4 templates:
- Run time T4 text template - Used at run time as a name suggests. One of the common uses of the run time templates is to generate a body of an email.
- Design time T4 text template - With design time templates you can generate code files dynamically at design (I would say development) time. Common use of the design time templates is to read some input from another file and produce an output code based on the the input. So in this post I will show how use the design time templates to generate DTOs.
Using T4 Deisgn time templates to generate DTOs
The are a lot of commands and events on my project. At first they were tiny classes with just a set of properties:public class AddDepartmentCommand
{
public string ReferenceNumber { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Then to simplify debugging most commands got ToString overridden. Then to simplify unit testing most commands got IEquatable implemented and so on. So at the moment we have a minimal command similar to below:
public class AddDepartmentCommand : IEquatable<AddDepartmentCommand>
{
public string ReferenceNumber { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public AddDepartmentCommand(string referencenumber = default(string), string name = default(string), string description = default(string))
{
ReferenceNumber = referencenumber;
Name = name;
Description = description;
}
public bool Equals(AddDepartmentCommand other)
{
return
ReferenceNumber == other.ReferenceNumber &&
Name == other.Name &&
Description == other.Description;
}
public override string ToString()
{
return string.Format("", ReferenceNumber, Name, Description);
}
}
Initially we kept related DTOs in a single source file but very soon the file became to long to skim through quickly. The file with just five commands like above would be about 200 lines of code! Putting each DTO in a separate code file is also not the best option. There will be a lot of code files just defining your DTOs making it harder to see and analyse the structure of your project.An approach we are currently using is an XML based DSL file where all the commands are defined:
<?xml version="1.0" encoding="utf-8" ?>
<DepartmentCommands namespace="DotnetT4PocoDsl.Commands">
<AddDepartment ReferenceNumber="string" Name="string" Description="string" />
<UpdateDepartment Id="int" ReferenceNumber="string" Name="string" Description="string" />
<RemoveDepartment Id="int" Reason="string" />
</DepartmentCommands>
This file is processed with the Design time T4 template that generates command classes with aforementioned code structure.
The template
The template is quite long and boring so I'll show just some key parts of it. Full source code can be found on the Github.At the top of the template there are input DSL file paths. The paths are relative to the project root.
//Define your inputs below
var inputs = new[]
{
"Commands.xml",
"Events.xml"
};
Then iterate through the inputs and load corresponding DSL file
var source = Host.ResolvePath(input); //Resolve actual path of the input file
var sourceDoc = XDocument.Load(source);
The rest logic is straight forward. We just iterate through the XML elements and generate corresponding items of our DTO class.For example to generate the most simple Equals method you can use just this:
public bool Equals(<#= classDefElement.Name #> other)
{
return <#= String.Join(" && ", propertyDefAttribs.Select(prop => prop.Name + " == " + "other." + prop.Name)) #>;
}
That will produce
public bool Equals(UpdateDepartmentCommand other)
{
return Id == other.Id && ReferenceNumber == other.ReferenceNumber......
}
However in certain cases you may want the Equals to be smarter and handle complex types. Some of my commands have properties of type IDicitonary and XElement. So let's tweak our Equals method to handle them:<#
var equalityParts = propertyDefAttribs.Select(property =>
{
if (property.Value == "XElement")
return "XNode.DeepEquals(" + property.Name + ", other." + property.Name + ")";
if (property.Value.StartsWith("IDictionary"))
return property.Name + ".SequenceEqual(" + "other." + property.Name + ")";
return property.Name + " == " + "other." + property.Name;
});
#>
public bool Equals(<#= classDefElement.Name #> other)
{
return <#= String.Join(" &&\r\n ", equalityParts) #>;
}
So now this will produce:
public bool Equals(AddDepartmentCommand other)
{
return ReferenceNumber == other.ReferenceNumber &&
Name == other.Name &&
Description == other.Description &&
CustomFields.SequenceEqual(other.CustomFields) &&
XNode.DeepEquals(FieldsMeta, other.FieldsMeta);
}
ToString is also quite simple to generate:
<# var toStringParts = propertyDefAttribs.Select(property => "\"" + property.Name + ": \", " + property.Name); #>
public override string ToString()
{
return String.Concat("<<#= classDefElement.Name #> ", <#= String.Join(", \", \" , ", toStringParts) #>, ">");
}
This gives a clear and simple ToString method:
public override string ToString()
{
return String.Concat("<AddDepartmentCommand ", "ReferenceNumber: ", ReferenceNumber,.........);
}
Regenerating DTOs to reflect input changes
Visual Studio automatically generates the output for you when you save the template, however it knows nothing about your inputs. In order to regenerate the DOTs in case of the input changes you basically have two options: you can open the template and just do Ctrl + S, you can use Run Custom Tool Command (see below)
One of the greatest benefits of using T4 Design time templates for code generation is flexibility. On the other hand editing very long T4 template can be quite challenging. So if you are using some better approach then please share your ideas.
