开发者

Acessing Calling Context from a Reflection Assembly in C#

I have a windows service written in C# that sends emails based on variables in an XML file this includes access to SQL or MySQL databases via connection strings provided in said XML file. I also give the ability for what I call "Custom Fields" which evaluate C# or VB.NET code that a user can also put in the XML file the result of the custom code then can be used to generate emails. Now in custom code I want to provide a set of helper functions that a user can call from their code that will retrieve the value for a field which they can then manipulate however they wish.

Basically in the XML the user sets up references to each database they want to access then each table within that database and then each field within that table, each are given an id that is '0' (zero) based I.E. within each database the table id's start from (zero) and within each table again the fields start from '0' (zero)

A reference to a specific field is formatted as follows in what I call breadcrumbs:

0,2,6 (Which is Database ID 0, Table ID 2 within Database ID 0, Field ID 6 within Table ID 2 within Database ID 0)

or

0,3,6 (Which is Database ID 0, Table ID 3 within Database ID 0, Field ID 6 within Table ID 3 within Database ID 0)

The user can call helper functions within their custom code that will retrieve the value or values as the case may be of any field that they have referenced in their XML file such as:

    <customFields>
    <field id="0" language="VB">
                    <name>Member Program Week</name>
                    <code><![CDATA[Dim FullName As String
FullName = MailEngine.GetFieldValue(0,0,1) + &quot; &quot; + MailEngine.GetFieldValue(0,0,1)
Return FullName]]></code>
                </field>
    </customFields>

As you can see in this custom code there are two fields being referenced one with the field breadcrumbs "0,0,1" (Database ID 0, Table ID 0 within Database ID 0, Field ID 1 within Table ID 0 within Database ID 0) and two with the field breadcrumbs "0,0,2" (Database ID 0, Table ID 0 within Database ID 0, Field ID 2 within Table ID 0 within Database ID 0)

These breadcrumbs are references to the schema provided in the XML for example:

...
<databases nextid="1" mainDataTable="0,0">
    <database id="0">
        <name>Fake Company Databse</name>
        <schema>fakeCompany</schema>
        <tables nextid="1">
            <table id="0" primaryKey="member_id" schema="dbo">
                <name>Members</name>
                <schema>members</schema>
                <fields nextid="3">
                    <field id="0">
                        <schema>member_id</schema>
                        <name>Member ID</name>
                    </field>
                    <field id="1">
                        <schema>member_firstname</schema>
                        <name>Member Firstname</name>
                    </field>
                    <field id="2">
                        <schema>member_lastname</schema>
                        <name>Member Lastname</name>
                    </field>
                    <field id="3">
                        <schema>member_email</schema>
                        <name>Member Email</name>
                    </field>
                </fields>
            </table>
        </tables>
    </database>
</databases>
...

Essentially that custom code would create a string variable named "FullName" and then retrieve the First Name and Last Name of a member join them by a " " (space) character and store the result in the "FullName" variable and then return the result. The customCode could then be called from within a message as such "C,0" C=Custom.

Now here comes my question:

As all the processing of database fields etc is done within the service itself then the custom code is called also from within the service using Reflections how do I, from the helper (MailEngine) class gain access to the functions and variables used by the service seeing that the helper (MailEngine) class is included in the custom code which is in a different context to the service.

For example if in the MailEngine class I have a function as follows

public static string GetValueForField(int DatabaseID, int TableID, int FieldID)
{

}

What do I need to do so that from within this function or others in this class so that I can call functions or retrieve variables that I have within the service.

Is this possible or do I need to re-parse the xml document reconnect to the database recollect all the field values and go from there...

I don't know how I c开发者_如何学JAVAould really ask that any better, hopefully you can both understand this and have a suggestion.

Thanks for any help you can give.

Chris

EDIT:

public class EvalVBCode{

        private string _Code;
        private CompilerErrorCollection m_oCompilerErrors = new CompilerErrorCollection();
        private Dictionary<String, String> _ReferencedAssemblies = new Dictionary<String, String>();
        private List<EvalInputObject<dynamic>> _RuntimeVariables = new List<EvalInputObject<dynamic>>();

        public string Code
        {
            get
            {
                return this._Code;
            }

            set
            {
                this._Code = value;
            }
        }

        public CompilerErrorCollection CompilerErrors{ 
            get
            {
                return m_oCompilerErrors;
            }

            set
            {
                m_oCompilerErrors = value;
            }
        }


        public Dictionary<String, String> ReferencedAssemblies
        {
            get
            {
                return this._ReferencedAssemblies;
            }
        }

        public List<EvalInputObject<dynamic>> RuntimeVariables
        {
            get
            {
                return this._RuntimeVariables;
            }
        }

