Build a Custom .NET "EVAL" Provider
By Peter A. Bromberg, Ph.D.
Printer - Friendly Version
Peter Bromberg

At work (my real job) today, an interesting discussion came up about how nice it is to be able to use the Windows Scripting Host Object Model and / or Javascript to parse and execute arbitrary strings of code "on the fly". The .NET Platform doesn't have, in any of the supported languages I've seen, an "EVAL" method to do this. However, after a little research, I found out that it's almost a trivial matter to "roll your own"!

The key built - in .NET classes with which this can be so easily achieved are the CodeProvider classes (VBCodeProvider and CSharpCodeProvider, for example) in the System.CodeDoMCompiler namespace. Two entire book chapters could be written about how to use these, along with Reflection, and some authors probably already have, so I leave the research to you. Here my objective is simply to illustrate how easily these namespaces and classes can be put to use for something truly useful.



Think about this: Your application does a lot of business logic, some of which requires complicated logical strings of code that may change over time to meet certain business conditions or metadata. Wouldn't it be great if you could pull the most current string of code to be run out of your database based on certain stored procedure input parameters, and be sure it's run and you get back the desired result? In fact, the returned string of code may even be dynamically created based on some of the input parameters from the sproc itself. . . Well, that's what this exercise is all about!.

Without further discussion, I present the basic framework for the class:

Imports Microsoft.VisualBasic
Imports System
Imports System.Text
Imports System.CodeDom.Compiler
Imports System.Reflection
Imports System.IO
Namespace PAB.Util
    Public Class EvalProvider
        Public Function Eval(ByVal vbCode As String) As Object
            Dim c As VBCodeProvider = New VBCodeProvider
            Dim icc As ICodeCompiler = c.CreateCompiler()
            Dim cp As CompilerParameters = New CompilerParameters

            cp.ReferencedAssemblies.Add("system.dll")
            cp.ReferencedAssemblies.Add("system.xml.dll")
            cp.ReferencedAssemblies.Add("system.data.dll")
            ' Sample code for adding your own referenced assemblies
            'cp.ReferencedAssemblies.Add("c:\yourProjectDir\bin\YourBaseClass.dll")
            'cp.ReferencedAssemblies.Add("YourBaseclass.dll")
            cp.CompilerOptions = "/t:library"
            cp.GenerateInMemory = True
            Dim sb As StringBuilder = New StringBuilder("")
            sb.Append("Imports System" & vbCrLf)
            sb.Append("Imports System.Xml" & vbCrLf)
            sb.Append("Imports System.Data" & vbCrLf)
            sb.Append("Imports System.Data.SqlClient" & vbCrLf)
            sb.Append("Namespace PAB  " & vbCrLf)
            sb.Append("Class PABLib " & vbCrLf)

            sb.Append("public function  EvalCode() as Object " & vbCrLf)
            'sb.Append("YourNamespace.YourBaseClass thisObject = New YourNamespace.YourBaseClass()")
            sb.Append(vbCode & vbCrLf)
            sb.Append("End Function " & vbCrLf)
            sb.Append("End Class " & vbCrLf)
            sb.Append("End Namespace" & vbCrLf)
            Debug.WriteLine(sb.ToString()) ' look at this to debug your eval string
            Dim cr As CompilerResults = icc.CompileAssemblyFromSource(cp, sb.ToString())
            Dim a As System.Reflection.Assembly = cr.CompiledAssembly
            Dim o As Object
            Dim mi As MethodInfo
            o = a.CreateInstance("PAB.PABLib")
            Dim t As Type = o.GetType()
            mi = t.GetMethod("EvalCode")
            Dim s As Object
            s = mi.Invoke(o, Nothing)
            Return s
        End Function
    End Class
End Namespace

What does it do? Well, it creates the CodeCompiler instance and the ICodeCompiler interface, sets it all up, and takes your input string (which needs to look just like valid code (VB.NET in this case, though its just as easy to use the CSharpCodeProvider). It creates a public method, EvalCode() with a return value of Object, compiles it, loads the compiled assembly into memory, and using Reflection, instantiates an instance and calls your method which is "wrapped' into the generic EvalCode method. It then returns the result (whatever Object that may be) and you are "good to go!".

An example of code that you could send into this method might be:

Dim strConn As String = "Server=(local);dataBase=Northwind;User id=sa;Password=;"
Dim cmd As New SqlCommand()
Dim cn As New SqlConnection(strConn)
cn.Open()
cmd.Connection = cn
cmd.CommandText = "select * from employees"
cmd.CommandType = CommandType.Text
Dim ds As DataSet = New DataSet()
Dim da As New SqlDataAdapter()
da.SelectCommand = cmd
da.Fill(ds)
Return ds

This code could come from your database, or wherever, and be dynamically assembled before you send it in. The downloadable solution has a Winforms test harness illustrating sample usage with the code snippet shown above, and uses it to populate a DataGrid on the form. Enjoy!

Download the code that accompanies this article

 


Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.