Make your .Net application support scripting - a practical approach

Summary

Adding scripting support to your application is one of the most valuable things you can do for your client, letting them add value to your software, and keep it current over time with little or no overhead from the developers. Your users will be able to modify behavior at runtime, change business rules as the market changes and fix subtle bugs as they appear until better fixes come along in the form of compiled code. It is one of the most powerful techniques today employed my many varied business applications. But guess what? Its not very easy to do in .Net. In this article Ill show you how you can use some of the techniques of the past mixed with the .Net framework to add that scripting ability to managed applications, with a touch on a subject that was never considered for scripting: WebServices , including asynchronous calls to them.

 

Scripting in the old days

How was scripting performed in non managed applications? For Visual Basic 6.0 applications, this usually entailed referencing a Microsoft supplied control library the Microsoft Script Control, which provided the developer with a simple object model on which they could call some generic functions. For example Eval(expression) evaluated an expression either in VBScript on in Jscript and returned a result, or Run(ProcedureName,Params) to execute a specific procedure .

The developer could also add pieces of script into the scripting memory at runtime, for example, reading whole script files containing procedures and modules which could then later be called at runtime. Methods such as AddCode(text) took care of this task and were one of the main entry points to scripting applications.

 

Finally, one of the most powerful features in the Microsoft Script library was the ability of the application developer to expose whole object models into the script runtime including all their properties and methods. These objects could later be referenced in the scripts at runtime and manipulated just as if they were declared in the script itself. This allowed amazing features for many applications. The client could literally program against an API exposed by the application runtime, an API that could be just as powerful as the application that was hosting it.

This amazing feature was achieved using the AddObject(name,object) method of the script control and will forever be remembered in my heart as one of my favorite development tasks over the years.

Examples from nowadays

Many applications today take advantage of this ability. For a simple example take a look at an automated build tool called FinalBuilder, or another build tool called VisualBuild Pro. Both of these tools provide a programmable API which exposes events such as BeforeBuild and AfterBuild which can be handled in either VBScript of Jscript. These are perfect examples of when you would need to allow user customization of your application and scripting is a perfect way of doing this.

 

Scripting options today

Today in .Net there are basically two ways to go about adding scripting to your managed application:

  • You could use reflection and CodeCompiler objects at runtime, build .Net code on the fly, compile it and run it. This is a relatively well known method, yet rather costly to do and hard to maintain and debug. Its also too sophisticated to master in the short time spans developers are expected to churn out software these days. There have been some third party attempts (some successful) in providing a generic scripting environment for .Net. Alintex Script is one of those third party products that Im guessing uses this technology. Its still hard to accomplish on your own. Harder than it should be.
  • You could use the Microsoft.Vsa namespace object model to create in memory .Net scripts and compile them at runtime. This is the “preferred“ way to do this but it leaves a lot to be desired in terms of usability and object model design. In short, it's hard to understand and hard to learn and maintain.
  • You could look back and use the good old Microsoft Script Control in your managed projects. This is the solution Ill be focusing on in this article. The main reasons to use this solution are:
    • Its very easy
    • It holds the 80/20 rule. 80% of the features you would look for in scripting your application can be provided using it.
    • Its been tried and tested over a long period of time
    • Easier to maintain, even if a developer with less experience comes along, its easier for them to understand and maintin, since its a pretty small and simple amount of code overall, compared to the first method.

Using the MSScript control in .Net

Its pretty easy to add basic scripting to your application. The first steps will usually be:

  • reference the Microsoft Script Control library
    • In your project (Ill assume its a Winforms project) open the references dialog and select the COM tab.
    • Find the Microsoft Script Control reference and add it to your project
  • Create a script object to work with
    • Declare an object of type MSScriptControl.ScriptControlClass and instantiate it
    • Initialize the scripting language to be used with the object (Either VBScript or Jscript. Ill assume VBScript for the rest of this article
  • Try the simplest scripting task
    • Add a multiline textbox to your main form
    • Add a button with Execute caption and use this code in the Click event of the button:

    Private Sub cmdExecute_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) _

Handles cmdExecute.Click

 

        txtResult.Text = script.Eval(txtResult.Text)

    End Sub

    • script is the name of the script control object and txtResult is the name of the TextBox object.
    • Run your application.. When the window opens, write the following in the textbox:  2*2. Then press Execute. See what happens.
    • Now try writing Msgbox Hello, Scripting world! and press execute

 

Congratulations. Youve just added simple scripting abilities to your application. Sure, its not much, but hopefully youve seen just how powerful you client can get when using scripting.  You should really get a good grasp of VBScript and its abilities to see all the things you can do. But wait.

 

Were not done yet. This is only the beginning. Now its going to get really interesting.

 

Exporting your own .Net objects to the party

One of the things most people dont realize is that eve though this is managed code, you can still expose your own object model through the script control. Yes, even manages classes. In this next lines Ill show you how to do just that.

  • In the Form_load event, after initializing the script control, add the following line:

script.AddObject("form", me, True)

Youve just told the script

-         Expose an object from my application under the name of form.

-          This object is me.

-          Yes, please expose all its members for scripting as well.

  • Now, change the call to script.Eval into this code:

script.ExecuteStatement(txtResult.Text)

Instead of evaluating an expression, youre telling the script control to execute a statement. Now you can do some cool stuff.

  • Run the program and write the following in the text box:

Form.left+= 200

  • Press the execute button  and see what happens.

 

As you can see, .Net has no trouble exposing managed objects for scripting just as easy as it accommodates for COM objects in the program (underneath it wraps the calls to the COM objects in specialized wrapper objects that do all the COM calls in a low level manner, but we dont care about that since its done automagically for us)

 

Some guidelines for adding code at runtime

Theres a better way that to just execute a random procedure the user write everytime. A real application would have a bit more organized manner in which scripting would be implemented. Here are some guidelines:

  • Use the script.AddCode(text) method to add chunks of VBScript code (usually from script files with a .vbs extension) when the application initializes. These chunks can contain helper methods or methods that were changed by the user of your application.
  • You can call specific methods that were loaded in your script from any other method in your script. For example, if you know you have a SayHello method written in the vbs file you load at startup, your user can go ahead and write code that calls this procedure (they just have to know its there documentat you APIs).
  • You can call preloaded methods from your managed code as well using the script.Run(procedureName, paramArray params()as object) method. This allows you to call various user events on predefined application entry points. For example, if you want your users to customize what happens whenever your application shuts down, you can have an empty OnShutDown method in a script file. You will call this method before your application shuts down using the script.Run() method. If your client wants to customize what happens before shutdown, they can simply add VBScript code to the empty body of the method in the script file.
  • You can also send parameters to methods you call dynamically. These parameters can be full blown objects or simple strings and integers. You choose. Your user can accepts the objects in the method parameters and manipulate them just as regular objects. You dont have to expose them before you do this.
  • Since this is .Net and you have all the object oriented power in your hands, you should consider creating a custom class that inherits from the ScriptControlClass class and use that class as your scripting engine. Your custom class will use the constructor to initialize stuff such as the scripting language, loading various script files and adding support for various helper methods you might need in your application to do with scripting.

Where are we?

Weve seen so far that you can expose your own objects to script and you can evaluate various expressions. This is about as far as you can get with scripting in the unmanaged world. Now Ill take you a little further and see how .Net helps us add scripting to something which seems totally out of whack webservices.

 

Calling a webservice from script

As weird as it may sound, your user can call methods on a webservice that your application uses, just as easy as it would call a regular objects. How is this possible? Well, Ive already shown how to ws.BeginHelloWorld expose a managed object to script by using the script.AddObject() method. Exposing a webservice is just as easy. You do it exactly the same way. This is possible because when you use a webservice in your own applications you are essentially using a proxy object, a mediator between your application and the real service. This proxy looks and acts just like the real service but it lives in your own application domain and forwards all calls to the webservice when needed. This means that the webservice instance just another object that can be exposed and used in script much like any other object.

However, there is one caveat:  calling a webservice might take some time to accomplish; meanwhile your application will wait for the script to execute. The script in turn will wait for the webservice to execute and everyone will be looking at you asking why this is so darned slow.  To make this problem go away we can use the same technique we use in a real managed application: calling the service asynchronously.

 

