DCSIMG
Extending your C# application with IronPython - berniea

berniea

Extending your C# application with IronPython

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.

string code = @"100 * 2 + 4 / 3";

ScriptEngine engine = Python.CreateEngine();
ScriptSource source =
  engine.CreateScriptSourceFromString(code, SourceCodeKind.Expression);

int res = source.Execute<int>();
Console.WriteLine(res);

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:

ScriptEngine engine = Python.CreateEngine();
ScriptRuntime runtime = engine.Runtime;
ScriptScope scope = runtime.CreateScope();

string code = @"emp.Salary * 0.3";

ScriptSource source =
  engine.CreateScriptSourceFromString(code, SourceCodeKind.Expression);

var emp = new Employee { Id = 1000, Name = "Bernie", Salary = 1000 };

scope.SetVariable("emp", emp);

var res = (double)source.Execute(scope);

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.

image

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:

var saleBasket = new SaleBasket
{
  Lines = new List<Line>
   {
    new Line { ProductName = "Prod1", ProductPrice = 100, Quantity = 2, Amount = 100 * 2},
    new Line { ProductName = "Prod2", ProductPrice = 20, Quantity = 1, Amount = 20 * 1},
    new Line { ProductName = "Prod3", ProductPrice = 45.8, Quantity = 2, Amount = 45.8 * 2},
    new Line { ProductName = "Prod4", ProductPrice = 3.9, Quantity = 10, Amount = 3.9 * 10},
    new Line { ProductName = "Prod5", ProductPrice = 555.5, Quantity = 10, Amount = 555.5 * 10}
   }
};

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:

string rootDir = AppDomain.CurrentDomain.BaseDirectory;
string rulesDir = Path.Combine(rootDir, "Rules");

var files = new List<string>();


foreach (string path in Directory.GetFiles(rulesDir))
  if (path.ToLower().EndsWith(".py"))
     files.Add(path);

If we want the ScriptRuntime to be able to recognize our own classes we need to load the assembly into the ScriptRuntime. For example:

string path = Assembly.GetExecutingAssembly().Location;
string dir = Directory.GetParent(path).FullName;
string libPath = Path.Combine(dir,"berniea.ironPython.extending.ClassLib.dll");

Assembly assembly = Assembly.LoadFile(libPath);
runtime.LoadAssembly(assembly);

Without doing so our IronPython scripts will not recognize classes from our own domain. Like the following script:

from berniea.ironPython.extending.ClassLib import *

for line in saleBasket.Lines:

if line.ProductName == 'Prod1':
discount = line.Amount * 0.2
line.Amount = line.Amount - discount
print 'discount given: ' + discount.ToString()

if line.Quantity >= 10:
line.Amount = line.Amount - line.ProductPrice
print 'discount given: ' + line.ProductPrice.ToString()

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.

scope.SetVariable("saleBasket", saleBasket);

foreach (var file in files)
{
   source = engine.CreateScriptSourceFromFile(file);
   source.Execute(scope);
}

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.

Attachment: Src.zip
Posted: Dec 04 2008, 03:21 AM by berniea | with 17 comment(s)
תגים:,

Comments

Russell said:

This gives a good introduction to using IronPython (thanks for that!), but I'm not quite so clear on the "Why".  I love Python, but I have trouble seeing why you'd want to mix Python with C#, other than to show you can do it.  Why can you do this way that you couldn't do as easily with C# (plus Spring.NET or Unity for dependency injection, etc)?

I'm sure the answer is obvious and I'll probably see it as soon as I submit this comment, but for now it escapes me.

# January 8, 2009 2:55 AM

BradJ said:

This is just what I was looking for. Thanks!

# February 26, 2009 8:18 PM

Nettlive.com said:

Pingback from  Nettlive.com

# March 8, 2009 12:08 PM

Joe said:

I agree with Russel.

# April 28, 2009 2:25 AM

edvaldig said:

Russel and Joe:

Adding scripting support to your applications depends on your situation. Usually you have no use for it at all. For some cases though, you want to be able to change e.g. some logic/rules/calculations without having to recompile your application, scripting is essential. This is what most game developers are doing. After making a game engine which handles all the heavy work of rendering, networking, resources etc. much of the game logic can be scripted in a (higher level) scripting language. Python code is also much more readable and more compact than c# code, so scripts become easy to read and write even to people in your team that usually don't program.

# June 24, 2009 4:38 PM

edvaldig said:

By the way berniea, I believe this is called embedding, not extending:

www.twistedmatrix.com/.../extendit.html

Extending would be to write modules in e.g. C/C++ which can be loaded into python, where Embedding is calling python code from within e.g. your C# code.

# June 24, 2009 4:48 PM

Vadim said:

Thank you very much for taking your time to write this well written article how to integrate IronPython and C#.

I just started playing with IronPython and find your post one of the most usefull resources on the web.

# August 31, 2009 9:50 PM

Fabzter said:

It's both. He is extending when he exposes classes and objects to IronPython.

# April 2, 2010 1:55 AM

zezito said:

Man great work!

very very helpfull!

Thanks

# September 3, 2010 1:58 PM

vinay said:

Anything that would be "Elegant enough" but would not cause performance penalties??..

# September 22, 2010 7:33 PM

Chris Pietschmann said:

Intro to IronRuby/DLR Scripting in C# Silverlight 4 Application

# September 29, 2010 7:44 PM

Intro to IronRuby/DLR Scripting in C# Silverlight 4 Application | Science Report | Biology News, Economics News, Computer Science News, Mathematics News, Physics News, Psychology News said:

Pingback from  Intro to IronRuby/DLR Scripting in C# Silverlight 4 Application | Science Report | Biology News, Economics News, Computer Science News, Mathematics News, Physics News, Psychology News

# September 29, 2010 8:38 PM

Python eigentlich aktuell? - Seite 2 - Forum Fachinformatiker.de said:

Pingback from  Python eigentlich aktuell? - Seite 2 - Forum Fachinformatiker.de

# December 21, 2010 2:19 PM

Terry Barnes said:

I'm trying to execute a script but it imports the subprocess.py file (which is in the IronPython/Lib folder.

How do I set the engine up to reference this file as I currently receive an error stating "No module named subprocess".

I'm new to Python and IronPython and struggling to find how to do this. Any help appreciated as this is the best article I've found on a similar topic. Thanks.

# July 2, 2011 5:39 PM

IronPython ?? ???????????????????? ???? C# « IT ?????????????? said:

Pingback from  IronPython ?? ???????????????????? ???? C# &laquo;  IT ??????????????

# September 21, 2011 11:03 AM

Implementing a scripting language in C# - Programmers Goodies said:

Pingback from  Implementing a scripting language in C# - Programmers Goodies

# October 24, 2011 3:30 PM

IronPython by stevekr - Pearltrees said:

Pingback from  IronPython by stevekr - Pearltrees

# January 8, 2012 9:29 AM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: