In-Memory Code Generation With .NET – Templating Engines

Templating engines allow a very flexible and quite easy, because human-readable definition of the code to be generated. Following, three templating engines are introduced. The main differences between these engines in the context of this article is the level of separation between the WHAT and the HOW.

While T4 or the ASP.NET view engines compile into C#, which bascially allows doing anything C# is capable of inside of the view, StringTemplate brings its own compiler that enforces a strict View-Model-separation. Only read-only on the model or text output operations are allowed inside of the view. DotLiquid does the same, and only processes known and secure model types.

T4

The most common templating engine in the .NET world is probably the T4 Text Template. Since Visual Studio 2010, it comes in two flavors: regular and preprocessed. regular text templates are processed within Visual Studio itself (or a templating host created at runtime and loaded with the template). preprocessed text templates are transformed into C# classes that can be called at runtime for processing. Regular T4 templates need some infrastructure to run, which in turn needs refrences to Visual Studio assemblies. Also, a lot of that infrastructure is practically undocumented, which makes hosting and processing regular T4 templates in custom code not the easiest thing to do. Becasue of that, only preprocessed templates are discussed in this article. Please read my other article about some neet stuff you can do with hosted template processing.

Consider a simple T4 template that is saved in a file called ViewModelTemplate.tt. Visual Studio generates a class named ViewModelTemplate. This generated class has a method called TransformText which renders the template into a string. The generated class ViewModelTemplate can be extended by adding a partial class with the same name. Here, a partial class is added that contains a property to store the generation model:

partial class ViewModelTemplate
{
    public TypeGenerationModel Model { get; set; }
}

Inside the T4 template, the property Model can be accessed for information about the type to generate. The template generates three types of properties: properties that only wrap access to the underlying data model, properties that return other data view models and properties that return lists of data view models. The T4 template may look like this:

<#@ template language="C#" #>
<#@ import namespace="System.Linq" #>
using System.Collections.Generic;
using System.Linq;