Asynchronous invocation from script

How do we accomplish calling a webservice asynchronously today?

Lets assume our simple webservice has but one method HelloWorld which returns a string. In managed code we would call it like this:

    Public Sub CallWebService()

        Dim callback As New AsyncCallback(AddressOf AsyncWsCallback)

        wsObject.BeginHelloWorld(callback, Nothing)

    End Sub

 

    Public Sub AsyncWsCallback(ByVal ar As IAsyncResult)

        txtResult.Text = wsObject.EndHelloWorld(ar)

    End Sub

 

So heres what we need to have in script for the async call to work:

-         A call to wsObject.BeginHelloWorld passing in a callback object

-         An instance of an AsyncCallback object

-         A call to wsObject.EndHelloWorld(ar) passing in an IAsyncResult object

 

Can you guess how we manage this?

  • When initializing the script we should also expose an AsyncCallback object that will be used in our script when invoking the webservice. This AsyncCallback object will actually be pointing at a managed method. That managed method will invoke (using script.Run() a script method which will be used to do work when the webservice call ends.

 

  • Heres how to initialize the AsyncCallBack object in the script:

     Dim callback As New AsyncCallback(AddressOf AsyncWsCallback)

     script.AddObject("ws", wsObject, True)

     script.AddObject("callback", callback, True)

 

  • And heres the implementation that does not work of the managed callback

    Public Sub AsyncWsCallback(ByVal ar As IAsyncResult)

        script.Run("OnWsEndHelloWorld", ar)

    End Sub

 

It just invokes a script that does processing when the webservice finishes its work.

I wish this implementation would work out of the box, but it does not. If you use this technique, you cant send the IAsyncResult object as a parameter to your script method. Some very weird COM interop issues will happen so let me save you the trouble. The IAsyncResult object can only be used in managed code in this case. Your only option is to call  ws.EndHelloWorld(ar)  in managed code and then call a script method that processes the results. You do not want to do something like this in the script:

 

Sub OnWsEndHelloWorld(ar)

Msgbox ws.EndHelloWorld(ar)

End Sub

 

This is wrong. Your script should process the outcome of the EndHelloWorld as received from the managed code. Otherwise, as the say in SouthPark, Youre gonna have a bad time.

 

  • So, instead, well do this in our managed code

    Public Sub AsyncWsCallback(ByVal ar As IAsyncResult)

Dim result As String = wsObject.EndHelloWorld(ar)

  script.Run("OnWsEndHelloWorld", result)

    End Sub

 

Which leaves us with this piece of script:

 

Sub OnWsEndHelloWorld(result)

Msgbox result

End Sub

 

 

 

So, the final order of operations is this:

  • Somewhere in the script,  there is a call to ws.BeginHelloWorld(callback,nothing)
  • The callback is triggered in Managed code which picks up the result by calling ws.EndHelloWorld and calling another script method with the results.

 

Conclusion

As you can see, its pretty easy to add scripting support to your managed application, exposing an object model to your users, responding to major events in the application lifetime and even invoking webservices asynchronously.

Start thinking what you can do with all this power for your users today.

Published Tuesday, February 17, 2004 7:42 AM by RoyOsherove
Filed under:

Comments

Monday, February 16, 2004 10:10 PM by TrackBack

# Adding Scripting Support to .net Applications

Tuesday, February 17, 2004 1:54 AM by Sijin Joseph

# re: Make your .Net application support scripting - the practical way

Great info, i was wondering if this was possible in .Net but never got the time to look into it. BTW do you know if there exists a managed solution to add scripting capabilities to your .Net application.
Tuesday, February 17, 2004 2:11 AM by Roy Osherove

# re: Make your .Net application support scripting - the practical way

Yes. I mention one in the article. Alintex Script.
Tuesday, February 17, 2004 3:41 AM by Juan M. Servera

# re: Make your .Net application support scripting - the practical way

Check the Microsoft.Vsa namespace. .Net provides its own classes for scripting C#, VB.Net and J#, but there are some "design leaks" as .Net was not designed to unload assemblies, you will find more info at msdn site.
Tuesday, February 17, 2004 3:50 AM by Stefano Demiliani

# re: Make your .Net application support scripting - the practical way

Good article Roy :)
Tuesday, February 17, 2004 4:07 AM by Roy Osherove

