Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

.NET Code Generation through StringTemplate

3.75/5 (13 votes)
30 May 2008CPOL6 min read 1   516  
This article concisely introduces StringTemplate with a practical .NET code generation example. The scenario includes common issues when it comes to code generation such as conditionally generating codes, loop, and token replacement.

Introduction

This article concisely introduces how to apply the C# version of StringTemplate[1] with a practical .NET code generation example. It is about generating markup of *.ascx (i.e. UserControl) files programmatically. The scenario includes common issues when it comes to code generation such as conditionally generating codes, loop, and token replacement.

Background

Recently, I was conducting a Web-based project whose goal is to design a code generator for my company, letting developers generate half-finished DotNetNuke (DNN) modules with Web-based interface. A DNN module is, in reality, a UserControl with some added flavors by DNN platform. It is not important to emphasize the difference between DNN modules and UserControls in this article. The purpose of this task is to reduce the cost of developing a routine module. Developers can configure properties of a domain-specific module builder, click submit buttons to generate codes of the module, and then continue to enhance modules’ features with Visual Studio rather than reinventing the whole wheel each time a routine requirement arises. Having described the background, let's directly plunge into the exemplary problem we want to solve.

Problem

Consider the following markup tags in Listing 1, which describe an SqlDataSource control. Pay attention to the content within <SelectParameters></SelectParameters>. The difficulty to dynamically generate the content (e.g. <asp:CookieParameter>) within it lies in the fact that there are various kinds of parameters based on the parameter source such as Cookie, Session, etc. Moreover, some kinds of parameters possess distinct attributes. For example, CookieName and QueryStringField exclusively belong to CookieParameter and QueryStringParameter respectively. Hence, if we allow users to choose a parameter source, our code generator must take care of the conditional generation of codes.

Listing 1. Markup Tags of SqlDataSource

ASP.NET
<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
    ConnectionString="Data Source=.;Initial Catalog=NORTHWIND;
    Integrated Security=True"
    ProviderName="System.Data.SqlClient" 
    SelectCommand="SELECT [CustomerID], [CompanyName], 
    [ContactName], [ContactTitle], [Address], [City], [Region], 
    [PostalCode], [Country] FROM [Customers] WHERE (([City] = @City) AND 
    ([Country] = @Country))">
    <SelectParameters>
        <asp:CookieParameter CookieName="cookieCity" Name="City" Type="String" />
        <asp:Parameter Name="Country" Type="String" />
        <asp:QueryStringParameter QueryStringField="qsAddr" 
            Name="Address" Type="string" />

    </SelectParameters>
</asp:SqlDataSource>

The intuitive solution is to write a function that applies the ifelse rule to string concatenation within loops as illustrated in Listing 1. Assume ParamInfoList contains all parameter metadata collected from users. The logic within foreach appends appropriate strings according to the parameter source. The obvious disadvantage is maintainability. If someone else wants to increase a new or revised existing generation rule, he/she must jump into the immense code clusters; the longer the codes generated, the less maintainable is the code generating it.

Listing 2. Code generation with string concatenation

