DTO Builders with T4

T4 template that you can add to any Visual Studio project to generate DTO Builder classes automatically from the DTO classes. It generates builders with fluent extensible API.

It’s in GitHub!

You can find my T4 Builder generator from the GitHub. The solution contains some sample DTOs, unit tests (usage example of the generated code) and the Builder.tt itself.

What are DTOs and Builders?

Before we dive into generating builders for DTOs, we must first understand what is a DTO and how does the builder pattern work.

DTO is an acronym for Data Transfer Object. DTO’s are simple objects that contain only data. Think them as data structures. Usually DTOs are used at application boundaries to transmit data between layers and applications. For more detailed explanation, please read Martin Fowler’s definition of the pattern. Here is an example of DTO class.

public class PersonDto
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public Color FavoriteColor { get; set; }
}

Builder pattern enables creating objects step-by-step fashion. Builder object provides a fluent API to construct DTOs with specific values. Below is an example what it looks like to use a builder to create the PersonDto object mentioned above.

var person = Build.PersonDto.WithFirstName("Thomas")
                            .WithAge(33)
                            .WithFavoriteColor(Color.Red)
                            .Build();

Features

T4 Builder generator generates DTO builders for all DTO classes it finds from the solution. How it searches those DTOs is discussed later in the Usage part of this post. Let’s go through what you can do with the generated builders and how they can be extended.

Builder API

Generated builders provide a fluent API to build DTOs. You already saw an example of this API in the previous chapter. Let’s see what the code above contains. First, there is a static class Build which is an entry point to our builders. This class is not necessary and not part of the pattern, but it makes using builders more coder friendly. Everytime you type Build., Visual Studio intellisense will list all the types that can be built. Next, you’ll notice the With methods. Those correspond the DTO properties and can be used to set values. Last, there is a call to Build() method which as a result returns the built PersonDto object.

Nested DTOs

It’s not unsual to have DTOs that contain other DTOs. Generated builders support this scenario with a fluent API to build complete DTO trees. There are few different methods that builders provide. Let’s go them through one by one.

You can create nested DTO separately from the root DTO like this:

var address = Build.AddressDto.WithStreet("Address")
                              .WithZipCode("2424")
                              .WithCity("New York")
                              .Build();

var person = Build.PersonDto.WithAddress(address).Build();

Assert.AreEqual("New York", person.Address.City);

There is also support to build nested DTOs in place like this:

var person = Build.PersonDto.WithAddress(a => a.WithStreet("Address")
                                               .WithZipCode("2424")
                                               .WithCity("New York"))
                            .Build();

Assert.AreEqual("New York", person.Address.City);

Nested list can be added like this:

var p1 = Build.PersonDto.WithFirstName("First").Build();
var p2 = Build.PersonDto.WithFirstName("Second").Build();
var members = new List<PersonDto> { p1, p2 };

var group = Build.GroupDto.WithMembers(members).Build();

Or you can use params version of the method like this:

var p1 = Build.PersonDto.WithFirstName("First").Build();
var p2 = Build.PersonDto.WithFirstName("Second").Build();

var group = Build.GroupDto.WithMembers(p1, p2).Build();

Or you can use in place building just like with single nested DTOs.

var group = Build.GroupDto.WithMembers(p1 => p1.WithFirstName("First"),
                                       p2 => p2.WithFirstName("Second"))
                          .Build();

Defaults

Generated builders are usually used in test code to build input for the system under test. The input should be valid by default and each test case should only set the values that are meaningful in the context of that specific test. To enable this, there is an extension point to provide default values for each buildable DTO type. In other words Build.Person.Build() should result to a Person object where all properties are populated with some meaningful values. For example FirstName="John", LastName="Doe" and so on.

You can do this by creating a partial class for the generated builder and then overriding the Defaults() method. Below is an example of our PersonDto class defaults.

public partial class PersonDtoBuilder
{
   protected override void Defaults()
   {
       dto.FirstName = "Thomas";
       dto.LastName = "Anderson";
       dto.Age = 33;
       dto.FavoriteColor = Color.Green;
   }
}

