Load VB.net code from .txt file and execute it on fly using System.CodeDom.Compiler

SMultani picture SMultani · Jan 27, 2014 · Viewed 7.8k times · Source

I have found answer to this question already in this post : https://stackoverflow.com/a/14711110/1764912

But my next query is, When I try to declare either a DataTable or MsgBox inside this dynamic code, it give me an error that "Type 'DataTable' is not defined" and "Type 'MsgBox' is not defined" is. If I add imports using either first line in dynamic code as :

Imports System.Data

or

Imports System.Data.DataTable

or if I use any of the following code in GenerateScript() function (Please refer https://stackoverflow.com/a/14711110/1764912 for GenerateScript() function)

Dim importDataNameSpace As String = GetType(DataTable).Namespace
Dim codeArray() As String = New String() {"Imports " & importDataNameSpace & Environment.NewLine & code}

or if I use

Dim codeArray() As String = New String() {"Imports System.Data" & Environment.NewLine & code}

or

Dim codeArray() As String = New String() {"Imports System.Data.DataTable" & Environment.NewLine & code}

In all above cases, it give me an error "System.Data does not contain any public members or couldn't found".

Answer

Steven Doggart picture Steven Doggart · Jan 27, 2014

Importing namespaces does nothing for you unless you first also reference the library. If the library isn't referenced, then the namespace that you're importing will effectively be empty.

As others have mentioned in the comments above, just because you have the System.Data.dll library referenced in your project, that doesn't mean that it is also referenced by the assembly that you are dynamically compiling. Each assembly needs to directly reference all of the assemblies that it needs. Dynamically compiled assemblies are no exception.

References are added to the dynamic assembly via the CompilerParameters.ReferencedAssemblies.Add method. You can see an example of that in my answer to the question that you linked to. In that example, I had the dynamic assembly reference back to the main assembly so that it could use the IScript interface. You can, however, add as many references as you like. To also add a reference to System.Data.dll, you could do it like this:

Public Function GenerateScript(code As String) As IScript
    Using provider As New VBCodeProvider()
        Dim parameters As New CompilerParameters()
        parameters.GenerateInMemory = True
        parameters.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location)
        parameters.ReferencedAssemblies.Add("System.Data.dll")
        parameters.ReferencedAssemblies.Add("System.Xml.dll")
        Dim interfaceNamespace As String = GetType(IScript).Namespace
        Dim codeArray() As String = New String() {"Imports " & interfaceNamespace & Environment.NewLine & code}
        Dim results As CompilerResults = provider.CompileAssemblyFromSource(parameters, codeArray)
        If results.Errors.HasErrors Then
            Throw New Exception("Failed to compile script")
        Else
            Return CType(results.CompiledAssembly.CreateInstance("Script"), IScript)
        End If
    End Using
End Function

Since the System.Data.dll assembly is in the GAC, you don't need to specify a full path. Notice also, that in order to use DataTable, you'll also need to add a reference to System.Xml.dll. You'd find that out as soon as you ran the code.

So, if you had the above method defined, and you had the following interface defined:

Public Interface IScript
    Function DoWork() As String
End Interface

Then, you'd be able to call it like this:

Dim builder As New StringBuilder()
builder.AppendLine("Public Class Script")
builder.AppendLine("    Implements IScript")
builder.AppendLine("    Public Function DoWork() As String Implements IScript.DoWork")
builder.AppendLine("        Dim table As New System.Data.DataTable()")
builder.AppendLine("        table.TableName = ""Hello World""")
builder.AppendLine("        Return table.TableName")
builder.AppendLine("    End Function")
builder.AppendLine("End Class")
Dim script As IScript = GenerateScript(builder.ToString())
Console.WriteLine(script.DoWork())  ' Outputs "Hello World"