在 C# 中从反射程序集调用上下文
本文关键字:程序集 调用 上下文 反射 | 更新日期: 2023-09-27 17:48:54
我有一个用C#编写的Windows服务,该服务根据XML文件中的变量发送电子邮件,其中包括通过所述XML文件中提供的连接字符串访问SQL或MySQL数据库。我还提供了我所谓的"自定义字段"的功能,该字段评估 C# 或 VB.NET 代码,用户也可以将其放入 XML 文件中,然后可以使用自定义代码的结果生成电子邮件。现在,在自定义代码中,我想提供一组帮助程序函数,用户可以从其代码中调用这些函数,这些函数将检索字段的值,然后他们可以根据需要进行操作。
基本上在XML中,用户设置对他们想要访问的每个数据库的引用,然后是该数据库中的每个表,然后是该表中的每个字段,每个字段都有一个基于"0"(零)的id,即在每个数据库中,表ID从(零)开始,在每个表中,字段再次从"0"(零)开始
对特定字段的引用在我所谓的痕迹导航中按如下格式排列:
0,2,6(即数据库 ID 0、数据库 ID 0 中的表 ID 2、数据库 ID 0 中的表 ID 6、数据库 ID 0 中的表 ID 2 中的字段 ID 6)
或
0,3,6(即数据库 ID 0、数据库 ID 0 中的表 ID 3、数据库 ID 0 中的表 ID 3、数据库 ID 0 中的表 ID 3 中的字段 ID 6)
用户可以在其自定义代码中调用帮助程序函数,这些函数将检索一个或多个值,具体取决于他们在 XML 文件中引用的任何字段的大小写,例如:
<customFields>
<field id="0" language="VB">
<name>Member Program Week</name>
<code><![CDATA[Dim FullName As String
FullName = MailEngine.GetFieldValue(0,0,1) + " " + MailEngine.GetFieldValue(0,0,1)
Return FullName]]></code>
</field>
</customFields>
正如您在此自定义代码中看到的,引用了两个字段,一个字段痕迹导航为"0,0,1"(数据库 ID 0,数据库 ID 0 中的表 ID 0,数据库 ID 0 中的表 ID 1),两个字段具有字段痕迹导航"0,0,2"(数据库 ID 0,数据库 ID 0 中的表 ID 0, 表 ID 0 中的字段 ID 2 在数据库 ID 0 中
这些痕迹导航是对 XML 中提供的架构的引用,例如:
...
<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>
...
从本质上讲,该自定义代码将创建一个名为"FullName"的字符串变量,然后检索成员的名字和姓氏,通过"(空格)字符连接它们,并将结果存储在"FullName"变量中,然后返回结果。然后可以从消息中调用自定义代码,例如"C,0" C=Custom。
现在我的问题来了:
由于数据库字段等的所有处理都是在服务本身内完成的,因此自定义代码也使用反射从服务内部调用,我如何从帮助程序(MailEngine)类获得对服务使用的函数和变量的访问权限,看到帮助程序(MailEngine)类包含在与服务不同的上下文中的自定义代码中。
例如,如果在 MailEngine 类中,我有一个如下函数
public static string GetValueForField(int DatabaseID, int TableID, int FieldID)
{
}
我需要做什么才能从此函数或此类中的其他函数中调用函数或检索我在服务中的变量。
这是否可能,或者我是否需要重新解析 xml 文档重新连接到数据库重新收集所有字段值并从那里开始......
我不知道我怎么能更好地要求这一点,希望你们都能理解这一点并提出建议。
感谢您提供的任何帮助。
克里斯
编辑:
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 非常强大,在运行时创建实例时,将当前 AppDomain 传递到静态工厂方法中是一种很好的做法。上面的"评估"代码创建新的 AppDomain(上下文),因此您没有直接访问权限。我会在 Evaluate 方法中插入一个调试断点,以查看您是否有权访问 System.Reflection.Assembly.GetEntryAssembly()。全名。如果这样做,那么您仍将在同一域下运行,并且可以将其设置为System.Threading.Thread.GetDomain,而不是AppDomain的"新"实例
。至于获取对实例化动态程序集的方法实例的引用:
1)最容易传递对动态ctor的引用2) 传递对公共成员属性的引用3) 使用单例模式或静态构造来"调用",返回您要查找的引用。
在处理动态时,所有这些概念都相当复杂。但是,如果您可以通过阅读一些文档找到一种方法来执行上述第 1 项,那么您将处于良好的状态