.net项目的二次开发解决方案

本文介绍了一种使用.NET框架中的动态编译技术进行二次开发的方法。通过动态生成和编译C#代码,可以实现灵活的业务逻辑定制。文章详细展示了如何构建、编译和执行动态生成的代码,并给出了一种复杂的应用场景示例。
公司原来项目的二次开发方式主要使用SQL,基本上也能满足客户的要求,优点是使用简单,只要熟悉SQL语句就可以操作,缺点是受限制太多,需要对数据库底层相当的了解,使用时容易出错,无法直接调用业务层代码等,研究了一下.net的动态编译,感觉用它来做二次开发效果应该不错的。
首先我们先做个demo来解释一下动态编译,下面这段代码的意思就是先组织一个源码字符串,然后编译执行。

 
动态编译简单代码
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]); //执行方法
  }
  }
  }


 

了解了上面这段代码,我们基本对动态编译的概念清楚了,但是如果在项目中这样使用的话,我们要自己去控制源代码的全部文档内容,如果大规模应用的话会非常的麻烦,需要重复编写命名空间构造,类构造,函数构造等,如果我们还想看到一个格式良好的源码,我们还必须自己控制格式。现在我们来介绍一种源码构造方式来解决这些问题,请看如下代码:

 
动态编译简单代码改进
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中,格式如下

参数格式
<Parameters>
  <Parameter>
  <Type>System.String</Type>
  <ParameterName>inputMessage</ParameterName>
  </Parameter>
  <Parameter>
  <Type>System.Int32</Type>
  <ParameterName>inputInt</ParameterName>
  </Parameter>
</Parameters> 

为了方便起见我们在这里定义一个FunctionScript的分布类来处理参数问题

 

参数处理
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;
  }
  }


 

然后我们需要一个代码构造类,用于根据情况构造代码:

 
代码构造类
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();
  }
  }


 

下面我们再编写一个用于编译的类:

 
编译类
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())
  { }
  }


 

我们再构造一个用于执行的类:
执行类
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;
  }
  }


 

主函数代码如下:

 
主程序
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();

  }
  } 

 

运行结果如下:



这里边工作的四个对象的时序图如下:
看原文
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值