This post is part of the “MIX10 – Pumping Iron on the web” series:
- Part 1 - Introduction
- Part 2.1 – Server-side web development with dynamic languages
- Part 2.2 – Client-side web development with dynamic languages
- Part 3 - Using scripting in static applications
- Coming soon: Part 4 - Web-app extensibility with scripts
The original post was mistakenly removed, so it’s been reposted with the original post date, 4/27/2010. Sorry if this confuses your blog readers or causes any other inconvenience.
Up until now I've discussed how to use dynamic languages to power both the server-side as well as the client-side of your web-application, but what if you want to apply these methods to solve certain problems in an existing application?
Testing
A low-risk, high-benefit use of dynamic languages in your existing applications is for testing. This helps make the act of writing tests simpler, and quite possibly more fun, encouraging your team to actually maintain the test suite.
Before looking at how to test web-apps, let's take a brief look at what a test written with RSpec, a popular Ruby testing framework, looks like:
Note: there are Ruby testing frameworks that look a bit more like what you might be used to. The following is an equivalent test written with test/unit, and this will give you a better idea of the structure of the above example:
The RSpec snippet almost reads like english, making it very clear what the intended behavior of Stack is. Also, it shows the power of Ruby for creating internal DSLs; a "language" built out of the constructs of an existing language. describe
and it
look like language keywords, but in-fact they are really just methods, because Ruby has optional parameters (as we discovered earlier with Sinatra). Using actual strings as the test name, rather than a method name, allows you to describe the test accurately. Each object has a should
method which makes any subsequent calls part of an assertion, making it very obvious which value is the "expected" value and which is the "actual".
The crazy thing is how little code is required to make that work; 24 lines of Ruby. The key points are that yield
executes the do-end
block passed to a method, and the should
method is added to every object, turning any subsequent methods calls into an assertion:
However, please don't use this example as your real testing framework, and then get mad at me when it doesn't have a feature you want; RSpec, Bacon, or test/spec are much more mature testing frameworks that support this same syntax.
See my previous post about using IronRuby to test C# Silverlight applications; it’s still relevant though it’s a fairly old post.
Hosting
IronRuby and IronPython are built on-top of the Dynamic Language Runtime, which is comprised of many parts, one of which being a .NET Hosting API, allowing you to embed a scripting language in any .NET app.
Now we come to the "ah-ha!" moment of the talk; everything you've seen in the MIX10 posts is made possible by this API. Keep in mind these languages are built on .NET, so their implementations are accessible from any .NET language. C# and VB today are not built on .NET; they just compile programs to run on .NET, which is why you can't easily host those languages today.
Here's the catch; since these language engines are built on .NET, they need to run in a .NET application. So, all Ruby or Python code runs by hosting the languages inside a .NET application. We do things to make this seamless in specific environments: for example, ir.exe and ipy.exe are both .NET programs which host the language and run the code in a command-line, mimicking the behavior of ruby.exe and python.exe. Here are some other popular hosts:
- ipyw.exe: runs scripts in a console-less program for Windows applications
- Microsoft.Scripting.Silverlight.dll: entry-point for Silverlight applications which run HTML script tags and scripts inside the XAP
- IronRuby.Rack.dll: run rack-based applications on IIS
- Microsoft.Web.Scripting.dll: run Python in ASP.NET
- System.Web.Mvc.IronRuby: run Ruby in ASP.NET MVC
However, we can't provide "runners" for every environment that will spring up, so we allow you to use the same APIs that these runners use in your own apps. These APIs have been kept very simple, as we want any .NET developer to be able to use a DLR scripting language in their applications.
But why embed a scripting language into your application? The main scenario is to scripts as an extensibility mechanism, either internally or as functionality you provide for your end-users. Here are a few concrete examples of what scripts could be used for:
- An advanced search/filter (letting users use LINQ safely)
- High-level business logic to computing prices of items, applying discounts, etc; any type of rules engine
- A system which changes behavior based on external data
- Customizing a single codebase for different clients
- Add-ons for end-users to make your application better (eg. Facebook)
- Making application logic simpler to read than the core of your system (polyglot)
Let's show you how to do the basics, and hopefully that will spark your imagine to think up other cool use-cases:
- Create a new web application project in Visual Studio 2010, and open the Default.aspx.cs page.
- Place a label on the page and call it “Message”.
- Add references to the necessary DLLs to host IronPython (IronPython.dll and Microsoft.Scripting.dll)
- Write the 5 lines of code to update the label’s text from Python:
There are basically three types you need to know; ScriptRuntime
, ScriptEngine
, and ScriptScope
.
-
ScriptRuntime
is a level of encapsulation for your scripts; it represents the DLR scripting runtime, and all script operations go through it. -
ScriptEngine
is the type that is returned fromScriptRuntime.GetEngine
; it represents a DLR-language. In this case, we asked for the language by name, as that's the easiest way to keep it easily configurable, but the downside is you need language configuration info in yourApp.config
. However, if you only want to depend on IronPython, you can useIronPython.Hosting.Python.CreateEngine()
, which does all the setup for Python for you.The
ScriptEngine
enabled you to execute code in that language, in a variety of ways, from the basicengine.Execute
method (essentallyeval
), or being more fine-grainedengine.CreateScriptSourceFromString(code).Compile().Execute()
, which parses the file, compiles it, and then executes it. Code can be executed against aScriptScope
to set initial state and share state between executions. -
ScriptScope
defines the state for your script; like what variables/methods are present. It is a dynamic object, so you can do things likescope.page = this
, and that will set thepage
variable for scripts that execute against the scope. In downlevel .NET frameworks, you'd have to usescope.SetVariable(page, this)
.
Slight aside: since these APIs are .NET based, the dynamic languages themselves can consume them to run other dynamic languages! For example, here's Ruby executing Python code through the DLR Hosting APIs:
What's also interesting is the dynamic languages can communicate between each-other just as easily; here's Ruby calling Python code:
Anyway, hopefully this sparks some creativity! For more web-related information, also posted about this in relation to Silverlight applications: Scripting C# apps with IronPython.
The next post will show some cool applications of this … (coming soon).