C#
string strResult ="<SelectParameters>";
... ...
foreach (ParamInfo paramInfo in ParamInfoList)
{... ...if (paramInfo.Source == "Cookie"){ strResult += "CookieName=\"" + 
    paramInfo.SourceId +"\"";}else if (paramInfo.Source == "QueryString"){// ......}
}

strResult += "</SelectParameters>";

return strResult;

Solution with StringTemplate

A better approach to solve the problem described in the preceding section is to extract the code logic for concatenating strings (i.e. codes generated) as a template so that once a new requirement comes, developers can modify the template to accommodate the requirement with ease. This is a common practice among many code generation engines like CodeSmith. Although CodeSmith is indeed the most popular code generator in the .NET realm, the license fee to use its SDK is too high in respect of my project. After some researches, I found that StringTemplate[1] can satisfy my requirement. Without exhaustive introduction to it, let’s directly demonstrate how to apply it to solve the problem.

We begin with how to define the template of the <SelectParameters></SelectParameters> element. Take a look at the template in Listing 3. Before describing how to make use of this template to generate code of Listing 1, let’s analyze the semantic meaning.

The out dollar signs($) (starts at line 2; ends at line 8) tell StringTemplate engine to take special care of the inner text. In this case, we are defining a list of SqlParameters elements (line 7) that will be generated based on the input parameters, Prefixes, ParamNames, …, Types (line 2 to line 6). These parameters are formally named attribute in StringTemplate official document, and they all have shorthand, pre, pn, …, t respectively. Notice that the $if(sn)$$endif$ syntax in line 7. This tells StringTemplate if sn attribute has a value or is true, insert the content within the if region. Finally, the ;separator=“\n” at the end will add a line break to each <asp:Parameter> generated. Now let’s demonstrate how to use Listing 3 to programmatically generate the codes.

Listing 3. SqlParameters.st

1 <selectparameters>
2    $Prefixes,
3     ParamNames,
4     SourceNames,
5     SourceIds,
6     Types:
7     {pre, pn, sn, sid, t | <asp:$pre$Parameter 
    $if(sn)$$sn$="$sid$"$endif$ Name="$pn$" Type="$t$" />};separator="\n"$ 
8    $
9 </selectparameters>

To run the demo code in Listing 4, download the C# version of StringTemplate first and refer its DLLs (within .NET-2.0 directory) in place. The template content of Listing 3 is assumed to be stored at c:\temp\SqlParameters.st (included in the demo project); the *.st file extension is necessary for StringTemplate API.

Listing 4 begins with creating a group, called myGroup, rooted at c:\temp (line 3), and loading SqlParameters.st (line 4) as a StringTemplate. Once a template is created, we can feed values to the attributes (line 6 to 22). The query.ToString() in line 24 outputs the codes generated by SqlParameters.st, which is illustrated in Figure 1. Notice that we just set the attributes, and StringTemplate automatically produces a list of SqlParameter elements. This feature is credited to the colon(:) followed by {…} in line 6 of Listing 3; the text within {…} is treated as a sub-template (formally named anonymous template) whose parameters are from pre, pn, …, t. In addition, on line 15, we feed false to SourceNames attribute to conditionally tell StringTemplate to omit the codes about parameter’s source since <asp:SqlParameter> is not intended to bind to a source such as QueryString, Session, etc.

Listing 4. Use SqlParameters.st to generate codes of Listing 1

C#
1             using Antlr.StringTemplate;
2
3             StringTemplateGroup group = 
                   new StringTemplateGroup("myGroup", @"c:\temp");
4             StringTemplate query = group.GetInstanceOf("SqlParameters");
5
6             query.SetAttribute("Prefixes", "Cookie");
7             query.SetAttribute("ParamNames", "City");
8             query.SetAttribute("Types", "string");
9             query.SetAttribute("SourceNames", "CookieName");
10            query.SetAttribute("SourceIds", "cookieCity");
11
12            query.SetAttribute("Prefixes", "");
13            query.SetAttribute("ParamNames", "Country");
14            query.SetAttribute("Types", "string");
15            query.SetAttribute("SourceNames", false);
16            query.SetAttribute("SourceIds", "");
17
18            query.SetAttribute("Prefixes", "QueryString");
19            query.SetAttribute("ParamNames", "Address");
20            query.SetAttribute("Types", "string");
21            query.SetAttribute("SourceNames", "QueryStringField");
22            query.SetAttribute("SourceIds", "qsAddr");
23
24            Console.WriteLine(query.ToString());

Image 1

Figure 1. The output of SqlParameters.st

After elaborating how to generate <SelectParameters> of SqlDataSource, we extend Listing 3 to generate more markup of <SqlDataSource>. For simplicity, we just describe how to generate the ID attribute of <SqlDataSource>. The extended template is shown in Listing 5 where a $ID$ attribute is added. To use this template, you just need to insert query.SetAttribute("ID", "SqlDataSource1") in line 5 (or anywhere appropriate) of Listing 4. The output is shown in Figure 2. Discerning readers might observe that the whole *.ascx can be generated by a well-designed template.

By the way, you can try to add query.SetAttribute("ID", "SqlDataSource1") multiple times to see the difference between it and the one within <SelectParameters>. Calling query.SetAttribute("ID", "SqlDataSource1") two times will result in Figure 3, two successive SqlDataSource1 in ID attribute.

Listing 5. SqlDataSource.st

ASP.NET
<asp:SqlDataSource ID="$ID$" >
 <SelectParameters>
    $Prefixes,
     ParamNames,
     SourceNames,
     SourceIds,
     Types:
     {pre, pn, sn, sid, t | <asp:$pre$Parameter 
        $if(sn)$$sn$="$sid$"$endif$Name="$pn$" Type="$t$" />};separator="\n"
    $
 </SelectParameters>
</ asp:SqlDataSource >

Image 2

Figure 2. The output of SqlParameters.st

Image 3

Figure 3.

Conclusion

Code generation can be a time saver of project development if used appropriately. We introduce to you an excellent library, StringTemplate, to help you conduct the code generation process with flexibility. A practical example containing most of the issues in respect of code generation is used to demonstrate the ability of StringTemplate. By distilling the concern of code template as a standalone template file assists you in management and maintainability. This article merely explores the basic functionality of StringTemplate. In the future, I will introduce more advanced features of it. If you would like to understand StringTemplate thoroughly, I suggest you take a look at the great paper[2] written by the inventor of StringTemplate, and its official Web site[1].

References

  1. String Template Official Web site
  2. Terence Parr, Enforcing Strict ModelView Separation in Template Engines, Proceedings of the 6th international conference on Web engineering, 2006

History

  • 29th May, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)