When Spark meets ASP.NET WebForms
February 21st, 2010 | Published in Short Guides
This short guide will provide a proof of concept for an alternative to the traditional ASP.NET (WebForms) templated controls & provide an insightful use for the powerful Spark template engine that hasn’t been mentioned elsewhere.
The Spark template engine is a well-documented, open source view engine for .NET, mainly used as an alternative to the “WebForms” engine built into ASP.NET MVC. It can also be used with Castle MonoRail or even in a standalone application (for example, a console application that creates emails from a template & sends them). What excites me about this particular template engine is much of its syntax is derived by adding a few extra tags & attributes to HTML, allowing for a cleaner look for your template while not sacrificing the page’s rendering performance (unlike with other templating engines). Taking the example on the front page of the Spark website, a bit of WebForms code that is written like this…….
<% (IEnumerable<Product> products = (IEnumerable<Product>)ViewData["products"]; %>
<% if (products.Any())
{ %>
<ul>
<% foreach (Product p in products)
{ %>
<li><%= p.Name %></li>
<% } %>
</ul>
} else { %>
<p>No products available</p>
<% } %>
…can simply be expressed using Spark as:
<viewdata products="IEnumerable[[Product]]"/>
<ul if="products.Any()">
<li each="var p in products">${p.Name}</li>
</ul>
<else>
<p>No products available</p>
</else>
If you’re unfamiliar with Spark & I’ve got you interested in it, it’s well worth looking at the documentation for more syntactic tricks it can perform.
One problem we’ve continuously been running up against is we’ve had to build a server control on an ASP.NET WebForms project that produces custom HTML depending on values from our model. However, because we’re building these custom server controls for a Content Management System, we don’t have access to the page object, any form of code behind or anything from the model when we need them – the only way we can define them is on an ASCX template that lacks a code behind. This rules out any conditional logic where we define the template, & results in the dreaded HTML strings written in the control’s .cs file if we wanted to do more than using simple WebForms controls.
What I thought would be nice is to have a custom control with a spark template as its children, with the code for the control only dealing with fetching the domain model & assembling a view model. Something like:
<sc:MySparkControl runat="server">
<ul if="products.Any()">
<li each="var p in products">${p.Name}</li>
</ul>
<else>
<p>No products available</p>
</else>
</sc:MySparkControl>
The great thing is this idea is possible to implement! What you need to do is create a new control that is derived from System.Web.UI.WebControls.Literal. What you then do is very similar to what you do when running spark from a standalone application. The main difference is the template is the Text property of the control:
public class Product
{
public Product(int id, string name)
{
ID = id;
Name = name;
}
public int ID
{
get;
protected set;
}
public string Name
{
get;
set;
}
}
public abstract class MyViewModel : AbstractSparkView
{
public IEnumerable<Product> products { get; set; }
}
public class MySparkControl : Literal
{
protected override void OnPreRender(EventArgs e)
{
/* Set up spark with its settings, with an in-memory view
folder since we don't wish to use any external files */
SparkSettings settings = new SparkSettings().SetPageBaseType(typeof(MyViewModel));
InMemoryViewFolder templates = new InMemoryViewFolder();
SparkViewEngine engine = new SparkViewEngine(settings)
{
ViewFolder = templates
};
/* Add template - note we still need to specify a name even
though everything is in memory */
templates.Add("template.spark", this.Text);
// Render template
SparkViewDescriptor descriptor = new SparkViewDescriptor().AddTemplate("template.spark");
StringWriter output = new StringWriter();
var view = (MyViewModel)engine.CreateInstance(descriptor);
IEnumerable<Product> myProducts;
// Fetch the products at this point & put them in myProducts
view.products = myProducts;
view.RenderView(output);
this.Text = output.ToString();
base.OnPreRender(e);
}
}
Here I’ve also thrown in a view model & a basic product class for the sake of demonstration.
Provided you fill in some code to initialise the myProducts IEnumerable & do all of the web.config setting up for this control, it will give you a list of the names of the products given. No HTML written in C# strings, & you have full flow of control from within your template with the power & ease of the Spark syntax. The one drag I’ve discovered is you cannot use the traditional <%= %> notation from within the control template, but I personally feel this is a minor drawback as the alternatives offered by Spark (!{ } for without HTML encoding or ${ } with it) are much neater.
For my purposes this implementation will do the job fine. But there are additions that I invite any reader to implement. First of all, what if we can bind this control to a WebForms data source, such as a LINQ, SQL, or Object Data Source control? This may mean we can even have a rival to the GridView, Repeater, etc. on our hands. Another nice idea may be to have the ability to specify the location of an on-disk template as a parameter to the control. This idea of a template on WebForms tag isn’t just limited to Spark – there are other view engines you may well be able to carry this to, such as NHaml, NDjango, NVelocity & Brail.
Happy templating!