Now if you build a PersonDto with just an age like this

var person = Build.PersonDto.WithAge(20).Build();

Then both of these asserts are true.

Assert.AreEqual("Thomas", person.FirstName);
Assert.AreEqual(20, person.Age);

Custom methods

Most of the time generated With methods are enough, but sometimes it’s handy to have builder methods that don’t correspond any of the properties directly. I call these custom methods. They can be added to the same partial class as defaults. Here is an example of custom method WithFullname for the PersonDto builder.

public partial class PersonDtoBuilder
{
    public PersonDtoBuilder WithFullname(string fullname)
    {
        dto.FirstName = fullname.Split(' ')[0];
        dto.LastName = fullname.Split(' ')[1];
        return this;
    }
}

Use it like any other With-method.

var person = Build.PersonDto.WithFullname("Thomas Anderson")
                            .WithAge(24)
                            .Build();

Usage

As far as I know, there isn’t any deployment model to share T4 templates. The good news is that it’s still easy to start using existing T4 template in your own project. Here is what you need to do:

  1. Copy the template file into your project.
  2. Open and configure the template in Visual Studio.

There are 3 things we need to configure in the template file itself, before it can be used in your project. When you open the template you’ll see these three lines (lines 8-10):

var namespaceOfBuilders = "Tests";
var project = GetProjectContainingFile("Dtos.cs");
var dtoTypes = GetClassesOf(project).Where(c => c.Name.EndsWith("Dto")).ToList();

This is the configuration part of the template. First you want to set the namespaceOfBuilders. This is the namespace where generated classes will be located. Next you need to help template to find the project where your DTOs are located. Just put any source file name in that project to GetProjectContainingFile("Any.cs"). Finally you need to provide some convension to determine which classes should be considered as DTOs. In this example it takes all the classes with name ending Dto.

Here is how you can search DTOs from multiple projects.

var project1 = GetProjectContainingFile("SomeRequest.cs");
var project2 = GetProjectContainingFile("SomeDto.cs");

var requests = GetClassesOf(project1).Where(c => c.Name.EndsWith("Request")).ToList();
var dtos = GetClassesOf(project2).Where(c => c.Name.EndsWith("Dto")).ToList();

var dtoTypes = requests.Union(dtos).ToList();

How does it work?

T4 is a text templating system built into Visual Studio. You don’t need to install anything to start using it. Just create a file with file extension tt and Visual Studio recognizes it as a template. T4 templates allow us to generate code (or any text really) right in the Visual Studio. By default Visual studio runs the T4 template every time the template file is saved. By installing AutoT4 extension to Visual Studio you can automate T4 templates to be executed automatically just before compiling. I highly recommend using this extension when using the T4 Builder generator.

The template uses Visual Studio’s EnvDte API to access DTO classes before compiling. Remember, builders are generated before there are any binaries and therefore normal reflection is out of question. Visual Studio has this EnvDte API for building plugins and it allows code to access the solution tree as it is in VS. I believe this is the only way to achive code generation based on another existing code. The downside is that this obviously makes the T4 template depend on Visual Studio and thus cannot be used outside of it, let’s say for example in Xamarin Studio.

Conclusions

Builder pattern is really useful when you need to create multiple DTOs with some differences. This is usually the case when you write Behaviour/Integration tests against the application interface. Builders are nice, but writing and maintaining them is not. Generating these DTO builders frees you from typing the boilerplate code and allows you to focus on the test code itself.

The Good

  • Generates well formatted, clean and easy to read code
  • Generates builder classes with fluent and advanced API
  • Supports custom methods via partial classes
  • Supports setting meaningful defaults for each DTO type
  • Easy to utilize in any project just by copying one T4 file
  • Easy to switch back to manual coding from code generation if ever needed

The Bad

  • Works only in Visual Studio

The Ugly

  • T4 template code itself is not that well factored