Thoughts on Code Generation With Sitecore
Tags: sitecore, codegen
Published: 2020-07-26 22:28:16

As I've been putting together a base Helix solution, I've contemplated several things along the way. One was which source control system I'd use for Sitecore items, [TDS](https://www.teamdevelopmentforsitecore.com/TDS-Classic) or Unicorn. 

{% github SitecoreUnicorn/Unicorn %}

(The minor joke is that the originator of Unicorn works for Sitecore now, and TDS/Hedgehog is also owned by Sitecore.) 

I'm steering toward Unicorn because it's free and lightweight as far as a Helix solution is concerned, using configuration files to get the items rather than a whole other project(s).

In TDS, there's a section in the properties for doing code generation, where you can supply T4 files that will output your Sitecore templates as class files. When I did my first major project five years ago, I just discovered the magic of [Glass Mapper](http://glass.lu/), and being introduced to TDS, saw the code generation bit and thought "oh nice, a time saver!" Which was great...until I saw the output.

The code generation is such that the output is in one file. That might be a bit more tolerable now, where Helix has subdivided features to make the number of templates per minimal. But five years ago, that generated file was HUGE! And then you almost always had to do something else with it, so it was a partial class that you extended, and you get around to realizing you made your own class file anyway, just now it's in two spots. Kind of a pain in the butt.

So after that, I really became an advocate against code generation, and I've only used it on one project since, where the architect really wanted to use it. Talking to some other developers on [Sitecore Slack](https://docs.google.com/forms/d/1bAVDgP5-FhFh8ohPchHtifq-rz7EBkuPojAzdEofJyo/viewform?edit_requested=true), the consensus appears to be against code generation as well. That said, I've found one good use for the process, and that's to get IDs.

I like to get all of my template and field IDs for use with Glass (I'll discuss that more in another blog post), but the process of looking them up can be a bit tedious. The template IDs are pretty quick, but having to dig into the sections to get the fields, especially if you have a lot...that's a lot of drill-down. So rather than doing that, I took a T4 template designed to run with Rainbow Code Generation and stripped it back to just get the IDs out.

{% github heikof/RainbowCodeGeneration %}

In putting the script together, one thing I ran into was references to the various assemblies. First because the script was written for Sitecore 8, and I had to add more references for Sitecore 9, but second because I was using the PackageReferences format for my NuGet, which by default puts the packages in my users directory; not friendly for other users. Fortunately, someone I know just wrote a blog about this. {% link https://dev.to/kmac23va/nuget-pro-tip-control-where-your-packages-are-installed-4b5f %}

(Okay, fine, it was me. For those wondering about a random NuGet blog, see, it wasn't random!)

So I broke the T4 template process down into two parts. First is the main script, that does the actual work. This uses Unicorn, so if you have that and Sitecore.Kernel set up, you just need to get the RainbowCodeGeneration package; everything else should install as sub-packages of these.

```
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<# 
// ASSEMBLIES
// These assemblies need to be changed if there is an upgrade of the specified packages
#>
<#@ assembly name="C:\packages\rainbow.core\2.1.1\lib\net452\Rainbow.dll" #>
<#@ assembly name="C:\packages\rainbow.storage.yaml\2.1.1\lib\net452\Rainbow.Storage.Yaml.dll" #>
<#@ assembly name="C:\packages\rainbowcodegeneration\0.3.0\lib\net452\RainbowCodeGeneration.dll" #>
<#@ assembly name="C:\packages\sitecore.kernel\9.3.0\lib\net471\Sitecore.Kernel.dll" #>
<#@ assembly name="C:\packages\sitecore.logging\9.3.0\lib\net471\Sitecore.Logging.dll" #>
<#@ assembly name="C:\packages\microsoft.extensions.dependencyinjection.abstractions\2.1.1\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll" #>
<#@ assembly name="C:\packages\microsoft.extensions.dependencyinjection\2.1.1\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.dll" #>
<#@ assembly name="c:\packages\microsoft.extensions.logging\2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.dll" #>
<#@ assembly name="c:\packages\microsoft.extensions.logging.abstractions\2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll" #>
<#@ assembly name="c:\packages\microsoft.extensions.options\2.1.1\lib\netstandard2.0\Microsoft.Extensions.Options.dll" #>
<#@ assembly name="c:\packages\microsoft.extensions.diagnostics.healthchecks.abstractions\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll" #>
<# 
// CONFIGURATION
var physicalFileStore = @"..\..\serialization"; // the path to your serialization items
var treeName = layerName + "." + moduleName + ".Templates";
var treePath = "/sitecore/templates/" + layerName + "/" + moduleName; // the matching path in Sitecore for the configuration
var templates = RainbowCodeGeneration.RainbowReader.GetTemplates(Host.ResolvePath(physicalFileStore), treeName, treePath);
#>
<#@ import namespace="RainbowCodeGeneration" #>
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated based on the Unicorn serialization items
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

// ReSharper disable InconsistentNaming
using Sitecore.Data;

namespace <#= layerName #>.<#= moduleName #>.Models {
<# try { foreach (var template in templates) { #>
    public static class <#= StringExtensions.AsClassName(template.Item.Name.StartsWith("_") ? "Meta" + template.Item.Name.Substring(1) : template.Item.Name).Replace("_", string.Empty) #>Constants {
        public static readonly ID TemplateId = new ID("<#= template.Item.Id.ToString() #>");
    <#foreach(var field in template.Fields){#>    public static readonly ID <#= StringExtensions.AsPropertyName(field.Name).Replace("_", string.Empty) #>FieldId = new ID("<#=field.Id.ToString()#>");
    <#}#>}
<#  } } catch (Exception ex){ Console.WriteLine(ex); } #>
}
```

I name this file **IDCodeGen.ttinclude** and put it on the same level as my solution file. Then, in each project I need to generate IDs for, I add the following T4 file. I call it **ModelConstants.tt**, which creates **ModelConstants.cs**.

```
<# 
// MODULE CONFIGURATION
var layerName = "Foundation";
var moduleName = "Kernel";
#>
<#@ include file="$(SolutionDir)IDCodeGen.ttinclude" once="true" #>
```

The nice thing about this is the reusability factor. All I need to do is change the layer and module name as appropriate, and this is ready to go wherever. And if you use the [Anders Laub Visual Studio plugin for Helix](https://marketplace.visualstudio.com/items?itemName=AndersLaublaubplusco.SitecoreHelixVisualStudioTemplates), you can set up replacement variables for the layer and module and then everything is done for you without breaking a sweat!