Recently I’ve started messing around with IronPython; IronPython is the first language of a set of languages that rely on the DLR (Dynamic Language Runtime), the DLR is a hosting platform and a dynamic type system, capable of running dynamic languages on top of the CLR.
I really like the concept of a dynamic language, although it isn’t such a new concepts, I think it will be revived and gain more audience with the upcoming .Net Framework 4.0. One very useful thing you can do with IronPython (or any other language on top of the DLR) is to use it as an embedded scripting engine in your.Net application.
What can we gain from such a scripting engine?
I think that in almost every enterprise application project at some point you’ll come to the conclusion that you need extension points in the system. It can be a Rule Engine, a Workflow of some sort or a pluggable User Interface. So now you want to give someone else the ability to extend or modify the application’s behavior with a minimal effort and time. Of course it’s possible to achieve this with custom tools and code generators that compile and load at runtime, but these solutions are complex and time consuming. Utilizing the DLR as an embedded scripting engine is easy and for my opinion quite elegant (although this “magic” isn’t totally free, it comes with a certain performance penalty).
Embedding IronPython in a C# application
A very common scenario would be to allow a user to write some calculation expressions.
As you can see in this example I use the ScriptEngine to execute an expression that can easily be provided from various external sources. The ScriptEngine represents the language Hosting API (in this case the IronPython implementation on the DLR) It serves as the base entry point for embedding any DLR based language in other applications. The ScriptEngine can be used to create a ScriptSource and a ScriptScope which are used to execute the IronPython code.
In many cases we would like this calculation to operate on entities from our own domain, for example:
In this example I also needed to define a ScriptScope object the ScriptScope object is an execution unit for code. The host can set or get variable values from it and more…
These are very simple scenarios where we have expressions coming from an external source (which can be a file, database etc.). Another possible implementation could be to use this within a Business Rule Engine when each expression / code snippet / .py file, becomes an external business rule.
You’ll see in the code sample that I’ve added a class library named berniea.ironPython.extending.ClassLib I’ve created two classes that will serve us in the following sample, SaleBasket and Line as you can see in Figure 1.
Figure 1 – Sample Classes
As you can see SaleBasket hold Lines and has a Total, the total is the Sum of Lines Amount. The Line Represents a product added to the basket, the Line class holds the Product’s name, Price and Quantity of items added. The Amount property represents Price * Quantity.
As in the previous samples first I’ve created the ScriptEngine, ScriptRuntime and ScriptScope. The I’ve instantiated a new SaleBasket with some lines:
I’ve added a directory to store rules under the console application. We can store py files in this directory, when we wish execute the rules we can extract them from our Rules directory as shown in the following sample:
First getting all the files is quite easy; we can do something like this:
var files = new List<string>();
If we want the ScriptRuntime to be able to recognize our own classes we need to load the assembly into the ScriptRuntime. For example:
Without doing so our IronPython scripts will not recognize classes from our own domain. Like the following script:
You can see here that I’m importing all classes from berniea.ironPython.extending.ClassLib by using import * (this is similar to adding a “using” directive in a C# program). Then I use a “for loop” to iterate all the lines in the saleBasket in order to apply some business logic on them.
After all this preparations the rest is quite easy we need to set the “saleBasket” variable in the scope and execute all script files.
That’s about it, we have created an extensible rule engine which we can easily add new rules to and modify old ones without changing the core functionality of our application.
The samples I gave here are just the tip of the iceberg, there is a lot more that can be done with an embedded scripting engine in your C# application from writing your own DSL’s, enable plug-ins to your applications and allow other people to extend the applications UI. The nice thing about it is that you could actually build an entire new application using IronPython and very easily hook it to an existing application.
A word of caution, using these techniques is not a substitution for a good design. One very possible mistake could be to build an entire application around the ScriptEngine. I think that this approach will lead to a very messy and hard to maintain application. You need to carefully consider where you want to enable extension points in your application and only there provided the means.