Using Spark as a general purpose template engine
programming, spark December 16th, 2008There was a question in the discussion group about using Spark as a simple text templating engine for creating email bodies like you can do with Velocity. Maybe they meant NVelocity?
In any case I put together a small console sample to verify the use case that I thought I would share here. It shows a few of the ways you can micro-manage how the Spark engine works by providing a specific base page and replacing the IViewFolder abstraction.
using System;
using Spark;
using Spark.FileSystem;
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public abstract class EmailView : AbstractSparkView
{
public User user { get; set; }
}
class Program
{
static void Main(string[] args)
{
// following are one-time steps
// create engine
var settings = new SparkSettings()
.SetPageBaseType(typeof(EmailView));
var templates = new InMemoryViewFolder();
var engine = new SparkViewEngine(settings)
{
ViewFolder = templates
};
// add templates
templates.Add("sample.spark", @"
Dear ${user.Name},
This is an email.
Sincerely,
Spark View Engine
http://constanto.org/unsubscribe/${user.Id}
");
// following are per-render steps
// render template
var descriptor = new SparkViewDescriptor()
.AddTemplate("sample.spark");
var view = (EmailView)engine.CreateInstance(descriptor);
view.user = new User { Id = 655321, Name = "Alex" };
view.RenderView(Console.Out);
Console.ReadLine();
}
}
December 31st, 2008 at 1:46 am
Out of curiosity. If in EmailView I just had a pbulic property…
public object data {get; set;}
and set the data using an anonymous type like so…
view.data = new {user = new {first=”Phil”, Last=”Haack”}};
Could I do this in the template
Hi ${data.user.last}, ${data.user.first}
?
That would be pretty sweet for generating emails since I could have a general method that accepts a template and an object and just format the object via an anonymous initializer before passing it into the template.
Phil
December 31st, 2008 at 4:23 am
That one turns out to have a few snags…
With the csharp language you’d need to be strongly typed. One thing I looked into was creating that general method which takes the anonymous type as a template parameter, like TModel, and giving it to the EmailView base class a generic parameter.
Problem is the spark view is compiled in a dynamic assembly at runtime and loaded, and it doesn’t look like there’s a way to declare the anonymous type in the base.
class View230943 : EmailView[[???]]
{
}
—-
Another thought was to use “public object data {get; set;}” and rely on a DLR language. You can do that by adding several assembly refrences and replacing the default LanguageFactory, like:
var templates = new InMemoryViewFolder();
var engine = new SparkViewEngine(settings)
{
ViewFolder = templates,
LanguageFactory = new RubyLanguageFactory()
};
Which – much to my surprise :) – works fine except the anonymous type isn’t accessible to IronRuby. I tried a normal public object like:
public class Blah
{
public Blah2 User { get; set; }
public class Blah2
{
public int Id { get; set; }
public string First { get; set; }
public string Last { get; set; }
}
}
view.data = new Blah { User = new Blah.Blah2 { Id = 655321, First = “Phil”, Last = “Haack” } };
and that worked great so the syntax and capitalization of ${data.user.first} wasn’t a problem. Maybe because the anon type isn’t public, or that the anon type properties are readonly, that they’re not projected in the DLR type?
—-
In the end it might be simpler to use concrete types for data. :)
December 31st, 2008 at 1:48 pm
Yeah, probably. I forget that spark is actually compiling ${foo.bar.baz} into code and not doing some sort of late bound eval. Have you considered a late bound syntax?
December 31st, 2008 at 8:11 pm
Well… Sort of… But not really…
Property access would be pretty straightforward but it would only get you raw data placement. Anything else leads right to questions about invoking helpers or method invocation on the model. You’d kind of need a small reflection language interpreter.
That said – dropping in IronPython/IronRuby *was* pretty close to what you were looking for. I could see a terse variation of ${} that could rely on a dynamic language without swapping out csharp entirely – except properties on the anonymous types weren’t exposed so you’d still need a workaround for that problem.
January 3rd, 2009 at 5:43 am
Hey, wait a second! Isn’t this exactly what the C# 4.0 dynamic keyword is for?
public dynamic data {get; set;}
Especially because as Sam Ng points out:
http://blogs.msdn.com/samng/archive/2008/10/29/dynamic-in-c.aspx
“One of the design choices we made was that the runtime binder should have the exact same semantics that the static compiler has. … As such, each payload is bound exactly as the static compiler would have.”
Which would avoid problems with subtle (or not so subtle) differences between cs/rb/py/js/homemade languages. That same problem came up several times in NVelocity which has it’s own late bound syntax that brings it’s own peculiarities.
So then the answer is yes – with C# 4.0 you’ll be able to do exactly what you were asking.
January 27th, 2009 at 11:00 am
[...] back to December there was a comment on Using Spark as a general purpose template engine about the use of anonymous types for your ViewData model. Or about surfacing information in general [...]
April 16th, 2009 at 1:01 am
What’s the least amount of required config entries for that to work ? [Please Please tell me it's zero!]
April 16th, 2009 at 5:25 am
Yep, it’s zero.
The optional SparkSettings class, when passed in, contains all of the information which would otherwise come from the config section.
April 16th, 2009 at 5:44 am
Yeah I already had this up and running for a sample to use later for my own project. Blogging my own implementation also in the weekend (if God will).
Thanks a lot.
December 26th, 2009 at 11:46 am
Thank you very much man!! This will give another speed to my file generation…
It’s time to leave alone the NVelocity hehe! ;)
February 21st, 2010 at 11:03 am
[...] 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 [...]
April 16th, 2010 at 7:53 am
I love the fact that you can use Spark as a generic template renderer. Any chance you’ll add the ability to simply read from a TextReader rather than setting up an InMemoryViewFolder? Something like this would be awesome:
var view = (EmailView)engine.CreateInstance(textReader);
view.user = new User { Id = 655321, Name = “Alex” };
view.RenderView(textWriter);
then for general purpose, we could skip all the setup with descriptors, settings, view folders? Just a thought.
Great stuff.