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: , , ,

14 Responses to “Late-bound Eval - email templates revisited”

  1. Chris Cana; Says:

    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

  2. Chad Lee Says:

    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?

  3. Louis Says:

    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.

  4. Ben Cherry Says:

    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…”).

  5. Louis Says:

    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. :)

  6. Ben Cherry Says:

    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.

  7. Louis Says:

    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.

  8. Ben Cherry Says:

    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!

  9. A4Y Says:

    thanks very usefull information !

  10. Oahu Vacation Seeker Says:

    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.

  11. email templates Says:

    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.

  12. Oleta Rathgeb Says:

    Try & Retain your iPad for Nothing! -> http://bit.ly/cFBuis

  13. Jacquiline Kobara Says:

    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.

  14. mariano Says:

    Nice one… did not know that….anyone has link to other similiar stuff ? thx Mariano

Leave a Reply