        public delegate void EvalErrorEventHandler(Object sender, EvalErrorEventArgs e);
        public event EvalErrorEventHandler EvalError;

        protected virtual void onEvalError(EvalErrorEventArgs e)
        {
            if (EvalError != null)
                EvalError(this, e);
        }

        public static Object Evaluate(string code)
        {
            EvalVBCode EvalObj = new EvalVBCode();
            EvalObj.Code = code;
            return EvalObj.Evaluate();
        }

        public static Object Evaluate(string code, params EvalInputObject<dynamic>[] parameters)
        {
            EvalVBCode EvalObj = new EvalVBCode();
            EvalObj.Code = code;
            return EvalObj.Evaluate(parameters);
        }

        public static Object Evaluate(string code, EvalErrorEventHandler errorEventHandler)
        {
            EvalVBCode EvalObj = new EvalVBCode();
            EvalObj.Code = code;
            EvalObj.EvalError = errorEventHandler;
            return EvalObj.Evaluate();
        }

        public static Object Evaluate(string code, EvalErrorEventHandler errorEventHandler, params EvalInputObject<dynamic>[] parameters)
        {
            EvalVBCode EvalObj = new EvalVBCode();
            EvalObj.Code = code;
            EvalObj.EvalError = errorEventHandler;
            return EvalObj.Evaluate(parameters);
        }

