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.
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, orRun(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.
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.
Today in .Net there are basically two ways to go about adding scripting to your managed application:
Its pretty easy to add basic scripting to your application. The first steps will usually be:
Private Sub cmdExecute_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
HandlescmdExecute.Click
txtResult.Text = script.Eval(txtResult.Text)
End Sub
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 getreally interesting.
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.
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.
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.
Form.left+= 200
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)
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:
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.
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 tows.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 proxyobject, 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.
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(AddressOfAsyncWsCallback)
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 callbackobject
- An instance of an AsyncCallback object
- A call to wsObject.EndHelloWorld(ar) passing in anIAsyncResult object
Can you guess how we manage this?
Dim callback As New AsyncCallback(AddressOfAsyncWsCallback)
script.AddObject("ws", wsObject, True)
script.AddObject("callback", callback, True)
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 canonly 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.
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:
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.