Adding Syntactic Sugar to the Spark View Engine

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.

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

8 Responses to Adding Syntactic Sugar to the Spark View Engine

  1. Adam says:

    @karl

    Intellisense does work with if=”” statements. You must however have both quotes present first.

    I also found you must also make sure you have the tags in _global.spark file for the intellisense to pick up different methods, same as you would’ve added them to web.config in webforms view engine.

    Enjoy.

  2. Karl,

    Nice work on unless – I’m getting close to binning WFVE on current project in favour of spark for the very reasons you discuss ( and the built in support for theming views ).

    The reason the WFVE is called that is because under the hood it actually uses web forms to do the view rendering. The WFVE is actually little more than a shim around the Page class. IMHO MS made a horrible mistake in this and should have built a proper view engine that worked similar to web forms but was targetted at MVC ( rather than hacking MVC support onto web forms ).

  3. karl says:

    Note to self, refresh more often to see if Troy already answered 20 minutes earlier..

  4. karl says:

    @Rob:
    I can see where UI guys might have problems with it. You can forgo embedding attributes in HTML tags and use the spark elements, is also allows you to specify a namespace, so instead of you would need to do

    Unless I didn’t set it up right (which is possible) intellisense support is average at best. You largely only get it withint ${…} blocks (so to answer your question about intellisense within if conditions: no).

    There IS a usable view compiler that you could use in your build script. The website has the details:
    http://sparkviewengine.com/documentation/precompiling

  5. Troy Goode says:

    @Rob:

    “A point I can’t remember, but do you have access to intelli-sense when writing inside a Spark if statement?”

    There is a separate tools installer that installs an add-in for VS that gives you Intellisense.

    “And how does the asp_net compiler cope with it. We always run this as part of the build to ensure the aspx’s are valid. What if I write an invalid piece of Spark, will it pick up on that?”

    You can precompile your views either as a step in your build, or even in a unit test!

    http://www.sparkviewengine.com/documentation/precompiling

    I <3 Spark =)

  6. Rob says:

    One of your points (the first one) is the reason I’m not too fond or Spark.

    We did look at it briefly at Huddle, but the html-like way of writing conditions didn’t sit well with some of our UI experts. Ok so it’s a little messy writing statements in ASP MVC, but it clearly separates the html from the view logic.

    It’s very much like Wicket in the Java world. Being able to nest “if” statements inside an HTML tag, I think, is quite nasty. It just seems like a step back to the Webforms days with runat=server cloggging your elements.

    A point I can’t remember, but do you have access to intelli-sense when writing inside a Spark if statement? A simple, yet handy thing to have.

    And how does the asp_net compiler cope with it. We always run this as part of the build to ensure the aspx’s are valid. What if I write an invalid piece of Spark, will it pick up on that?

  7. Chad says:

    Another vote here for the spark view engine.

  8. John Sheehan says:

    Great article Karl. I hope you’ll submit a patch or pull request to get this incorporated in the main project. I would definitely use this (I’ll patch if I have to but I’d love to see it in the trunk).