Primary Menu

Using StringTemplate as a ViewEngine for ASP.Net MVC

I’ve been in love with the simplicity of MVC frameworks from day one. But I’ve always felt like the accompanying ViewEngine needed an equal measure of simplicity to have the greatest value. Having worked with ASP.Net MVC since preview 3, I realized pretty early on that if I didn’t find a ViewEngine I could get on board with, I’d find myself running back in defeat to … WebForms (gasp).

The good news: ASP.Net MVC offers 4 ViewEngines out of the box (assuming you include the MVC contrib project).

The bad news: I don’t really like using any of them.

The best news: I’m notÂ stuckÂ using any of them.

After kicking the tires on every ViewEngine I could get my hands on (including hacking my own template language just for fun), I became frustrated with endless pages of “tag soup”. My search for something lightweight, fast, easy to read, and wrist friendly led me to a solid product called StringTemplate. For those who’ve never heard of it, you can check it here. It’s in Version 3, and has an active user community. Here’s a scrape of their front page:

StringTemplate is a java template engine (with ports for C# and Python) for generating source code, web pages, emails, or any other formatted text output. StringTemplate is particularly good at multi-targeted code generators, multiple site skins, and internationalization/localization…

…Its distinguishing characteristic is that it strictly enforces model-view separation unlike other engines. Strict separation makes websites and code generators more flexible and maintainable; it also provides an excellent defense against malicious template authors.

True Model-View separation

So what does StringTemplate mean by “enforces model-view separation unlike other engines”? Well, for starters… string templates are not compiled C# classes like ASPX pages. They utilize a highly simple attribute container (similar to ViewData in a controller). There is no variable instantiation in templates. All data processing must take place *before* you get into the template. The only logic that is allowed inside the templates are simple conditional statements and foreach loops.

Now, before you hit the back button on this article, you might want to really consider what you use ASPX pages for anyway. When it comes down to it, if you remove the data calls from a server control, all that’s left are attributes, looping, and conditional statements. In other words, logic that is UI specific.

So, when it comes down to it, they only thing you actually lose when giving up access to C# code in your template … is the ability to hack business logic into your UI layer. This would be a good thing. StringTemplate removes the temptation, because it removes the ability.

My next step was to construct a view engine for string templates that plugs into the MVC framework. Following the lead of the MVC team, I extended the IViewEngine and IView interfaces and came up with a pretty simple one.

My requirements for a ViewEngine were as follows:

Templates must be cached.

Cached templates must be refreshed based on a File System change. Otherwise development is a real pain.

For enhanced performance, templates must be rendered to the Output stream, and not generate a monstrous string that gets dumped to the Response.

Little did I know, those requirements would be easier to meet than I thought. After poking around in the StringTemplate documentation, I found out that templates are cached natively, and utilize a FileSystemWatcher to detect changes. In addition, the object that renders templates can dump to a string, as well as a Stream. If only all of my coding endeavors worked out this way.

I wonât dig too deep into the details, but the implementation was pretty straightforward. Rather than blog about it, Iâll just let the code speak for itself. I created a Google Code project to host my ViewEngine. Feel free to download and kick the tires.

Template Basics

As mentioned on the previous page, all the data needed by the templates must be put in the ViewDataDictionary prior to being rendered. The ViewEngine takes everything from the calling controller’s ViewData dictionary and populates the data container of the template. In addition, the engine will add the HttpContext to the container so it’s accessible within the template.

ViewData Objects

Items in the ViewData dictionary can be accessed by placing them between dollar signs.

<h1>$message$</h1>
<div>$employee.name$ has an Id of $employee.id$</div>

As you can see, the template engine doesn’t care about type, or case. It just calls ToString() on the last property in the qualifier and renders it.

Conditional Statements

Conditions can only evaluate true or false. Thus, you cannot say things like if(employee.username == “super.user”), etc. This may seem like a huge missing feature, but it’s actually an excellent way to enforce model-view separation. With the UI only understanding true or false, it makes it so you’re not hardcoding ids, values, and other general business logic into your templates. Rather than if(employee.username == “super.user”), you could create a ViewData variable called “IsSuperUser” and set it to true. This way, the template doesn’t know what constitutes a “super user”, it only knows to turn sections of the page on and off.

A variable called “it” will automatically be created for a collection. You can override this by putting the following:$employees:{Â employee |Â $employee.name$ has an Id of $employee.id$ }$

You can call other templates inside of a loop.

MasterPages and UserControls

Unlike ASPX pages, string templates don’t have the concept of MasterPages and UserControls. On the contrary, they’re constructed with the idea that each template doesn’t care what “kind” of template it is, it only cares about rendering itself.

Sub-templates : The User Control Equivalent

Much like a user control, an external template can be nested inside of any template. Let’s use the example of a navigational menu. Take the following HTML page, and the subsequent template file:

When the parser hits line 7 above, it will render the contents of the template at “/views/shared/menu.st”. Sub-templates are included by using the construct of $()$. Templates can be nested to any level.

Passing arguments to a sub-template

Arguments from any template can be passed down to sub templates. Let’s take a look at an example. Let’s pretend we wanted to reuse our menu control, but wanted to add the option to put another link at the bottom for certain pages. First, we add a variable call to our menu file.

Notice the variable placeholders for “title” and “content”. You can define as many of these placeholders as you’d like, and name them anything. If variables are not specified by the calling template, the engine will just ignore them. Notice that calling a locally scoped variable is no different than calling an object from the ViewData dictionary. Here’s what the child template might look like…

As you can see, the entire template is a sub-template call. When you render the child template, the first thing it does is load the sub-template, and fill in the holes. As it uses the same construct as the “include” sub-template calls, you can nest them “up” the chain as many levels as “down” the chain. This gives you a massive amount of flexibility in terms of template reuse.

Other Tips and Trickery

StringTemplate has a small handful of other commands, but the ones listed on the other pages of this article will give you a good head start. Included on this page, however, are some other noteworthy items.

Dynamic Template and Variable Calls

You can dynamically call variables and templates with the construct of$variable_name.(dynamic_variablename)$. The same is true for templates:$(dynamic_variablename)()$. This comes in extremely handy when the name of the template isn’t static, such as dynamically rendered forms, etc.

Escape Character for the $ delimiter.

\$, will evaluate to the literal dollar character, instead of starting a StringTemplate command.

Cheat Sheet

The String Template documentation has aÂ good reference sheetÂ for those who are already familiar with the product.