UserScript
UserScript is an easy to understand, expandable scripting language built on C#. The goal behind UserScript is to allow end-users to easily input complex templates that then get filled with data. A simple example might be a mail merge, where each message should begin with a personalized message to the recipient (Like, for instance, "Hello, Tim!").The syntax of UserScript is similar to some other formatting frameworks, such as the .NET Framework's string.Format(string, object[]) method. Rather than receiving numeric indexes of arguments, however, UserScript accepts full object names.
To understand the UserScript syntax, we'll go over a few examples. Although multiple options are allowed to set data fields, examples here will utilize the reflection-based method.
Let's say you're developing an application to receive donations for a non-profit organization. You need to send out a simple thank you message to anyone who donates through your website. You run multiple campaigns and each should have a unique thank you message sent out. In the past, a solution to this might be using string.Replace on a template, having users enter keywords and storing templates in a database using proprietary formats, or even hard-coding and changing code with the creation of new campaigns. However, UserScript makes this easy. Have users enter a string template when they first create the campaign, then call the appropriate format method for your use. That's it.
Starting simple, any string is acceptable. The user could enter the string,
Hello! Thanks for your donation!
and no action would be taken. UserScript would parse it, find no expressions in it, and return the string as it came in. This means that users don't have to be constantly aware of the fact that they are using a programming language.
But that's not very personal. We want to include the donator's first name in the greeting. Chances are good that you'll already have a type like this defined, but if not, anonymous types work great too.
publicclass Donation { publicstring FirstName { get; set; } }
We'll add a bit to it later. For now, we simply want to use the FirstName property. After introducing your user to the UserScript syntax and the set of fields available to them, they'll be able to enter a template similar to the following:
Hello {firstname}! Thanks for your donation!
When you pass that template and an instance of the Donation class with FirstName set to "Mike" to the BasicStringHelpers.ReflectedFormat method, the result will read, as expected, "Hello Mike! Thanks for your donation!"
Better, but what if they prefer to remain anonymous? You might think to use two templates, but that makes it more difficult for users to make changes, and adds complexity. Rather, you can add a property to your Donation type:
publicbool IsAnonymous { get; set; }
Then the user can input, for example,
Hello{if(IsAnonymous,""," " + FirstName)}! Thanks for your donation!
Yep, UserScript supports functions too. Here, we call the built in method if(bool, object, object). Just as expected, it tests the bool value, then returns the first value if it's true and the second if not. Thus, if the user prefers to remain anonymous, we say "Hello!..." and if not, we say "Hello Mike!" You'll notice that we also perform a string concatenation in the second argument, adding in a space.
You could accomplish a similar result without an extra property by storing the first name as null. If you did that, the user could enter the following template to achieve the same result:
Hello{if(FirstName = null, "", " " + FirstName)}! Thanks for your donation!
Again, pretty straight-forward.
That's the gist of it. You've seen properties, string literals, functions, and binary expressions. Keep in mind that UserScript is case insensitive. But just for fun, we'll make a more advanced template now.
Consider we've added another property to the type, a decimal called DonationAmount.
Hello{if(IsAnonymous, "", " " + FirstName)}! Thanks for your{if(DonationAmount > 50.0, " generous")} donation of ${format(DonationAmount, "N2")}!
This is starting to get a little more realistic now. As you can likely read, we have three fields here:
- The first name, if the user chooses not to remain anonymous. This is the same as before.
- We check to see if the donation amount is over $50 and call it "generous" if it is (one overload of the if function doesn't accept a false-case and doesn't print anything if the bool is false)
- We print out the donation amount, formatted according to the built-in .NET formatting syntax of N2, a numeric with two decimal places printed.
In a single string, we've managed to handle several cases, and you never had to know anything about the template itself.
It's all super simple, and users can get the hang of the syntax with relative ease.
An important note, however, is that templates containing curly braces should be escaped the same way as in string.Format, using double-curly-braces.
Also, a note for best practice, I like to wrap my argument to the reflected method in an anonymous type in order to avoid any refactoring errors that affect the template. For instance, I might call the previous example with the following code:
Donation d = ...; string message = UserScript.BasicStringHelpers.ReflectedFormat(template, new { firstname = d.FirstName, isAnonymous = d.IsAnonymous, donationAmount = d.DonationAmount });
That way, any changes that might occur down the road with the Donation type won't go unnoticed.