Replacing T4 Templates with plain C# 11
Maybe you don't need a template library?
Replacing T4 Templates with plain C# 11
Generating files with T4
In Arc-Enabled Servers we have a quite a lot of different config files which need to be mostly the same, but a little different for each region we deploy into. For example, we have values that are used by Helm charts for each region:
Australia East
configmap:
Environment: Prod
Location: "australiaeast"
CloudEnvironment: "Public"
UseKeyVault: true
...
Brazil South
configmap:
Environment: Prod
Location: "brazilsouth"
CloudEnvironment: "Public"
UseKeyVault: true
...
We don’t want to manually maintain this for every region, so we have a T4 Template which is run to generate these files. Looks something like this:
<#@ template language="C#" hostspecific="true" #>
<#@ include file="MultiOutput.ttinclude" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="Deployment" #>
<#@ import namespace="Deployment.Models.Kubernetes" #>
<#@ assembly name="Deployment.dll" #>
<#
foreach (var environmentConfig in KubernetesDeploymentInfo.AllEnvironments)
{
var cloudWideConfig = environmentConfig.Value.Item1;
var regions = environmentConfig.Value.Item2;
foreach (var deployment in regions)
{
ClearOutput();
var valuesFileName = deployment.ValuesFileName;
var outputPath = "deployment\\charts\\certmgmtservice\\cmsfrontend";
#>
configmap:
Environment: "<#= deployment.Environment #>"
Location: "<#= deployment.LocationName #>"
CloudEnvironment: "<#= deployment.CloudEnvironment #>"
UseKeyVault: true
<#
SaveOutput(outputPath, valuesFileName, addToProject: false);
}
}
#>
NB These are snippets to illustrate the approach, you won’t be able to run them as-is. The real config files are quite a bit longer.
Anything in <# #>
is C# code, everything else is text which is echoed to the output when the template is run. Deployment.dll contains C# data structures which define things like the list of all known regions, and the names of each one.
This works OK, but I have some gripes:
- We use .NET 6 exclusively, but T4 is stuck in .NET Framework land. We would up moving to mono.texttemplating for .NET Core support
- It’s just not all that easy to read
- Debugging and developing the template code is a bit more of a hassle than regular code.
Generating with C# 11
I spent a bit of time looking at other templating solutions before thinking: The template is a text file with bits of embedded C#. What if instead we had a C# file with embedded text?
With the introduction of raw string literals this is a lot cleaner than it was. These are preview functionality at the time of writing, but since my use case only runs at build time, I’m not too worried about that. A C# program which does the equivalent of the template above looks like:
using Ev2Deploy.Deployment;
using System.IO;
using System.Text;
public class cmsfrontend
{
public static void Generate(string rootpath)
{
foreach (var environmentConfig in KubernetesDeploymentInfo.AllEnvironments)
{
var cloudWideConfig = environmentConfig.Value.Item1;
var regions = environmentConfig.Value.Item2;
foreach (var deployment in regions)
{
var valuesFileName = deployment.ValuesFileName;
var outputPath = "deployment\\charts\\certmgmtservice\\cmsfrontend";
vat outputFile = Path.Combine(rootpath, outputPath, valuesFileName)
using var output = new StreamWriter(outputFile, false, Encoding.UTF8);
output.Write($"""
configmap:
Environment: "{deployment.Environment}"
Location: "{deployment.LocationName}"
CloudEnvironment: "{deployment.CloudEnvironment}"
UseKeyVault: true
""");
}
}
}
public static void Main()
{
Generate("out");
}
}
This is about the same amount of boilerplate, but I prefer this version. It removes an entire tool (T4) from the toolchain and simplifies the dependencies; I can step through the code trivially if needed; syntax highlighting and Intellisense work normally.
More importantly, I think the actual core replacement syntax is cleaner. C# string interpolation in raw string literals is powerful enough to do all the things we were using in T4, it’s clear, easy to read, and familiar for C# developers.
Share this post
Twitter
Google+
Facebook
Reddit
LinkedIn
StumbleUpon
Pinterest
Email