Go Back  Xtreme Visual Basic Talk > General Discussion > Tech Discussions > Script execution with CodeDOM tutorial


Reply
 
Thread Tools Display Modes
  #1  
Old 11-15-2005, 10:11 PM
shaul_ahuva shaul_ahuva is offline
Ultimate Contributor

Retired Leader
* Expert *
 
Join Date: Jul 2003
Location: Camp Hill, PA
Posts: 1,992
Default Script execution with CodeDOM tutorial


I thought this might make a good tutorial - I spent the better part of a day searching msdn and various blogs while I was trying to resolve problems with compiling code and loading/unloading assemblies.

-----

Please note: This code was created with VS 2005 Beta 2.

The concept of dynamically compiling code can be daunting, but hopefully you will find that it's actually quite simple with this short tutorial that explains how to load, compile, execute, and re-build (if necessary) code contained within unrelated vb files.

In order for code to compile, it must be located within a class. Therefore, the first step is to define a generic class that can execute the code. The easiest solution I have found to do this is to create a template and store it as a resource to be pulled out of the assembly's manifest at runtime and updated.

First we will define an interface for this template class. Defining an interface allows us to execute the code without using Reflection.

Code:
''' <summary> ''' Defines classes created from script files. ''' </summary> ''' <remarks></remarks> Public Interface IScriptWrapper ''' <summary> ''' When implemented, executes the code in the script file. ''' </summary> ''' <remarks></remarks> Sub Execute() ''' <summary> ''' When implemented, returns the name of the script. ''' </summary> ''' <value></value> ''' <remarks></remarks> ReadOnly Property Name() As String End Interface
Next we can create the template that will be modified at runtime:

Code:
Imports System Imports System.Windows.Forms Namespace Scripts Public Class {0} Implements CSEE.Common.IScriptWrapper Public Sub Execute() Implements CSEE.Common.IScriptWrapper.Execute {1} End Sub Public ReadOnly Property Name() As String Implements CSEE.Common.IScriptWrapper.Name Get Return "{0}" End Get End Property End Class End Namespace
The above template will allow us to "plug in" code loaded from the script files into the Execute method.

Before we continue, a sidenote on the AppDomain class is needed. Every .NET application runs within the context of an AppDomain. A single AppDomain can have an unlimited number of referenced assemblies, and most .NET applications utilize a single AppDomain. There are many reasons why secondary AppDomains can be useful, but our main reason for this tutorial is fairly simple: once an assembly is loaded in an AppDomain it cannot be unloaded. The only way to unload an assembly is to unload the AppDomain in which it is referenced. This presents a problem: how do we call a method without getting a reference to the type the method is implemented on?

Enter the ScriptManager class. The ScriptManager class takes the responsibility of loading, compiling and executing scripts out of the main AppDomain and allows us to place that responsibility within the secondary AppDomain created for our dynamic assembly.

There are two main methods of the ScriptManager class: LoadScripts and Execute.

The general workflow of LoadScripts is:

1) Load the script files from the specified directory.
2) Insert the code from each script file into the template class, and store the combined code as a code source.
3) Using the VBCodeProvider class, compile the code sources into an assembly.
4) Create and store instances of the script classes for future use.

Code:
''' <summary> ''' Loads and compiles the scripts. ''' </summary> ''' <param name="scriptFolder">The folder containing the script (vb) files.</param> ''' <remarks></remarks> Public Sub LoadScripts(ByVal scriptFolder As String) mScripts = New List(Of IScriptWrapper) Dim compiler As VBCodeProvider = New VBCodeProvider() Dim files() As FileInfo = New DirectoryInfo(scriptFolder).GetFiles("*.vb") Dim names As New List(Of String) 'Add the assembly references we'll need, 'specify the assembly name and indicate we 'want debug symbols generated. Dim parameters As New CompilerParameters(New String() _ {"System.dll", _ "System.Windows.Forms.dll", _ "Microsoft.VisualBasic.dll", _ "Common.dll"}, "Scripts.dll", True) Dim results As CompilerResults Dim sources(files.Length - 1) As String Dim template As String = My.Resources.Templates.ScriptTemplate RaiseEvent ScriptEvent(String.Format("Processing scripts in ""{0}""...", scriptFolder)) For i As Integer = 0 To files.Length - 1 RaiseEvent ScriptEvent(String.Format("Processing ""{0}""...", files(i).FullName)) Dim f As FileInfo = files(i) Dim r As New StreamReader(f.OpenRead()) Dim source As String = r.ReadToEnd() Dim scriptName As String = f.Name.Substring(0, f.Name.IndexOf(".vb")) r.Close() 'Replace the placeholders with the script name and code. sources(i) = String.Format(template, scriptName, source) names.Add(scriptName) Next RaiseEvent ScriptEvent("Compiling ""Scripts.dll""...") 'Compile the assembly. parameters.MainClass = "Scripts" parameters.OutputAssembly = "Scripts" results = compiler.CompileAssemblyFromSource(parameters, sources) If results.Errors.Count > 0 Then RaiseEvent ScriptEvent("Errors occurred during compilation:") For Each e As CompilerError In results.Errors RaiseEvent ScriptEvent(String.Format("Line ""{0}"": {1}", e.Line.ToString(), e.ErrorText)) Next ElseIf results.NativeCompilerReturnValue Then RaiseEvent ScriptEvent("Errors occurred during compilation.") For Each s As String In results.Output RaiseEvent ScriptEvent(s) Next Else 'Create an instance of each script class. For Each n As String In names mScripts.Add(DirectCast(AppDomain.CurrentDomain.CreateInstanceAndUnwrap("Scripts", "Scripts." & n), _ IScriptWrapper)) Next RaiseEvent ScriptEvent("""Scripts.dll"" compiled successfully.") End If End Sub

