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;
    }
}