# re: Make your .Net application support scripting - the practical way

Juan: yes, I've looked at that namespace but decided that the MS Script was easier to use. I forgot to mention it the article but I've added it now. Thanks for the tip :)
Tuesday, February 17, 2004 8:18 AM by Hamza GOLYERI

# re: Make your .Net application support scripting - the practical way

Tuesday, February 17, 2004 9:05 AM by Addy Santo

# re: Make your .Net application support scripting - the practical way


Yo,

Great article!! Keep in mind however that lots of the "cool" developers out there would rather cut off their right hand than go back to the VBScript environment after 2+ years of managed code development. That is one of my main pain points while working with InfoPath.

I suggest going the long way and allowing for managed code.... a part II to this article perhaps? :)

Tuesday, February 17, 2004 3:44 PM by Roy Osherove

# re: Make your .Net application support scripting - a practical approach

Santo: you'd be right, only the scripting is for the clients\users, not the developers. And it's MUCH much easier doing stuff in VBscript that starting to import .Net namespaces. Easy wins for client usability in my eyes.
Wednesday, February 18, 2004 8:51 AM by R

# re: Make your .Net application support scripting - a practical approach


Just playing about with your example .. if my script is JScript and I call the following ...

Form.Left += 200;
Form.Left += 100;

It bombs out .. if I change it to

Form.Left += 200;
2+2;
Form.Left += 100;

Moral of the story ... be careful..
Wednesday, February 18, 2004 1:27 PM by R

# re: Make your .Net application support scripting - a practical approach


As an addendum to my previous post, this does not seem to happen on non-gui elements. Doing AddObject() to bind our ORM objects allows us to call as frequently as we like :

c = currentCase.GetCaseDetails();
d = currentCase.Refresh();

works fine.
Wednesday, February 18, 2004 2:59 PM by Bertrand Le Roy

# And yet another whacky application of the script control... Sharing session between ASP and ASP.NET

I wrote this paper about one year ago about using the script control to share session between ASP and ASP.NET. Well, it goes far beyond sharing session, and provides a new way to migrate from ASP to ASP.NET.
Read this:
<a href="http://www.dotnetguru.org/us/modules.php?op=modload&name=News&file=article&sid=16&mode=thread&order=0&thold=0">http://www.dotnetguru.org/us/modules.php?op=modload&name=News&file=article&sid=16&mode=thread&order=0&thold=0</a>
Thursday, February 19, 2004 3:24 AM by c enders

# re: Make your .Net application support scripting - a practical approach

Concept sounds nice but I failed to run it.

Problems

Step 1
The script control didn't show up on the COM tab. Instead I had to browse for it
c:\winnt\system32\mscript.ocx.
Also is it the Microsoft Scripting, Scriplet or ScriptControl ?

Step 2
Put a VB line
Dim myScript as new MSScriptControl.ScriptControlClass

Step 3
Didn't realize "script" was an object.
The textbox line fail to compile because of
Option Strict On disallows implicit converions from 'System.Object' to 'String'
After fixing it compiles when.

Running it causes an unhandled exception
COM object with CLSID (...} is either not valid or not registered

After typing on the command prompt Regsvr32 msscript.ocx it finally ran

Entering 2+2 and execute

Unhandled exception
The operation could not be completed because the script engine has not been initialized to a valid language

After putting in the form load
myScript.Language = "VBScript"

it finally worked.

So put the complete code in it.

But the concept is nice. Have you thought about making test scripts with it ?

CE





Thursday, February 19, 2004 10:28 PM by TrackBack

# Still in Love With VBScript and JScript?

Thursday, February 19, 2004 10:31 PM by TrackBack

# Still in Love With VBScript and JScript?

Friday, February 20, 2004 1:54 PM by JosephCooney

# re: Make your .Net application support scripting - a practical approach