namespace <#= Model.Namespace #>
{
  public class <#= Model.Name #> : Demo.ViewModelBase<<#= Model.ModelTypeName #>>
  {
<#foreach(var property in Model.Properties.Where(p => !p.IsViewModel))
  {#>
    public <#= property.TypeName #> <#= property.Name #>
    {
<#  if(property.CanGet) 
    {#> 
      get 
      { 
        return Model.<#= property.Name #>; 
      } 
<#  }        
    if(property.CanSet) 
    {#> 
      set 
      { 
        if(Model.<#= property.Name #> == value) return;
        Model.<#= property.Name #> = value; 
        OnPropertyChanged("<#= property.Name #>");
      } 
<#  }#>      
    }
<#} 
  
  foreach(var property in Model.Properties.Where(p => p.IsViewModel && !p.IsList))
  {#>
    private <#= property.TypeName #> _<#= property.Name #>;
    public <#= property.TypeName #> <#= property.Name #>
    {
<#  if(property.CanGet) 
    {#> 
      get 
      { 
        if(_<#= property.Name #> == null) _<#= property.Name #> = new <#= property.TypeName #>(Model.<#= property.Name #>);
        return _<#= property.Name #>; 
      } 
<#  }        
    if(property.CanSet) 
    {#> 
      set 
      { 
        if(_<#= property.Name #> == value) return;
        _<#= property.Name #> = value; 
        Model.<#= property.Name #> = value.Model; 
        OnPropertyChanged("<#= property.Name #>");
      } 
<#  }#>      
    }
<#} 
  
  foreach(var property in Model.Properties.Where(p => p.IsViewModel && p.IsList && p.CanGet))
  {#>
    private List<<#= property.TypeName #>> _<#= property.Name #>;
    public List<<#= property.TypeName #>> <#= property.Name #>
    {
      get 
      { 
        if(_<#= property.Name #> == null) _<#= property.Name #> = new List<<#= property.TypeName #>>(Model.<#= property.Name #>.Select(m => new <#= property.TypeName #>(m)));
        return _<#= property.Name #>; 
      } 
    }
<#}#>
    public <#= Model.Name #>(<#= Model.ModelTypeName #> model) : base(model)
    {
    }
  }
}

Executing a T4 template is very easy since the template itself is already a .NET class.

public class CodeGenerator
{
    public static string Generate(TypeGenerationModel model)
    {
        var template = new ViewModelTemplate { Model = model };
        return template.TransformText();
    }
}

The main program can call the above class now:

static void Main(string[] args)
{
    var generationModels = CreateGenerationModels(typeof(Person), "Generated");
    var sources = generationModels.Select(T4.CodeGenerator.Generate);

    // ... rest of the work here ...
}

StringTemplate

StringTemplate is a templating engine created by the ANTLR team. It is available for Java, Pyhton and the .NET framework. There are many differences between T4 and StringTemplate, but the main difference that is of matter in this case is that of the strict model-view separation. This means that inside StringTemplate templates, invoking methods or operators is not allowed. Although StringTemplate supports if-conditions, these conditions only match boolean values and the existance of the expression (comparable to JavaScript’s undefined).

To be able to use StringTemplate, the generation model from above must be extended by properties that replace the property filters in the T4 template. So instead of filtering the properties inside the template, the filtering must be applied inside the generation model:

/// <summary>
/// Defines the type that will be generated.
/// </summary>
public class TypeGenerationModel
{
    // ... properties omitted ...

    /// <summary>
    /// Gets the regular properties to generate.
    /// </summary>
    public IEnumerable<PropertyGenerationModel> Properties { get; private set; }

    /// <summary>
    /// Gets the properties to generate that return view model types.
    /// </summary>
    public IEnumerable<PropertyGenerationModel> ViewModelProperties { get; private set; }

    /// <summary>
    /// Gets the properties to generate that return lists of view model types.
    /// </summary>
    public IEnumerable<PropertyGenerationModel> ViewModelListProperties { get; private set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="TypeGenerationModel"/> class.
    /// </summary>
    /// <param name="modelType">Type of the data model for which to generate the view model.</param>
    /// <param name="namespace">The @namespace of the generated view model.</param>
    public TypeGenerationModel(Type modelType, string @namespace)
    {
        Namespace = @namespace;
        ModelType = modelType;
        ModelTypeName = modelType.GetTypeName();
        Name = modelType.Name;

        var allProperties = ModelType
            .GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Select(p => new PropertyGenerationModel(this, p))
            .Where(p => p.CanGet || p.CanSet)
            .ToList();

        Properties = allProperties.Where(p => !p.IsViewModel);

        ViewModelProperties = allProperties.Where(p => p.IsViewModel && !p.IsList);

        ViewModelListProperties = allProperties.Where(p => p.IsViewModel && p.IsList && p.CanGet)
    }
}

The T4 template from above can quite easily be transformed into StringTemplate syntax:

using System.Collections.Generic;
using System.Linq;

namespace $Model.Namespace$
{
   public class $Model.Name$ : Demo.ViewModelBase<$Model.ModelTypeName$>
   {
   $Model.Properties:
   {
      property|
      public $property.TypeName$ $property.Name$
      {      
      $if(property.CanGet)$
         get 
         { 
            return Model.$property.Name$; 
         \} 
      $endif$
               
      $if(property.CanSet)$
         set 
         { 
            if(Model.$property.Name$ == value) return;
            Model.$property.Name$ = value; 
            OnPropertyChanged("$property.Name$");
         \} 
      $endif$         
      \}
   }$ 
   
   $Model.ViewModelProperties:
   {
      property|
      private $property.TypeName$ _$property.Name$;
      public $property.TypeName$ $property.Name$
      {
      $if(property.CanGet)$
         get 
         { 
            if(_$property.Name$ == null) _$property.Name$ = new $property.TypeName$(Model.$property.Name$);
            return _$property.Name$; 
         \} 
      $endif$
            
      $if(property.CanSet)$
         set 
         { 
            if(_$property.Name$ == value) return;
            _$property.Name$ = value; 
            Model.$property.Name$ = value.Model; 
            OnPropertyChanged("$property.Name$");
         \} 
      $endif$
      \}
   }$
   
   $Model.ViewModelListProperties:
   {
      property|
      private List<$property.TypeName$> _$property.Name$;
      public List<$property.TypeName$> $property.Name$
      {
         get 
         { 
            if(_$property.Name$ == null) _$property.Name$ = new List<$property.TypeName$>(Model.$property.Name$.Select(m => new $property.TypeName$(m)));
            return _$property.Name$; 
         \} 
      \}
   }$
      public $Model.Name$($Model.ModelTypeName$ model) : base(model)
      {
      }
   }
}

A StringTemplate template must be loaded into the StringTemplate engine. While StringTemplate offers functionality for more complex scenarios (e.g. dynamically loading sub templates), here only a basic set of this functionality is required:

public static string Generate(TypeGenerationModel model)
{
    var template = new Antlr4.StringTemplate.Template(Resource.STViewModelTemplate, '$', '$');
    template.Add("Model", model);

    return template.Render();
}

The main program can call the above class now:

static void Main(string[] args)
{
    var generationModels = CreateGenerationModels(typeof(Person), "Generated");
    var sources = generationModels.Select(ST.CodeGenerator.Generate);

    // ... rest of the work here ...
}

DotLiquid

DotLiquid is the new kid on the block. It is a port of Ruby’s Liquid Markup. Like StringTemplate, DotLiquid does not allow calling any methods from inside the view. On the other hand, it does allow comparison operators. The main difference to other templating engines is that only certain types of models get processed. This means that only basic types like strings or integers or types that derive from DotLiquid.Drop are allowed as models. DotLiquid’s syntax is simmilar to that of StringTemplate. The above template looks for DotLiquid like this:

using System.Collections.Generic;
using System.Linq;

namespace {{ Model.namespace }}
{
    public class {{ Model.name }}: Demo.ViewModelBase<{{ Model.model_Type_name }}>
    {
    {% for property in Model.Properties -%}
        public {{ property.type_name }} {{ property.name }}
        {
        {% if property.CanGet %}
            get 
            { 
                return Model.{{ property.name }}; 
            } 
        {% endif %}
        {% if property.CanSet %}
            set 
            { 
                if(Model.{{ property.name }} == value) return;
                Model.{{ property.name }} = value; 
                OnPropertyChanged("{{ property.name }}");
            } 
        {% endif %} 
        }
    {% endfor -%}
    
    {% for property in Model.view_model_properties -%}
        private {{ property.type_name }} _{{ property.name }};
        public {{ property.type_name }} {{ property.name }}
        {
        {% if property.CanGet %}
            get 
            { 
                if(_{{ property.name }} == null) _{{ property.name }} = new {{ property.type_name }}(Model.{{ property.name }});
                return _{{ property.name}}; 
            } 
        {% endif %}
        {% if property.CanSet %}
            set 
            { 
                if(_{{ property.name }} == value) return;
                _{{ property.name }} = value; 
                Model.{{ property.name }} = value.Model; 
                OnPropertyChanged("{{ property.name }}");
            } 
        {% endif %}
        }
    {% endfor -%}
    
    {% for property in Model.view_model_list_properties -%}
        private List<{{ property.type_name }}> _{{ property.name }};
        public List<{{ property.type_name }}> {{ property.name }}
        {
            get 
            { 
                if(_{{ property.name }} == null) _{{ property.name }} = new List<{{ property.type_name }}>(Model.{{ property.name }}.Select(m => new {{ property.type_name }}(m)));
                return _{{ property.name }}; 
            } 
        }
    {% endfor -%}

        public {{ Model.name }}({{ Model.model_Type_name }} model) : base(model)
        {
        }
    }
}

The code needed to execute thee template loosk like this:

public class CodeGenerator
{
    public static string Generate(TypeGenerationModel model)
    {
        var template = DotLiquid.Template.Parse(Resource.DotLiquid_ViewModelTemplate);
        return template.Render(DotLiquid.Hash.FromAnonymousObject(new { Model = model }));
    }
}
Series Navigation<< In-Memory Code Generation With .NET – ModelsIn-Memory Code Generation With .NET – Compilation >>

Freelance full-stack .NET and JS developer and architect. Located near Cologne, Germany.

Leave a Reply

Your email address will not be published. Required fields are marked *