We’ve recently started using Spark View Engine for some ASP.NET MVC projects here at Logos. To me, the syntax feels cleaner than ASP.NET views, probably because it’s more markup and less embedded code. One thing that bugged me was that in order to use MVC form helpers I had to go back to embedding code.
# using (Html.BeginForm()) {
Email: <input type="text" name="email" />
# }
Some searching of the Internet revealed I wasn’t the only one who found that unpalatable. Unfortunately, the only suggested solution, using Spark’s completely undocumented bindings feature, doesn’t actually work. While reading the Spark code (man, I love open source) I found that there is an extension mechanism. I read some more code and figured out how I could use it to make a “using” extension that would allow me to avoid embedding code when I want to use using.
What I want is an element like this:
<logos:using form="Html.BeginForm()">
Email: <input type="text" name="email" />
</logos:using>
When the Spark compiler encounters a logos:using element node, it should replace that node with a statement node that generates the correct “using” C# code and begin a code block. When the compiler encounters the end element, it should close the block. The following extension accomplishes this.
UsingExtension.cs:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Spark;
using Spark.Compiler;
using Spark.Compiler.ChunkVisitors;
using Spark.Compiler.NodeVisitors;
using Spark.Parser.Markup;
namespace Logos.Utility.Web
{
public class
UsingExtension : ISparkExtension
{
public
UsingExtension(ElementNode node)
{
m_node = node;
}
public void
VisitNode(INodeVisitor visitor, IList<Node> body, IList<Chunk> chunks)
{
if (visitor is
ChunkBuilderVisitor)
{
// build a list of
expressions from the node attributes
List<string>
expressions = m_node.Attributes
.Select(attribute =>
string.Format("var {0} = {1}", attribute.Name, attribute.Value))
.ToList();
// transform this
node and its body into a new list of nodes to visit
List<Node> newNodes
= new List<Node>();
newNodes.AddRange(expressions
.Select(exp =>
(Node) new StatementNode(string.Format("using ({0}) {{", exp))
{
OriginalNode =
m_node
}));
newNodes.AddRange(body);
newNodes.AddRange(Enumerable.Repeat((Node) new StatementNode("}"),
expressions.Count));
// visit the new
nodes normally
visitor.Accept(newNodes);
// keep the output
chunks to render later
m_chunks = chunks;
}
}
public void
VisitChunk(IChunkVisitor visitor, OutputLocation location, IList<Chunk> body,
StringBuilder output)
{
if (location ==
OutputLocation.RenderMethod)
visitor.Accept(m_chunks);
}
private readonly
ElementNode m_node;
private IList<Chunk>
m_chunks;
}
}
Now we just need to tell the SparkViewEngine how to use our extension. To do that we need a factory.
LogosSparkExtensionFactory.cs:
using Spark;
using Spark.Compiler.NodeVisitors;
using Spark.Parser.Markup;
namespace Logos.Utility.Web
{
public class
LogosSparkExtensionFactory : ISparkExtensionFactory
{
public const string
Prefix = "logos";
public
ISparkExtension CreateExtension(VisitorContext context, ElementNode node)
{
string name =
node.Name;
if
(!name.StartsWith(Prefix + ":"))
return null;
name =
name.Substring(Prefix.Length + 1);
if (name == "using")
return new
UsingExtension(node);
return null;
}
}
}
This is what my Application_Start() looks like:
protected void
Application_Start()
{
AreaRegistration.RegisterAllAreas();
SparkViewFactory
sparkViewFactory = new SparkViewFactory
{
Engine =
{
ExtensionFactory =
new LogosSparkExtensionFactory()
}
};
ViewEngines.Engines.Add(sparkViewFactory);
RegisterRoutes(RouteTable.Routes);
}
What I’ve presented here is actually a simplified version of what I ended up with. I extended UsingExtension with FormExtension, which has attributes for setting up the form and results in a view file that looks even more like markup and less like code. I also created some other extensions for other helpers.
<logos:form controller="User" action="Update" routeValues="new { id = 123 }">
Email:
<logos:textbox for="Email" />
</logos:form>
Now we have a small library of commonly used custom controls, of a sort, to help keep our presentation layer looking presentable.
Posted by Dave Dunkin on May 04, 2010