公司原来项目的二次开发方式主要使用SQL,基本上也能满足客户的要求,优点是使用简单,只要熟悉SQL语句就可以操作,缺点是受限制太多,需要对数据库底层相当的了解,使用时容易出错,无法直接调用业务层代码等,研究了一下.net的动态编译,感觉用它来做二次开发效果应该不错的。
首先我们先做个demo来解释一下动态编译,下面这段代码的意思就是先组织一个源码字符串,然后编译执行。
![ContractedBlock.gif](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
namespace ConsoleApplication6
{
class Program
{
//C#代码提供者
private static CodeDomProvider comp = new CSharpCodeProvider();
//用于调用编译器的参数
private static CompilerParameters cp = new CompilerParameters();
private static MethodInfo mi;
static void Main(string[] args)
{
StringBuilder codeBuilder = new StringBuilder();
codeBuilder.AppendLine("using System;");
codeBuilder.AppendLine("public class MyClass");
codeBuilder.AppendLine("{");
codeBuilder.AppendLine("public void hello()");
codeBuilder.AppendLine("{");
codeBuilder.AppendLine("Console.WriteLine( \"hello\");");
codeBuilder.AppendLine("}");
codeBuilder.AppendLine("}");
//加入需要引用的程序集
cp.ReferencedAssemblies.Add("System.dll");
CompilerResults cr = comp.CompileAssemblyFromSource(cp, codeBuilder.ToString());
//如果有编译错误
if (cr.Errors.HasErrors)
{
foreach (CompilerError item in cr.Errors)
{
Console.WriteLine(item.ToString());
}
}
else
{
Assembly a = cr.CompiledAssembly; //获取已编译的程序集
Type t = a.GetType("MyClass"); //利用反射获得类型
object mode = a.CreateInstance("MyClass");
mi = t.GetMethod("hello", BindingFlags.Instance | BindingFlags.Public);
mi.Invoke(mode, new object[0]); //执行方法
}
}
}
}
了解了上面这段代码,我们基本对动态编译的概念清楚了,但是如果在项目中这样使用的话,我们要自己去控制源代码的全部文档内容,如果大规模应用的话会非常的麻烦,需要重复编写命名空间构造,类构造,函数构造等,如果我们还想看到一个格式良好的源码,我们还必须自己控制格式。现在我们来介绍一种源码构造方式来解决这些问题,请看如下代码:
![ContractedBlock.gif](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.CodeDom;
using System.IO;
namespace ConsoleApplication6
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(GenerateCode("Hello","Hello","\t\t\treturn \"hello\";"));
}
public static CodeCompileUnit CreateExecutionClass(string typeNamespace,
string typeName,string scriptBody)
{
// 创建CodeCompileUnit以包含代码
CodeCompileUnit ccu = new CodeCompileUnit();
// 分配需要的命名空间
CodeNamespace cns = new CodeNamespace(typeNamespace);
cns.Imports.Add(new CodeNamespaceImport("System"));
ccu.Namespaces.Add(cns);
// 创建新的类声明
CodeTypeDeclaration parentClass = new CodeTypeDeclaration(typeName);
cns.Types.Add(parentClass);
// 创建获得一个参数并返回一个字符串的Hello方法
CodeMemberMethod method = new CodeMemberMethod();
method.Name = "Hello";
method.Attributes = MemberAttributes.Public;
CodeParameterDeclarationExpression arg = new CodeParameterDeclarationExpression(typeof(string), "inputMessage");
method.Parameters.Add(arg);
method.ReturnType = new CodeTypeReference(typeof(string));
// 添加方法实体需要的代码
CodeSnippetStatement methodBody = new CodeSnippetStatement(scriptBody);
method.Statements.Add(methodBody);
parentClass.Members.Add(method);
return ccu;
}
public static string GenerateCode(string typeNamespace,
string typeName,string scriptBody)
{
// 调用我们前面的方法创建CodeCompileUnit
CodeCompileUnit ccu = CreateExecutionClass(typeNamespace,
typeName, scriptBody); CSharpCodeProvider provider = new CSharpCodeProvider();
CodeGeneratorOptions options = new CodeGeneratorOptions();
options.BlankLinesBetweenMembers = false;
options.IndentString = "\t";//指定缩进字符
options.BracingStyle = "C";//大括号换行
StringWriter sw = new StringWriter();
try
{
provider.GenerateCodeFromCompileUnit(ccu, sw, options);
sw.Flush();
}
finally
{
sw.Close();
}
return sw.GetStringBuilder().ToString();
}
}
}
执行结果如下:
大家了解了动态编译之后,我们这里介绍一个稍微复杂一点的应用:
需求:我们先预定义一个执行流程,而具体执行代码可以在我们项目部署之后再编写。比如说工资的计算在不同应用中算法会有很大的不同。
我们首先定义一个数据库中的数据结构:
然后将这个表拖入到dbml(生成的dbml文件请下载源码)文件中,现在我们就开始编写相应的代码吧:
首先我们先来处理函数的参数,如果我们只是将参数列表的字符串存入数据库中的话,我们还要根据格式序列化和反序列化这个参数,所以我们使用
xml存入sqlserver2005中,格式如下
![ContractedBlock.gif](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
<Parameters>
<Parameter>
<Type>System.String</Type>
<ParameterName>inputMessage</ParameterName>
</Parameter>
<Parameter>
<Type>System.Int32</Type>
<ParameterName>inputInt</ParameterName>
</Parameter>
</Parameters>
为了方便起见我们在这里定义一个FunctionScript的分布类来处理参数问题
![ContractedBlock.gif](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
namespace Phenix.DynamicCompiler
{
public partial class FunctionScript
{
/// <summary>
/// 返回参数列表
/// </summary>
/// <returns></returns>
public List<CodeParameterDeclarationExpression> GetParameterList()
{
List<CodeParameterDeclarationExpression> parameterList = new List<CodeParameterDeclarationExpression>();
if (this.Parameters!=null)
{
var x = from n in this.Parameters.Elements("Parameter")
select n;
foreach (var item in x)
{
parameterList.Add(new CodeParameterDeclarationExpression(
Type.GetType((string)item.Element("Type")),
(string)item.Element("ParameterName")));
}
}
return parameterList;
}
}
}
然后我们需要一个代码构造类,用于根据情况构造代码:
![ContractedBlock.gif](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
namespace Phenix.DynamicCompiler
{
public class CodeBuilder
{
StringCollection importNameSpace;
List<FunctionScript> functionScriptList;
string typeNamespace;
string className;
/// <summary>
///
/// </summary>
/// <param name="importNameSpace">导入命名空间</param>
/// <param name="className">类名</param>
/// <param name="functionInfoList">函数列表</param>
public CodeBuilder(StringCollection importNameSpace,string typeNamespace,string className,
List<FunctionScript> functionScriptList)
{
if (functionScriptList == null || functionScriptList.Count==0)
{
throw new Exception("函数列表不能为空");
}
this.importNameSpace = importNameSpace;
this.typeNamespace = typeNamespace;
this.className = className;
this.functionScriptList = functionScriptList;
if (functionScriptList.GroupBy(c => c.ClassName).Count() > 1)
{
throw new Exception("这些函数不属于一个类");
}
}
public CodeBuilder(string typeNamespace,string className,
List<FunctionScript> functionScriptList):this( null,typeNamespace,className,functionScriptList)
{
}
public CodeBuilder(List<FunctionScript> functionScriptList):
this(functionScriptList.Count==0?null:functionScriptList[0].ClassNameSpace,
functionScriptList.Count == 0 ? null : functionScriptList[0].ClassName,
functionScriptList)
{
}
private CodeCompileUnit CreateExecutionClass()
{
// 创建CodeCompileUnit以包含代码
CodeCompileUnit ccu = new CodeCompileUnit(); // 分配需要的命名空间
CodeNamespace cns = new CodeNamespace(typeNamespace);
cns.Imports.Add(new CodeNamespaceImport("System"));
ccu.Namespaces.Add(cns); // 创建新的类声明
CodeTypeDeclaration codeClass = new CodeTypeDeclaration(className);
cns.Types.Add(codeClass); // 在命名空间下加入类
foreach (var functionScript in functionScriptList)
{
CodeMemberMethod method = new CodeMemberMethod();
method.Name = functionScript.FunctionName;
//方法的访问标识符为public static
method.Attributes = MemberAttributes.Static | MemberAttributes.Public;
foreach (var parameter in functionScript.GetParameterList())
{
method.Parameters.Add(parameter);
}
if (functionScript.ReturnType != null)
{
method.ReturnType = new CodeTypeReference(Type.GetType(functionScript.ReturnType));
}
// 添加方法实体需要的代码
CodeSnippetStatement methodBody = new CodeSnippetStatement(functionScript.ScriptBody);
method.Statements.Add(methodBody);
codeClass.Members.Add(method);
}
return ccu;
}
public string GenerateCode()
{
// 调用我们前面的方法创建CodeCompileUnit
CodeCompileUnit ccu = CreateExecutionClass();
CSharpCodeProvider provider = new CSharpCodeProvider();
CodeGeneratorOptions options = new CodeGeneratorOptions();
options.BlankLinesBetweenMembers = false;
options.BracingStyle = "C";//大括号换行
options.IndentString = "\t";
StringWriter sw = new StringWriter();
try
{
provider.GenerateCodeFromCompileUnit(ccu, sw, options);
sw.Flush();
}
finally
{
sw.Close();
}
return sw.GetStringBuilder().ToString();
}
}
}
下面我们再编写一个用于编译的类:
![ContractedBlock.gif](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
namespace Phenix.DynamicCompiler
{
public class PhenixCompiler
{
/// <summary>
/// 编译
/// </summary>
/// <param name="codeString">需要编译的代码</param>
/// <param name="outputAssembly">输出程序集位置</param>
public void Compile(string codeString, string outputAssembly)
{
CompilerParameters compilerParams = new CompilerParameters();
///编译器选项设置
compilerParams.CompilerOptions = "/target:library /optimize";
compilerParams.OutputAssembly = outputAssembly;
///生成调试信息
compilerParams.IncludeDebugInformation = false;
///添加相关的引用
foreach (var item in ReferencedAssemblies)
{
compilerParams.ReferencedAssemblies.Add(item);
}
CSharpCodeProvider provider = new CSharpCodeProvider();
///编译
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, codeString);
if (results.Errors.HasErrors)
{
StringBuilder errors = new StringBuilder();
foreach (CompilerError item in results.Errors)
{
errors.AppendLine(item.ToString());
}
throw new Exception(errors.ToString());
}
///创建程序集
Assembly asm = results.CompiledAssembly;
}
private StringCollection ReferencedAssemblies
{ get; set; }
public PhenixCompiler(StringCollection referencedAssemblies)
{
ReferencedAssemblies = referencedAssemblies;
ReferencedAssemblies.Add("mscorlib.dll");
ReferencedAssemblies.Add("System.dll");
}
public PhenixCompiler()
: this(new StringCollection())
{ }
}
}
我们再构造一个用于执行的类:
![ContractedBlock.gif](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
namespace DynamicCompiler
{
public class Executor
{
string inputAssembly;
string instanceName;
string methodName;
public void Execute()
{
Assembly assembly = Assembly.LoadFrom(inputAssembly);
MethodInfo mi;
Type t = assembly.GetType(instanceName);
object mode = assembly.CreateInstance(instanceName);
mi = t.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public);
mi.Invoke(mode, new object[0]);
}
public Executor(string inputAssembly,string instanceName,string methodName)
{
this.inputAssembly = inputAssembly;
this.instanceName = instanceName;
this.methodName = methodName;
}
}
}
主函数代码如下:
![ContractedBlock.gif](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
class Program
{
const string functionIndentSpace = "\t\t\t";
static void Main(string[] args)
{
XElement f1parameter = new XElement("Parameters",
new XElement("Parameter",
new XElement("Type", "System.String"),
new XElement("ParameterName", "inputMessage")),
new XElement("Parameter",
new XElement("Type", "System.Int32"),
new XElement("ParameterName", "inputInt"))
);
FunctionScript f1 = new FunctionScript()
{
ClassName = "MyClass",
ClassNameSpace = "My",
FunctionName = "hello",
ReturnType="System.String",
Parameters=f1parameter,
ScriptBody = functionIndentSpace+"return \"hello\"+inputInt.ToString();"
};
FunctionScript f2 = new FunctionScript()
{
ClassName = "MyClass",
ClassNameSpace = "My",
FunctionName = "hello1",
ScriptBody = functionIndentSpace + "Console.WriteLine(hello(\"x\",1));"
};
List<FunctionScript> list=new List<FunctionScript>();
list.Add(f1);
list.Add(f2);
CodeBuilder c = new CodeBuilder(list);
Console.WriteLine(c.GenerateCode());
Compiler pc = new Compiler();
pc.Compile(c.GenerateCode(), "x.dll");
Executor ex = new Executor("x.dll", "My.MyClass", "hello1");
ex.Execute();
}
}
运行结果如下:
这里边工作的四个对象的时序图如下:
在项目中应用可以结合http://www.cnblogs.com/nuaalfm/archive/2008/09/08/1286640.html中的插件编程