Introduction to code generation with SourceMason

Introduction

This article will introduce the fundamentals of SourceMason code templates by dissecting a very basic business object template. The template only generates properties for the business object, it does not include any logic/output for loading or saving etc.

Sample Image - maximum width is 600 pixels

Installing software

Before you can run the code generation template you need to download the SourceMason Free Edition from www.sourcemason.com. The Free Edition includes the SourceMason Runner application which uses the same template engine as SourceMason Studio (included in Professional Edition).

SchemaExplorer

SchemaExplorer is an addon for SourceMason (included in the install) that allows you to read the schema for any type of database. In this example I will be using Sql Server 2005 and the AdventureWorks sample database.

Below is the SchemaExplorer interface and table classes. View and Command classes are not displayed in the diagram below.

Code Template Structure

A code template consists of three sections, a header section, script section and template body or markup section.

Template Header Section

A template header section only contains template directives.

Template directives have the format of: <%@ some directive definition %>

CodeTemplate Directive

Every template must have one CodeTemplate directive.

The format is:
<%@ CodeTemplate Language="" TargetLanguage="" Src="" Inherits="" Description="" %>

Example:
<%@ CodeTemplate Language="C#" TargetLanguage="C#" Description="This template creates a basic business object." %>

Property Directive

Templates usually have one or more properties defined by property directives. The properties can be of any .NET type.

The format is:
<%@ Property Name="" Type="" Default="" Optional="False" Category="" Description="" Editor="" EditorBase="" Serializer="" %>

Example:
<%@ Property Name="SourceTable" Type="EquatorIT.SchemaExplorer.TableSchema" Description="Table that should be read." %>

Using Directive

The Using directive is the equivalent of the C# using directive or the VB.NET Imports statement. It allows the use of types without the need to qualify them with a namespace.

The format is: <%@ Using Namespace="" %>

Example:
<%@ Using Namespace="EquatorIT.SchemaExplorer" %>

Assembly Directive

The format is: <%@ Assembly Name="" %>

The Assembly directive allows assembly references to be added to your template. It the equivalent of using Add Reference in a C# or VB.NET project. Example:
<%@ Assembly Name="EquatorIT.SchemaExplorer" %>

Register Directive

The format is: <%@ Register Name="" Template="" %>

The Register directive allows other templates to be used within the template. These templates become sub-templates of the template.

Include Directive

The Include directive allows source files to included in the template. This is an alternative to putting custom code in external assemblies.

The format is: <%@ Include FileName="" %>

Script Section

The script section is where custom methods for the template reside.

The script section has the following format:
<script runat="template">
</script>

Template body or markup section

The template body or markup section is where the template output is defined. It contains a combination of static output and markup. Static output is text that will be rendered unchanged to the template output. Template markup is used in addition to static output to create template output.

There are three types of markup elements, inline expression, inline code, and comment elements.

Inline expression element

The Inline Expression element is used to include the expression result into the generated output of the code template.

Inline expressions have the format:
<%= SomeExpression%>
where SomeExpression is an expression

Example:
<% foreach(TableColumnSchema column in SourceTable.Columns) 
{ %>
  Database Type - <%= column.DbType %>
  .Net Type     - <%= column.Type %>
<%} %>

Inline code element

The Inline Code element allows programatic control of template output. The code used is of the type specified in the CodeTemplate directive Language attribute.

Comment elements

Comments may span multiple lines and will not form part of the code template's output. The SourceMason Engine will ignore the content inside the comment element.

Example:
<%-- 
TemplateName: BusinessObject.smt
Author: Brett Watt
Description: Generate business object class from database table schema
--%>

Template Source Code

<%@ CodeTemplate Language="C#" TargetLanguage="C#" Description="" %>
<%@ Property Name="SourceTable" Type="EquatorIT.SchemaExplorer.TableSchema" Description="Table that should be read to create the business object." %>
<%@ Property Name="Namespace" Type="string" Default="" Optional="False" Category="" Description="Namespace for the business object" Editor="" EditorBase="" Serializer="" %>

<script runat="template">
private string GetMemberVariableInit(TableColumnSchema column)
{
  switch (column.Type.ToString().ToUpper())
  {
    case "SYSTEM.STRING":
      return " = string.Empty";
		
    case "SYSTEM.GUID":
      return " = Guid.Empty";			
		break;
  }
	
  return string.Empty;
}

</script>
using System;
using System.ComponentModel;
using System.Collections;

