---
title: "Make your .Net application support scripting - a practical approach"
url: "https://osherove.com/blog/2004/2/17/make-your-net-application-support-scripting-a-practical-appr.md"
canonical: "https://osherove.com/blog/2004/2/17/make-your-net-application-support-scripting-a-practical-appr.html"
published: "2004-02-17T03:42:00"
updated: "2004-02-17T03:42:00"
tags: ["Other"]
author: "Roy Osherove"
description: "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…"
usage: "Licensed under CC BY 4.0 — reuse, quote and adapt with attribution (Roy Osherove, link to the original page)."
license: "https://creativecommons.org/licenses/by/4.0/"
---

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

By Roy Osherove February 17, 2004 In [Other](https://osherove.com/blog/category/other)

**Historical article** · Originally published February 17, 2004; technical details may be outdated.

## 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](http://www.atozedsoftware.com/finalbuilder/), or another build tool called [VisualBuild Pro](http://www.kinook.com/VisBuildPro/). 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](http://www.alintex.com/) 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.

## More in Other

[All posts in Other](https://osherove.com/blog/category/other)

- Sep 3, 2004

    ### [Review - JetBrains Resharper](https://osherove.com/blog/2004/9/3/review-jetbrains-resharper.html)

- Jun 16, 2004

    ### [BackgroundWorker implementation -in C#](https://osherove.com/blog/2004/6/16/backgroundworker-implementation-in-c.html)

- Jan 17, 2004

    ### [Learning from mistakes: the Don Quixote pattern](https://osherove.com/blog/2004/1/17/learning-from-mistakes-the-don-quixote-pattern.html)

- Dec 25, 2003

    ### [An optimizing experience](https://osherove.com/blog/2003/12/25/an-optimizing-experience.html)


---

## About the author

**Roy Osherove**: Senior Solutions Architect at AWS working on AI for production and AI-driven development; author of The Art of Unit Testing and Elastic Leadership, with about 20 years of consulting and training on unit testing, TDD, team leadership and continuous delivery.

### Sites

- [RobotPaper](https://robotpaper.ai/): This blog: AI patterns and anti-patterns, AI and coding agents, and engineering leadership.
- [osherove.com](https://osherove.com): Roy's personal site. Author of The Art of Unit Testing and Elastic Leadership; consulting and training on unit testing, TDD, team leadership and continuous delivery, plus about 20 years of blog posts.
- [The Art of Unit Testing](https://www.artofunittesting.com): The Art of Unit Testing, 3rd Edition (Manning): readable, maintainable, trustworthy unit tests, plus TDD workshops in C#, JavaScript, Java and Kotlin.
- [5whys.com](https://www.5whys.com): Roy's writing on software team leadership: self-organizing teams, surviving chaos, learning mode and the Elastic Leadership model.
- [Pipeline Driven](https://pipelinedriven.org): Continuous delivery and DevOps practices: build patterns, pipeline-friendly testing, DevOps metrics and enterprise DevOps.
- [Elastic Leadership](https://www.elasticleadership.com): Elastic Leadership, Roy's framework for software team leaders (survival, learning and self-organizing modes).

### Social

- X: https://x.com/royosherove
- LinkedIn: https://www.linkedin.com/in/osherove/

### Contact

- Contact form: https://forms.gle/jQSx5KoXvrVA1JCK7

### License

Licensed under CC BY 4.0 — reuse, quote and adapt with attribution (Roy Osherove, link to the original page). License: https://creativecommons.org/licenses/by/4.0/
