Today I vowed to learn the Spark View Engine. While I don't hate ASP.NET's MVC WebForm View Engine, its clearly outclassed by the other MVC stacks. So, given my views (hahaha) on the matter and with a wonderful greenfield project in front of me, I figured I'd do the switch.
(I think some developers still don't get the distinction between traditional ASP.NET WebForms and ASP.NET's MVC WebForm View Engine. The latter is the default View Engine used for templates in ASP.NET MVC and its confusing name was likely picked because its similar to the former.)
Going through the documentation, I was pleased to see the inclusion of conditional attributes on standard HTML element. So, instead of writing:
<% if (CurrentUser != null) { %>
<span id="loggedInUser">Hello <%=Html.Encode(CurrentUser.Name)%></span>
<% } %>
I can simply write:
<span id="loggedInUser" if="CurrentUser != null">Hello ${CurrentUser.Name}</span>
OR
<if condition="CurrentUser != null">
Hello ${CurrentUser.Name}
</if>
The next thing I noticed was the lack of unless - a statement found in Ruby which some love, some hate, and many abuse. So, I figured I'd go ahead and try to implement support for it, if nothing else it'd be a great way to get more familiar with the inner workings of the framework. The minor changes required to implement this feature, and the fact that it took less than 10 minutes for someone with no familiarity with the code, is a testament to the readability and general quality of the codebase.
All my changes were made in the main Spark project.
unless is merely an inverted if, so my plan was to pinpoint the parsing and code generation around the Spark's if attribute and if element and piggy-back on their implementation. The first part of my journey took me to the ConditionalAttributeVisitor in the Spark.Compiler.NodeVisitors namespace. I got here by doing a search for "if" (with the quotes). From the name, and the code, I figured that the purpose of this class was to determine whether an attribute was a conditional attribute, I simply added a check for "unless" to both the qualified and unqualified code paths:
if (Context.Namespaces == NamespacesType.Unqualified)
return attr.Name == "if" || attr.Name == "elseif" || attr.Name == "unless";
if (attr.Namespace != Constants.Namespace)
return false;
var nqName = NameUtility.GetName(attr.Name);
return nqName == "if" || nqName == "elseif" || attr.Name == "unless";
Next, I got lazy, ran the code with an unless and found the ChunkBuilderVisitor class within the same namespace. This class threw an exception that "unless" wasn't defined. Within the class, we can see a mapping of tokens to actions. I added a new entry:
{"unless", VisitUnless},
VisitUnless, I looked at what VisitIf did - it creates a ConditionalChunk of type If and adds it to the Chunks member. I figured the simplest thing would be to call VisitIf and switch the type of the chunk to my own type. I added a new value of Unless to the ConditionalType enum, then implemented by simple VisitUnless:
private void VisitUnless(SpecialNode specialNode, SpecialNodeInspector inspector)
{
VisitIf(specialNode, inspector);
((ConditionalChunk) Chunks[Chunks.Count - 1]).Type = ConditionalType.Unless;
}
As you can see, it lets VisitIf do all the work, and then switches the type.
I found where ConditionalType.If was being used, and came across the a CSharp Code Generator (GeneratedCodeVisitorBase within Spark.Compiler.CSharp.ChunkVisitors) and JavaScript Code Generator (JavascriptGeneratedCodeVisitor within Spark.Compiler.Javascript.ChunkVisitors). This is the code that outputs the actual C# and JavaScript. Based on the case block for If, which is:
case ConditionalType.If:
{
CodeIndent(chunk)
.Write("if (")
.WriteCode(chunk.Condition)
.WriteLine(")");
}
break;
I wrote the case block for Unless:
case ConditionalType.Unless:
{
CodeIndent(chunk)
.Write("if (!(")
.WriteCode(chunk.Condition)
.WriteLine("))");
}
break;
The change was equally straightforward for the JavaScript generator.
That took care of supporting unless as an attribute (<p unless="CurrentUser == null">...</p>), but I still wanted to add support for unless as an element (<unless condition="...">...</unless>); For this I went to the SpecialNodeVisitor class within the Spark.Compiler.NodeVisitors namespace (I had looked briefly at it when getting started). I added "unless" to the _containingNames member in the constructor.
Next I did...nothing..that was all that was needed...aside from slightly different parsing rules, attributes and elements share the exact same code paths (which pleasantly surprised me at first, but is obviously the right implementation).
The moral of the story is that if you are looking for a view engine for ASP.NET MVC, one that, at first glance, looks better than WebForms and much easier to customize, I strongly suggest you check out the Spark View Engine.
Posted
Wed, Aug 19 2009 10:41 PM
by
karl