Late-bound Eval - email templates revisited
Going 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 without being type-specific.
Out of curiosity. If in EmailView I just had a public 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}
To which I replied
Well… Sort of… But not really…
Thinking about it later I was mulling over the steps you would take to inject small DLR expressions of something like Ruby or Python. For performance you’d want to have keep and re-use the same code instance, because it trains and optimizes itself based on the types that pass through it. Most DLR expressions are ultimately run with the same type of value at most of their code points, which is how dynamic language engines can get blisteringly fast performance out of a run-time-type situation.
Then I realized something. That’s what the dynamic keyword does! It’s a tiny instantiation of a csharp DLR expression.
Hey, wait a second! Isn’t this exactly what the C# 4.0 dynamic keyword is for?
So then the answer is yes - with C# 4.0 you’ll be able to do exactly what you were asking.
And then after thinking about it even more I realized that’s also exactly what ViewData.Eval is for. The problem with ${ViewData.Eval(”data.user.last”)} is you’re not really gaining the benefit of late binding from a wrist pain standpoint. Yes, you haven’t declared the viewdata types, but taken together the repeated ViewData.Eval( ) will increase the overall template size and reduce readability.
Enter a new syntax! Within a code expression it’s never valid to have a # in front of an identifier, or series of identifiers connected by dots. That syntax is now repurposed - when it’s seen it’s treated as the string argument for a call to Eval.
Hi ${#user.last}, ${#user.first}
You can also provide a format string immediately after the late-bound expression. Like ${#foo.price '#,##0.00'}. Be sure not to put a comma between the #property and ‘format’ - the syntax doesn’t expect a comma because you could accidentally consume the next argument in a method call where a late-bound expression is used as a parameter.
There’s an example of using Spark directly for email or text templating with late-bound anonymous data in the Samples folder of the download.
Label3.Text = MessageBuilder.Current.Transform(
"Promo42b",
new
{
siteroot = "http://example.com",
user = new { id = 65321, name = "Fred", email = "fred.foo@bar.com" },
promo = new { size = 30000, msgnum = 24376, dist = "C3" },
product = new { id = 938, name = "Green Flashlight", price = 19.95, discount = 5 }
});
Here’s promo42b.spark:
Hi ${#user.name},
You opted into our spam at some point, and now that's going to pay off!
Because now you have the option of owning your very
own ${#product.name} today for the
low price of ${#product.price "$#,##0.00"}!
That's a saving of like ${#product.discount "#,##0"} bucks!!
Yeah! I know! How cool is that!?!?
Click the following link to get yourself some of that action.
${#siteroot}/addtocart?id=${#product.price}&offer=${#promo.dist}
<test if="IsInStock((int)#product.id)">
Yours is in stock and ready to fly!!!
<else/>
We can't keep them on the shelves! So get your order in today!!!
</test>
Regards,
- Spark view engine
${#siteroot}/unsubscribe/${#user.id}?source=${#promo.dist}
(I really need a different source code plugin)
And here’s the base class. For a direct usage scenario it’s “Bring Your Own Eval” so this example relies on the ViewDataDictionary.
public override void Transform(
string templateName, object data, TextWriter output)
{
var descriptor = new SparkViewDescriptor()
.AddTemplate(templateName + ".spark");
var view = (TemplateBase)_engine.CreateInstance(descriptor);
try
{
view.ViewData = new ViewDataDictionary(data);
view.RenderView(output);
}
finally
{
_engine.ReleaseInstance(view);
}
}
public abstract class TemplateBase : AbstractSparkView
{
public ViewDataDictionary ViewData { get; set; }
public object Eval(string expression)
{
return ViewData.Eval(expression);
}
public string Eval(string expression, string format)
{
return ViewData.Eval(expression, format);
}
/// <summary>
/// Members of this class are also available to the views
/// </summary>
public bool IsInStock(int productId)
{
return DateTime.UtcNow.Second % 2 == 1;
}
}
Tags: programming, spark, tech, templating
January 29th, 2009 at 7:09 am
I started using the email templating last week, was about to write my own and noticed it in the samples directory. Works a treat, great job :D
January 30th, 2009 at 1:32 pm
This is cool. I can definitely see how this is useful. I’m assuming ViewData.Eval is something specific to asp.net mvc? What would it take to get this into Monorail?
February 2nd, 2009 at 8:29 pm
The #foo.bar syntax will become a call to an Eval method on the view class, so all it would take is a
public object Eval(string expression)
and
public string Eval(string expression, string format)
method on the Castle version of the SparkView base class. The only tricky part would be breaking up the expression on the dots and evaluating it down through reflection.
April 30th, 2009 at 10:37 pm
Ooh, cool! I just found this, I’m sorry I’d missed it before. Unfortunately, it solved one problem only to create another…
This works great, but breaks when using a JavascriptViewResult and the Spark.*.RenderView client-side method (”Eval is not defined…”).
May 4th, 2009 at 4:45 pm
Yeah… A lot of the javascript generating functionality assumed you’ll be providing look-alike implementation for the code which appears on client/server templates, like html helpers, or after a certain point some templates would be client-only and jump onto the javascript language fully.
I have to admit I wasn’t really sure if people would adopt dual-use client/server templates to the extent they have been. You have to *really* want them to work. :)
May 4th, 2009 at 6:47 pm
Thanks Lou!
Yeah, I’m making very heavy use of dual-use templates, it’s actually one of the killer features of Spark for me, that drove me away from the standard MVC views or alternates like NHaml. I’m making some interesting work-arounds to get these things working right, and it’s paying off for my project.
When you mention providing “look-alike implementation”, do you mean that I can define how the Javascript Spark compiler processes certain C# lines into Javascript? This would be very useful for me, as it would allow me to implement a lot of features that are currently not able to be used in dual-use templates.
May 4th, 2009 at 9:42 pm
Nope, not what I meant but hooking into the code generation would be really nice.
By look-alike implementation I just meant things like declaring an object named Html in javascript and adding methods named ActionLink to be able to have ${Html.ActionLink(…)} work client-side.
May 4th, 2009 at 11:48 pm
Ah I see, that makes a lot of sense. It’s not quite as flexible as being able to control the code-generation directly, but it sounds like a useful approach for certain tasks. I’ll probably be using that technique, so thanks for the tip!
December 15th, 2009 at 3:23 am
thanks very usefull information !
December 23rd, 2009 at 9:15 am
Hello I discovered this website by mistake, I was surfing around the net for sites to see on Oahu when I came upon your website, I must say your site is very cool I truely think the theme, its astounding!. I’m strapped for time in this instance to totally read through your blog but I have favorited it and also subscribed for your RSS feeds. I will be back when I free up some time. Thanks for a great site.
December 29th, 2009 at 10:20 am
Thanks for writing this up, great post. I have a few clients in mind that could get a lot out of this site so I will be sending them a link. Thanks again and keep up the good work.
April 6th, 2010 at 9:00 am
Try & Retain your iPad for Nothing! -> http://bit.ly/cFBuis
August 5th, 2010 at 7:55 pm
Hmm.. Well, first of all, what VPN client are you using? I’m guessing the built in Windows VPN client. When you try to connect, what is the 3 letter error code you get? If you are using high speed Internet, do you have a router between your modem and your computer? My guess would be that the router is the culprit. If you do have a router, try connecting your pc directly to the high speed modem.
August 20th, 2010 at 10:17 am
Nice one… did not know that….anyone has link to other similiar stuff ? thx Mariano