loading...

Thoughts on Code Generation With Sitecore

kmac23va profile image Kenneth McAndrew Updated on ・4 min read

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 or Unicorn.

GitHub logo SitecoreUnicorn / Unicorn

A Sitecore utility designed to simplify deployment of Sitecore items across environments automatically

la la la la

Unicorn is a utility for Sitecore that solves the issue of moving templates, renderings, and other database items between Sitecore instances. This becomes problematic when developers have their own local instances - packages are error-prone and tend to be forgotten on the way to production. Unicorn solves this issue by writing serialized copies of Sitecore items to disk along with the code - this way, a copy of the necessary database items for a given codebase accompanies it in source control.

For basic usage, Unicorn has two moving parts:

  • Data provider - The default Sitecore data provider is extended to automatically serialize item changes as they are made to Sitecore. This means that at any given time, what's serialized is the "master copy."
  • Control panel - this tool (/unicorn.aspx) is a page that can sync the state of Sitecore to the state stored on disk (respecting presets and exclusions). You…

(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, 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, 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 logo heikof / RainbowCodeGeneration

A simple set of utility classes and a sample T4 template that allow easy code generation for Sitecore templates from Rainbow / Unicorn serialized items. Currently, only the YAML serialization format is supported.

RainbowCodeGeneration

NuGet

A simple set of utility classes and a sample T4 template that allow easy code generation for Sitecore templates from Rainbow / Unicorn serialized items. Currently, only the YAML-based serialisation format is supported.

Using the Rainbow / Unicorn code generation

You are probably already using Unicorn with the YAML serialisation format (you really should - it's awesome!). I am also assuming that you serialise your templates in a dedicated folder inside your solution, ideally in a modular fashion like in Sitecore Habitat. Using the Rainbow / Unicorn code generation is simple:

  • Install the nuget package
  • Configure the T4 template
  • Re-Run the code generation when Sitecore templates change

Installing the nuget package

Install the RainbowCodeGeneration package. This installs a set of simple utility classes and an example T4 template. The template will generate an empty SitecoreTemplates struct in your project.

Empty Sitecore Template

Configuring the T4 template

Adjust a few settings…

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.

(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, you can set up replacement variables for the layer and module and then everything is done for you without breaking a sweat!

Posted on by:

kmac23va profile

Kenneth McAndrew

@kmac23va

Over 20 years in web development, from HTML (remember image maps and frames?) to classic ASP to ASP.NET to .NET CMSes.

Discussion

markdown guide