Monday, November 17, 2008

Adding scripting to a C# Silverlight app

The Minority

At Microsoft, the people I work with and I are definitely the minority, preaching about the benefits of dynamic languages and using the right tools for the right jobs, as in don't use static languages where you don't need it -- though there are plenty of times where you need it.

And now you ask, "Jimmy, but why would I, someone who gets paid to write C#, use a dynamic language?"

The majority would only have static languages pulled from their cold, dead fingers, and I totally agree with them. Don't change for the sake of change. Though, for certain scenarios, running scripts in a VB/C# application would be useful. For example, a shopping application that has a bunch of business rules, like "when someone has three items in their cart that all have to do with cooking, give them 10% off." These type of rules can change all the time, and traditionally you'd either store the rules in a database and implement a engine to understand the rules, or hand-code them yourself and have to redeploy the system every time you want to change them.

Or, you could save yourself the hassle and store the rules as Python or Ruby code, and then host the DLR in your application to run the code. Want to update the rules? Just update the code, nothing more. And a dynamic language is probably closer to how the domain-expert would represent them as, so they could even write them. Yes, this is just one scenario, but a powerful one for existing static language developers. This was the last part of my Seattle CodeCamp talk -- how to host the DLR in your C# Silverlight application.

Starting with an existing C# Silverlight application that just takes some input and echos it back, I'll extend it to run the code through IronRuby and print the result. First, open the solution, and hit F5 to see that the app just echos what you type in the grey area. This requires you to have installed the Silverlight Tools.

image

Now add references to the DLR and IronRuby (in the Dependencies folder):

image

Let's write a little wrapper class run Ruby code. Open App.xaml.cs and add the following class to the SilverlightDLRDemo namespace:

class RubyEngine {
  private ScriptEngine _engine;

  public RubyEngine() {
    var runtime = new ScriptRuntime(
      DynamicApplication.CreateRuntimeSetup()
    );
    _engine = Ruby.GetEngine(runtime);
  }

  public object Execute(string code) {
    return _engine.Execute(code);
  }
}

Note: this exact code won't work on the Desktop; you'd have to just give the ScriptRuntime constructor no parameters. Here I use the DynamicApplication.CreateRuntimeSetup() to use the same ScriptRuntimeSetup object that Microsoft.Scripting.Silverlight uses, which knows how to map File system access to the XAP file, which is useful for dynamic languages to depend on other files. Of course, it's your decision to allow this or not.

You'll also need to add the following "using" statements:

using Microsoft.Scripting.Hosting;
using IronRuby;
using Microsoft.Scripting.Silverlight;

And that's it! That's all we need to run IronRuby code. Now let's hook it up to the page. Open Page.xaml.cs and add an instance variable to the Page class, and initialize it in the Page constructor:

// add to Page class
private RubyEngine _ruby;

// add to Page constructor
_ruby = new RubyEngine();

Lastly, replace the "Result.Text = ..." line with this, which prints the typed code, and then the result computed by the RubyEngine wrapper we just wrote:

Result.Text = "\n\n" + 
  ">> " + Code.Text + "\n" +
  (_ruby.Execute(Code.Text).ToString()) + 
  Result.Text;

Now hit F5 again, and type some Ruby into the TextBox, hit enter, and boom, you're C# Silverlight application is running IronRuby code.

image

In fact, this is exactly how AgDLR, aka the dynamic language integration with Silverlight, works -- a C# application (Microsoft.Scripting.Silverlight.dll) which hosts the DLR and runs a script file (app.rb or app.py). Take a look for yourself.

I hope that shows you how easy it is to host the DLR from a C# Silverlight application, and think twice when writing those complex rule engines. =)

6 comments:

Pete said...

This is exactly what I was looking for. You're very much right that blending static and dynamic languages has its benefits, and man, do I need this for my current app right now. However, on the concept of "...a dynamic language is probably closer to how the domain-expert would represent [rules], so they could even write them..." I've written three domain specific DLR-like languages in my career and the outcome was always the same. I'd teach the domain experts how to use the tailored language and then the domain experts wanted me to do the programming anyway. It's not their job to program, and they're not interested in it. But I think there's something going on here even more fundamental than that: the language is not the application. When you said in your post that we are "someone who gets paid to write C#", that's not true. We're someone who gets paid to create an application that simplifies and embodies expert knowledge in one form or another, and no-one really cares what language it is written in except other programming wonks like ourselves. It could be a set of well-trained monkeys trading slips of paper running the app and they still wouldn't care. The only thing they care about is how well we train the monkeys. Thanks so much for the post.

Piter Protsyk said...

Hi,

This is nice, but Iron language uses dynamic compilation and this has a number of implications.

Take a look at the following scripting solution for both .NET, CF and Silverlight.
Script.NET for Silverlight

Regards,
Piter

Jimmy Schementi said...

@Piter

What implications are you concerned about? Reflection.Emit? And how does Script.NET do it differently? Interpreted? Precompiled? The DLR will ship with an interpreter as well (IronPython is using it to do adaptive compilation currently).

That being said, Script.NET looks very cool! =) I'm curious how you solve the edit-refresh issue with Silverlight, since you need to rexap and all, or you require a build step no matter what.

Joakim said...

Great article!

Any chance you could elaborate on how to integrate with a C# program? I am a novice and I want to mix C# programming with dynamic rules as you mentioned... any pointers as to where I can find more about this?

Jimmy Schementi said...

@joakim Thanks! Check out my latest post for a more elaborate example: http://blog.jimmy.schementi.com/2009/03/scripting-c-silverlight-apps-with.html

jboggs said...

I would like to hear more about how business rules can be created in a script.

Has anyone had experience with this?