namespace <%= Namespace %>
{	
  [Serializable]	
  public class <%= EquatorIT.Common.StringUtils.ToPascalCase(SourceTable.Name) %>
  {	
  <%foreach(TableColumnSchema column in SourceTable.Columns) %>
  <%{ %>
    protected <%= EquatorIT.Common.TypeUtils.GetCSharpTypeFromDotNetType(column.Type.ToString()) %> _<%= EquatorIT.Common.StringUtils.ToCamelCase(column.Name) %><%= GetMemberVariableInit(column)%>;		
  <%} %>	
	
    public <%= EquatorIT.Common.StringUtils.ToPascalCase(SourceTable.Name) %>()
    {
    }
	
    #region Properties
    <%foreach(TableColumnSchema column in SourceTable.Columns) %>
    <%{ %>
		
    public <%= EquatorIT.Common.TypeUtils.GetCSharpTypeFromDotNetType(column.Type.ToString()) %> <%= EquatorIT.Common.StringUtils.ToPascalCase(column.Name) %>
    {
      get { return _<%= EquatorIT.Common.StringUtils.ToCamelCase(column.Name) %>; }			
      set { _<%= EquatorIT.Common.StringUtils.ToCamelCase(column.Name) %> = value; }	
    }	
    <%} %>	
		
    #endregion
	
    }
}

Template Breakdown

  1. Output Namespace
    namespace <%= Namespace %>
    
    I use the template property Namespace in an inline expression element to output the namespace for the class.

  2. Output Class Name
    public class <%= EquatorIT.Common.StringUtils.ToPascalCase(SourceTable.Name) %>
    
    I use a helper function to convert the SourceTable Name to pascal case and ouptut as the class name.

  3. Output Member Variables
    <%foreach(TableColumnSchema column in SourceTable.Columns) %>
      <%{ %>
        protected <%= EquatorIT.Common.TypeUtils.GetCSharpTypeFromDotNetType(column.Type.ToString()) %> _<%= EquatorIT.Common.StringUtils.ToCamelCase(column.Name) %><%= GetMemberVariableInit(column)%>;		
      <%} %>	
    

    I use an inline code element to iterate through all the TableColumnSchema objects in SourceTable.Columns and create the corresponding member variables. I also use a helper function to create the initialisation expression for each member variable.

  4. Output Properties
    <%foreach(TableColumnSchema column in SourceTable.Columns) %>
        <%{ %>
    		
        public <%= EquatorIT.Common.TypeUtils.GetCSharpTypeFromDotNetType(column.Type.ToString()) %> <%= EquatorIT.Common.StringUtils.ToPascalCase(column.Name) %>
        {
          get { return _<%= EquatorIT.Common.StringUtils.ToCamelCase(column.Name) %>; }			
          set { _<%= EquatorIT.Common.StringUtils.ToCamelCase(column.Name) %> = value; }	
        }	
        <%} %>	
    
  5. I use an inline code element to iterate through all the TableColumnSchema objects in SourceTable.Columns and create the corresponding properties. I also get the CSharp type by using the GetCSharpTypeFromDotNetType helper function.

Template Output

using System;
using System.ComponentModel;
using System.Collections;

namespace Example
{
  [Serializable]
  public class Address
  {
    protected int _addressID;
    protected string _addressLine1 = string.Empty;
    protected string _addressLine2 = string.Empty;
    protected string _city = string.Empty;
    protected int _stateProvinceID;
    protected string _postalCode = string.Empty;
    protected Guid _rowguid = Guid.Empty;
    protected DateTime _modifiedDate;

    public Address()
    {
    }

    #region Properties

    public int AddressID
    {
      get { return _addressID; }
      set { _addressID = value; }
    }

    public string AddressLine1
    {
      get { return _addressLine1; }
      set { _addressLine1 = value; }
    }

    public string AddressLine2
    {
      get { return _addressLine2; }
      set { _addressLine2 = value; }
    }

    public string City
    {
      get { return _city; }
      set { _city = value; }
    }

    public int StateProvinceID
    {
      get { return _stateProvinceID; }
      set { _stateProvinceID = value; }
    }

    public string PostalCode
    {
      get { return _postalCode; }
      set { _postalCode = value; }
    }

    public Guid Rowguid
    {
      get { return _rowguid; }
      set { _rowguid = value; }
    }

    public DateTime ModifiedDate
    {
      get { return _modifiedDate; }
	  set { _modifiedDate = value; }
    }

    #endregion

  }
}

Conclusion

As you can see code templates have the potential to automatically generate much of the boilerplate code use in todays sofware.