WEBINAR:
On-Demand
Application Security Testing: An Integral Part of DevOps
Making Life Easier with wwScripting
There's a lot of power in all of that codeit shows how much flexibility there is in the .NET Framework, but you certainly wouldn't want to put all of that code into your application each time you need to execute code dynamically. It's reasonably easy to abstract all of this code into a class. You can
find the code in the
wwScript.cs source file and in the Westwind.Tools.Scripting namespace with the wwScripting class.
The class provides the following features:
- Transparent execution of C# and Visual Basic code
- Execution in the current AppDomain or via external AppDomains for shutdowns
- Error handling
- High level and low level methods
With the class running dynamic code gets a bit easier as shown in
Listing 5.
If you want to load the code into a different AppDomain call the
CreateAppDomain("Name") method before the
ExecuteCode() method call.
The class also includes several methods for executing code. For example,
ExecuteMethod() allows you to provide a full method including the signature defining parameters and return values. This makes it possible to create properly typed parameters and return values. For example, take a code snippet like this:
public string Test(string lcName, int x)
{
string cHello;
cHello = lcName;
MessageBox.Show(cHello,"Compiler Demo");
return DateTime.Now.ToString();
}
You can then run with this code:
string lcResult = (string)
loScript.ExecuteMethod(lcCode,
"Test","rick strahl",x);
Notice that you can access the parameters directly by name in the dynamic code snippet. It's a little cleaner if you pass parameter and return values this way. You can also pass multiple methods as a string:
public string Test(string lcName, int x)
{
string cHello;
cHello = lcName;
MessageBox.Show(cHello,"Compiler Demo");
return DateTime.Now.ToString();
}
public string Test2(string lcName, int x)
{
return Test(lcName,x);
}
You can then call the two methods like this:
string lcResult = (string) loScript.ExecuteMethod(
lcCode,"Test","rick strahl",(int) x);
lcResult = (string)
loScript.CallMethod(loScript.oObjRef,
"Test2","rick strahl",(int) x);
Note that making the second call is rather more efficient because the object already exists and is loaded. No recompilation or regeneration occurs on this second call.
CallMethod() is one of the lower level methods of the class. With it you can perform each step of the compile process individually. A number of other low level methods are (see Table 1 and Table 2).
Table 1: Low-level methods of the wwScripting object.
Low Level Method
|
Function
|
Parameters
|
CompileAssembly
|
Compiles an assembly and holds an internal pointer to the
assembly object (only if locally loadedAppDomains are handled from disk).
|
lcSource
Source code
|
CreateInstance
|
Creates an instance of the compiled code either in the
local or a remote AppDomain. Sets the oObjRef property with the reference to
the object or Interface.
|
None
Uses internal references to the Assembly or the name of the DLL file to load
into an AppDomain.
|
CallMethod
|
Executes a method by name using the oObjRef pointer. Knows
about local or remote AppDomain.
|
lcMethod
The method to call.
Parameters()
A variable list of parameters from 0 to n.
|
CreateAppDomain
|
Creates an AppDomain and forces CreateInstance and
CallMethod to use that domain to load and execute code in.
|
lcAppDomainName
Name of the domain
|
Dispose
|
Cleans up and releases references.
|
None
|
Table 2: Low-level properties of the wwScripting object.
Property
|
Function
|
bError
|
Error flag that should be checked after making calls
before using any results.
|
cErrorMsg
|
Contains error information either after compiling or
running code.
|
lSaveSourceCode
|
Determines whether the code that is finally compiled is
saved. Full assembly source code.
|
cSourceCode
|
Set before compilation if lSaveSourceCode is true.
|
oObjRef
|
After a successful method execution (or after calling
CreateInstance) this property contains an instance of the dynamic object.
|
cAssemblyNamespace
|
Name of the namespace that the code is generated into.
This is used to generate the assembly and then used again when the class is
instantiated to reference the type.
|
cClassname
|
Same as cAssemblyNamespace
|
lDefaultAssemblies
|
Determines if certain assemblies and namespaces are loaded
by default. Loads System, System.IO, System.Reflection.
|