In a similar vein - Tim Dawson has an article on using .NET languages (VB.NET, C#) to make your application scriptable http://www.divil.co.uk/net/articles/plugins/scripting.asp
Thursday, February 26, 2004 8:59 PM by TrackBack

# re: Welcome to the Scripting Guys' First Blog

Tuesday, March 16, 2004 3:45 PM by TrackBack

# Scripting Goodies

Tuesday, March 16, 2004 3:45 PM by TrackBack

# Scripting Goodies

Tuesday, March 16, 2004 4:34 PM by TrackBack

# Scripting Goodies

Thursday, March 25, 2004 5:44 AM by TrackBack

# Roy Osherove on adding scripting support to your .NET application

Sunday, June 06, 2004 1:21 PM by tom

# re: Make your .Net application support scripting - a practical approach

great!But I had some problem on following code:
--------------
object [] paramArray = new object[1];
paramArray[0] = "1";
string strbody = "sub monitor(x) alert(x) end sub";
scriptEngine.AddCode(strbody);
scriptEngine.Run("monitor",ref paramArray);
--------------
when i run it ,it tells me :--
System.Runtime.InteropServices.COMException (0x800A000D): type mismatch: 'alert'
at MSScriptControl.ScriptControlClass.Run(String ProcedureName, Object[]& Parameters)
at TongHua.WEBClient.Form1.button4_Click(Object sender, EventArgs e) in d:\WEBClient\form1.cs:line 679
Friday, July 02, 2004 6:34 AM by Shailesh Bapat

# re: Make your .Net application support scripting - a practical approach

Section Using the MSScript control in .Net help me a lot. Thanks
Wednesday, July 07, 2004 9:33 AM by Denis P Gohel

# re: Make your .Net application support scripting - a practical approach

Yes.. it works fine.

But in VB.NET, how can i trap EXCEPTION and show appropriate error info to user ?

Thanx
Saturday, July 17, 2004 12:49 PM by dcahrakos

# re: Make your .Net application support scripting - a practical approach

I got it to work, except, im using VBScript, and im trying to add a textbox as an object, so I can go like
txtbox2.write("Hey")

but I dont know how to make a text box an object, ive tried

Script.AddObject("textbox2",textBox2,True) - error, and you can use it to make a form an object, so anyone know how I make a text box an object?
Sunday, July 18, 2004 4:53 PM by plasmatic

# re: Make your .Net application support scripting - a practical approach

I too have tried to add a textbox, but it doesn't work. I even tried doing Script.AddObject("TextBox2",Me.TextBox2,True) and it doesn't work.


I've managed to create a debugger though:

Try
Script.ExecuteStatement(txtResult.Text)
Catch ex As Exception
TextBox1.Text = Script.Error.Description & " | Line of error: " & Script.Error.Line & " | Code error: " & Script.Error.Text
End Try
Monday, July 19, 2004 1:21 PM by plasmatic

# Adding Textboxes and References

I have just figured out with someone else how to add textboxes. It took as three days, but in the end we figured it out. Here is how you do it:

Private Sub InitializeScript()
script.Reset()
script.Language = "VBScript"
script.AddObject("Form1", Me, True)
End Sub
Dim blank As String
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
script.Language = "VBScript"
blank = "Hey"
End Sub
Public Property BlankString()
Get
Return blank
End Get
Set(ByVal Value)
blank = Value
End Set
End Property
Public Property LabelText()
Get
Return Label1.Text
End Get
Set(ByVal Value)
Label1.Text = Value
End Set
End Property

Public Sub ShowScriptMessage(ByVal s As String)
System.Windows.Forms.MessageBox.Show(s)
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
InitializeScript()
script.ExecuteStatement("Form1.LabelText=""This is set via the script control""")
script.ExecuteStatement("Form1.ShowScriptMessage(""This is shown from the script control"")")
script.ExecuteStatement("MsgBox(""Hey"" & Form1.BlankString)")
End Sub

That shows how to add references and objects on your form. (roy, you should add that to your article.)
Thursday, July 22, 2004 1:26 PM by John

# re: Make your .Net application support scripting - a practical approach

Couple of questions for anybody reading this...
Is there any way to start execution of the script from a certain line number?
Also is there any possibility of pausing the script while it is running?

Thanks,
John
Thursday, July 13, 2006 5:01 PM by Daniele

# Specified cast is not valid

Declaration: Public ScriptControl As MSScriptControl.ScriptControlClass in form_load ScriptControl = New MSScriptControl.ScriptControlClass ScriptControl.Language = "VBScript" in Sub ScriptControl.AddObject("form", Me, True) i've error "Specified cast is not valid". Also if i add a procedure ScriptControl.AddCode("Public sub Test(X) msgbox(X) end sub") Dim p(0 To 1) As Object p(1) = "test" ScriptControl.Run("Test", p) Exception from HRESULT: 0x800A01C2 This code works good in vb6
Saturday, August 26, 2006 4:30 AM by Shaun

# re: Make your .Net application support scripting - a practical approach

Lua.NET integrates the Lua scripting language with the .NET runtime. We've been using it successfully in our projects. http://www.lua.inf.puc-rio.br/luanet/ http://luaforge.net/projects/luainterface
Thursday, August 31, 2006 5:15 PM by Mikey

# It doesn't work.

Dim VBScript As New MSScriptControl.ScriptControlClass VBScript.Language = "vbscript" VBScript.AddObject("form", Me, True) 'Crashes here, "Invalid Cast Exception" -- But all my parameters are of the correct type. What gives? ... I can get it to do some things like: msgbox "Hello World" -- but msgbox "string1", "string2" crashes, also for loops seem to work, but I can't expose my objects to the scripts so this is pretty much useless to me.
Tuesday, September 05, 2006 6:35 AM by Jimmy

# re: Make your .Net application support scripting - a practical approach

I'm running .net 2005 I have the same problem. Is this article as spam?
Wednesday, September 06, 2006 9:38 PM by Jimmy

# re: Make your .Net application support scripting - a practical approach

I found the answer! use: Imports System.runtime.InteropServices ... Public Class ClassNameToAddToScriptControl Then it goes well with AddObject of ScriptControl
Thursday, September 07, 2006 12:45 PM by Dana

# re: Make your .Net application support scripting - a practical approach

Jimmy, I tried the adding "Imports System.runtime.InteropServices" statement to my class file and yet, I get the same "Invalid Cast Exception" when using AddObjectin MSScriptControl. Could you please post some additional information in how you got MSScript Control to work with VS 2005. Thanks
Monday, September 11, 2006 4:19 PM by Dan

# re: Make your .Net application support scripting - a practical approach

Dana, If you're using Visual Studio 2005, make sure in your AssemblyInfo.cs file you have this: using System.Runtime.InteropServices; and, lower down, this: [assembly: ComVisible(true)] It's probably set to false - set it to true.
Saturday, October 21, 2006 2:49 AM by kli

# re: Make your .Net application support scripting - a practical approach

got the same error. tries with the 'using' stamement and the attribute definition but it simply doesn't work!
Thursday, October 26, 2006 10:18 PM by Morgan

# re: Make your .Net application support scripting - a practical approach

I got this to work. In C# I had created a simple class called ScriptableObject as a test. By default, a new class is created without an access modifier, making it internal. If you change it to public, you can then add it using the script.AddObject call. Good luck
Friday, January 05, 2007 4:46 PM by Zerox Millienium

# re: Make your .Net application support scripting - a practical approach

If I create a text file with the script commands in it, how should I best load and execute the script in that file.

Wednesday, February 28, 2007 6:01 AM by Davman

# re: Make your .Net application support scripting - a practical approach

To make this work in VB2005, change your class definitions:

<ComVisible(True)> Public Class ClassNameToAddToScriptControl

to enable .AddObject for a class.

Wednesday, March 07, 2007 6:31 PM by Tom Henrich

# re: Make your .Net application support scripting - a practical approach

Great article - this is just what I was looking for. Was having an issue with the AddObject until I added this <ComVisible(True)> works fine now....

Monday, March 12, 2007 7:11 AM by Alistair

# re: Make your .Net application support scripting - a practical approach

This is definately an interesting article. However, since the article was first published, other alternatives have been introduced. A good (and surprisingly easy) example is hosting IronPython within your application.