The Execute method is fairly simple as there is only one thing to do...execute the scripts

Code:
''' <summary> ''' Executes the scripts. ''' </summary> ''' <remarks></remarks> Public Sub ExecuteScripts() For Each w As IScriptWrapper In mScripts RaiseEvent ScriptEvent(String.Format("Executing script ""{0}""...", w.Name)) Try w.Execute() Catch ex As Exception RaiseEvent ScriptEvent(String.Format("An exception of type ""{0}"" occurred while executing script ""{1}"".", _ ex.GetType().ToString(), w.Name)) End Try Next End Sub
Now that the infrastructure is in place, we need to add a simple application to create and execute scripts in a second AppDomain. First, we will create the AppDomain and specify that we want the assemblies to be shadow copied. This option will allow us to rebuild the assembly without restarting the application.

Code:
mScripts = AppDomain.CreateDomain("Scripts", _ AppDomain.CurrentDomain.Evidence, _ AppDomain.CurrentDomain.BaseDirectory, _ AppDomain.CurrentDomain.RelativeSearchPath, True)
Then we need to create an instance of the ScriptManager class in the mScripts AppDomain.

*** NOTE *** This is critical if you plan on rebuilding the assembly. If the main AppDomain EVER gets the type information for any types within the scripts assembly, an assembly reference will be added to the main AppDomain and we will be unable to unload the scripts assembly.

Code:
mManager = DirectCast(mScripts.CreateInstanceAndUnwrap("Common", _ "CSEE.Common.ScriptManager"), ScriptManager)
The ScriptManager class inherits from MarshalByRef, which essentially allows the primary AppDomain to work with a proxy of the actual ScriptManager instance in the second AppDomain.

The only things left to do are to load and compile the scripts, which are done quite easily:

Code:
mManager.LoadScripts(lblRepository.Text) mManager.ExecuteScripts()
I hope that this tutorial and the accompanying code will help some of you out there - I know I spent a good bit of time troubleshooting various problems with compiling code on the fly and loading/unloading assemblies.
Attached Files
File Type: zip CodeDOMExample.zip (28.9 KB, 23 views)
Reply With Quote
Reply


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off

Forum Jump

Advertisement:





Free Publications
The ASP.NET 2.0 Anthology
101 Essential Tips, Tricks & Hacks - Free 156 Page Preview. Learn the most practical features and best approaches for ASP.NET.
subscribe
Programmers Heaven C# School Book -Free 338 Page eBook
The Programmers Heaven C# School book covers the .NET framework and the C# language.
subscribe
Build Your Own ASP.NET 3.5 Web Site Using C# & VB, 3rd Edition - Free 219 Page Preview!
This comprehensive step-by-step guide will help get your database-driven ASP.NET web site up and running in no time..
subscribe
 
 
-->