        public Object Evaluate(params Object[] parameters){

            AppDomainSetup loSetup = new AppDomainSetup();
            loSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
            AppDomain loAppDomain = AppDomain.CreateDomain("MyAppDomain", null, loSetup);

             VBCodeProvider oCodeProvider = new VBCodeProvider();
            //Obsolete in 2.0 framework
            //oICCompiler ICodeCompiler = oCodeProvider.CreateCompiler

            CompilerParameters oCParams = new CompilerParameters();
            CompilerResults oCResults; 
            System.Reflection.Assembly oAssy;
            Object oExecInstance = null;
            Object oRetObj  = null;
            MethodInfo oMethodInfo;
            Type oType;

            try{
                //Setup the Compiler Parameters  
                //Add any referencedsemblies
                oCParams.ReferencedAssemblies.Add("system.dll");
                oCParams.ReferencedAssemblies.Add("Microsoft.VisualBasic.dll");
                oCParams.ReferencedAssemblies.Add("Mscorlib.dll");

                //oCParams.ReferencedAssemblies.Add("system.xml.dll");
                //oCParams.ReferencedAssemblies.Add("system.data.dll");

                //Add User Assemblies
                foreach (String Assembly in _ReferencedAssemblies.Keys)
                {
                    if (!oCParams.ReferencedAssemblies.Contains(Assembly))
                        oCParams.ReferencedAssemblies.Add(Assembly);
                }

                oCParams.CompilerOptions = "/t:library";
                oCParams.GenerateInMemory = true;

                //Generate the Code Framework
                StringBuilder sb  = new StringBuilder();

                sb.Append("Imports System " + Microsoft.VisualBasic.Constants.vbCrLf);
                sb.Append("Imports Microsoft.VisualBasic" + Microsoft.VisualBasic.Constants.vbCrLf);
                sb.Append("Imports System.Math" + Microsoft.VisualBasic.Constants.vbCrLf);

                foreach (String Import in _ReferencedAssemblies.Values.Distinct(StringComparer.InvariantCultureIgnoreCase))
                {
                    if (!Import.IsIn(StringComparer.InvariantCultureIgnoreCase, "SYSTEM", "MICROSOFT.VISUALBASIC", "SYSTEM.MATH"))
                        sb.AppendFormat("Imports {0}" + Microsoft.VisualBasic.Constants.vbCrLf, Import);
                }
                //sb.Append("Imports System.Xml" + Microsoft.VisualBasic.Constants.vbCrLf);
                //sb.Append("Imports System.Data" + Microsoft.VisualBasic.Constants.vbCrLf);

                //Build a little wrapper code, with our passed in code in the middle 
                sb.Append("Namespace EvalVBCode " + Microsoft.VisualBasic.Constants.vbCrLf);
                sb.Append("Class EvalVBCodeClass " + Microsoft.VisualBasic.Constants.vbCrLf);
                sb.Append("Public Function Eval(");

                if (RuntimeVariables.Count > 0)
                {
                    List<String> MethodParams = new List<String>();
                    foreach (EvalInputObject<dynamic> InputObject in _RuntimeVariables.Distinct())
                    {
                        MethodParams.Add(String.Format("{0} {1}", InputObject.Type.ToString(), InputObject.Name));
                    }
                    sb.Append(String.Join(", ", MethodParams));
                }
                sb.Append(") As Object " + Microsoft.VisualBasic.Constants.vbCrLf);
                sb.Append(this._Code + Microsoft.VisualBasic.Constants.vbCrLf);
                sb.Append("End Function " + Microsoft.VisualBasic.Constants.vbCrLf);
                sb.Append("End Class " + Microsoft.VisualBasic.Constants.vbCrLf);
                sb.Append("End Namespace " + Microsoft.VisualBasic.Constants.vbCrLf);
                //Response.Write("<br />" + sb.ToString() + "<br />");

                try{
                    //Compile and get results 
                    //2.0 Framework - Method called from Code Provider
                    oCResults = oCodeProvider.CompileAssemblyFromSource(oCParams, sb.ToString());
                    //1.1 Framework - Method called from CodeCompiler Interface
                    //cr = oICCompiler.CompileAssemblyFromSource (cp, sb.ToString)

                    //Check for compile time errors 
                    if(oCResults.Errors.Count > 0){
                        onEvalError(new EvalErrorEventArgs(this._Code, this._ReferencedAssemblies, this._RuntimeVariables, new Exception(oCResults.Errors[0].ErrorText)));
                        return null;
                    }else{
                        //No Errors On Compile, so continue to process...
                        oAssy = oCResults.CompiledAssembly;
                        oExecInstance = oAssy.CreateInstance("EvalVBCode.EvalVBCodeClass");

                        oType = oExecInstance.GetType();
                        oMethodInfo = oType.GetMethod("Eval");
                        if (parameters.Length > 0)
                        {
                            oRetObj = oMethodInfo.Invoke(oExecInstance, parameters);
                        }
                        else if(RuntimeVariables.Count > 0)
                        {
                            List<Object> RuntimeVariableValues = new List<Object>();
                            foreach(EvalInputObject<dynamic> Variable in _RuntimeVariables)
                            {
                                RuntimeVariableValues.Add(Variable.Value);
                            }
                            oRetObj = oMethodInfo.Invoke(oExecInstance, RuntimeVariableValues.ToArray<Object>());
                        }
                        else
                            oRetObj = oMethodInfo.Invoke(oExecInstance, null);

                        /*if (oRetObj != null)
                            Response.Write("<br />" + oRetObj.GetType().ToString() + " - " + Convert.ToString(oRetObj) + "<br />");*/
                        return oRetObj;
                    }
                }catch(Exception ex){
                    //Compile Time Errors Are Caught Here
                    //Some other weird error 
                    onEvalError(new EvalErrorEventArgs(this._Code, this._ReferencedAssemblies, this._RuntimeVariables, ex));
                    return null;
                }
            }catch(Exception ex){
                onEvalError(new EvalErrorEventArgs(this._Code, this._ReferencedAssemblies, this._RuntimeVariables, ex));
                return null;
            }
            return oRetObj;
        }
    }

    public class EvalErrorEventArgs
    {
        private string code;
        private Dictionary<String, String> _ReferencedAssemblies;
        private List<EvalInputObject<dynamic>> _RuntimeVariables;
        private Exception exception;

        public EvalErrorEventArgs(string Code, Dictionary<String, String> ReferencedAssemblies, List<EvalInputObject<dynamic>> RuntimeVariables, Exception Exception)
        {
            this.code = Code;
            this._ReferencedAssemblies = ReferencedAssemblies;
            this._RuntimeVariables = RuntimeVariables;
            this.exception = Exception;
        }

        public string Code
        {
            get
            {
                return this.code;
            }
        }

        public Dictionary<String, String> ReferencedAssemblies
        {
            get
            {
                return this._ReferencedAssemblies;
            }
        }

        public ReadOnlyCollection<EvalInputObject<dynamic>> RuntimeVariables
        {
            get
            {
                return this._RuntimeVariables.AsReadOnly();
            }
        }

        public Exception Exception
        {
            get
            {
                return this.exception;
            }
        }
    }


Dynamics are incredibly powerful with a good practice to pass the current AppDomain into a static factory method when creating the instance at runtime. Your code above "Evaluate" creates the new AppDomain (context) and thus you have no direct access. I would insert a debug breakpoint within the Evaluate method to see if you have access to the System.Reflection.Assembly.GetEntryAssembly().FullName. If you did then you would still be running under the same domain and could set it as System.Threading.Thread.GetDomain instead of a "new" instance of AppDomain

As far as getting a reference to the instance of the method which instantiated your dynamic assembly:

1) Easiest to pass a reference to the ctor of the dynamic 2) Pass a reference to public member properties 3) Use a singleton pattern or static construct to "call" which returns the reference you are looking for.

All of these concepts are fairly complex when dealing with dynamics. But if you can find a way to do number 1 above by reading thru some docs you will be in fine shape

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