.NET的动态编译与WS服务调用详解

  动态编译与WS服务,有关系么?今天就乱弹一番,如何使用动态编译动态生成WS服务调用的代理类,然后通过这个代理类调用WS服务。

  首先,动态编译这玩意在.NET里面是非常简单的,实际上只涉及到两个类型:CodeDomProvider以及CompilerParameters他们都位于System.CodeDom.Compiler命名空间。

  以下代码可将源码动态编译为一个程序集:

  动态编译

  

复制代码 代码如下:

  CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");

  CompilerParameters codeParameters = new CompilerParameters();

  codeParameters.GenerateExecutable = false; //编译为dll,如果为true则编译为exe

  codeParameters.GenerateInMemory = true; //编译后的程序集保存到内存中

  StringBuilder code = new StringBuilder();

  //此处构造源代码

  CompilerResults results = provider.CompileAssemblyFromSource(codeParameters, code.ToString());

  Assembly assembly = null; //动态编译生成的程序集

  if (!results.Errors.HasErrors)

  {

  assembly = results.CompiledAssembly;

  }

  获得assembly后,随后我们即可以通过反射获取程序集里面的类型,然后实例化,调用类型方法…

  不过在此之前,我们得构造WS服务的代理类,它是什么样子的呢?我们使用WCF框架,创建服务代理类也是十分简单的,常见的代理类结构如下:

  服务调用代理类

  

复制代码 代码如下:

  [ServiceContract(Namespace="http://www.glzy8.com/")]

  public interface TestService

  {

  [OperationContract(Action = "http://www.glzy8.com/HelloWorld", ReplyAction = "http://www.glzy8.com/HelloWorldResponse")]

  string HelloWorld();

  }

  public class TestServiceClient : ClientBase<TestService>, TestService

  {

  public TestServiceClient(Binding binding, EndpointAddress address) :

  base(binding, address)

  {

  }

  public string HelloWorld()

  {

  return base.Channel.HelloWorld();

  }

  }

  所以,我们要动态构造出代理类源码,应该知道服务的命名空间、服务方法的Action地址、ReplyAction地址,当然还有服务方法的名称,返回类型,参数列表。这里,我们省略掉服务方法的参数列表,构造代理类,实际上就是一个字符串组装的问题,先创建一个类型,用于保存构造代理类所要用到的参数:

  服务代理类构造参数

  

复制代码 代码如下:

  public class WebServiceParamaters

  {

  public string address;

  public string Address

  {

  get { return address; }

  set

  {

  address = value;

  }

  }

  private string serviceNamespace;

  public string ServiceNamespace

  {

  get { return serviceNamespace; }

  set

  {

  serviceNamespace = value;

  }

  }

  private string methodAction;

  public string MethodAction

  {

  get { return methodAction; }

  set

  {

  methodAction = value;

  }

  }

  private string methodReplyAction;

  public string MethodReplyAction

  {

  get { return methodReplyAction; }

  set

  {

  methodReplyAction = value;

  }

  }

  private string methodName;

  public string MethodName

  {

  get { return methodName; }

  set

  {

  methodName = value;

  }

  }

  private string returnType;

  public string ReturnType

  {

  get { return returnType; }

  set

  {

  returnType = value;

  }

  }

  }

  好,现在我们只需要构造出代理类源码,然后动态编译出代理类的程序集,最后通过反射调用服务方法:

  WebServiceProxyCreator

  

复制代码 代码如下:

  public class WebServiceProxyCreator

  {

  public Object WebServiceCaller(WebServiceParamaters parameters)

  {

  CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");

  CompilerParameters codeParameters = new CompilerParameters();

  codeParameters.GenerateExecutable = false;

  codeParameters.GenerateInMemory = true;

  StringBuilder code = new StringBuilder();

  CreateProxyCode(code, parameters);

  codeParameters.ReferencedAssemblies.Add("System.dll");

  codeParameters.ReferencedAssemblies.Add("System.ServiceModel.dll");

  CompilerResults results = provider.CompileAssemblyFromSource(codeParameters, code.ToString());

  Assembly assembly = null;

  if (!results.Errors.HasErrors)

  {

  assembly = results.CompiledAssembly;

  }

  Type clientType = assembly.GetType("RuntimeServiceClient");

  ConstructorInfo ci = clientType.GetConstructor(new Type[] { typeof(Binding), typeof(EndpointAddress) });

  BasicHttpBinding binding = new BasicHttpBinding(); //只演示传统的WebService调用

  EndpointAddress address = new EndpointAddress(parameters.address);

  Object client = ci.Invoke(new object[] { binding, address });

  MethodInfo mi = clientType.GetMethod(parameters.MethodName);

  Object result = mi.Invoke(client, null);

  mi = clientType.GetMethod("Close"); //关闭代理

  mi.Invoke(client, null);

  return result;

  }

  public static void CreateProxyCode(StringBuilder code, WebServiceParamaters parameters)

  {

  code.AppendLine("using System;");

  code.AppendLine("using System.ServiceModel;");

  code.AppendLine("using System.ServiceModel.Channels;");

  code.Append(@"[ServiceContract(");

  if (!String.IsNullOrEmpty(parameters.ServiceNamespace))

  {

  code.Append("Namespace=\"").Append(parameters.ServiceNamespace).Append("\"");

  }

  code.AppendLine(")]");

  code.AppendLine("public interface IRuntimeService");

  code.AppendLine("{");

  code.Append("[OperationContract(");

  if (!String.IsNullOrEmpty(parameters.MethodAction))

  {

  code.Append("Action=\"").Append(parameters.MethodAction).Append("\"");

  if (!String.IsNullOrEmpty(parameters.MethodReplyAction))

  {

  code.Append(", ");

  }

  }

  if (!String.IsNullOrEmpty(parameters.MethodReplyAction))

  {

  code.Append("ReplyAction=\"").Append(parameters.MethodReplyAction).Append("\"");

  }

  code.AppendLine(")]");

  code.Append(parameters.ReturnType).Append(" ");

  code.Append(parameters.MethodName).AppendLine("();");

  code.AppendLine("}");

  code.AppendLine();

  code.AppendLine("public class RuntimeServiceClient : ClientBase<IRuntimeService>, IRuntimeService");

  code.AppendLine("{");

  code.AppendLine("public RuntimeServiceClient(Binding binding, EndpointAddress address) :base(binding, address)");

  code.AppendLine("{");

  code.AppendLine("}");

  code.Append("public ").Append(parameters.ReturnType).Append(" ");

  code.Append(parameters.MethodName).AppendLine("()");

  code.AppendLine("{");

  code.Append("return base.Channel.").Append(parameters.MethodName).AppendLine("();");

  code.AppendLine("}");

  code.AppendLine("}");

  }

  }

  注意,红色部分,由于代理类使用了WCF框架,所以编译时我们需要添加System.ServiceModel的引用,当然System.dll肯定是必须的,这里要注意,System.ServiceModel.dll应该保存到应用程序目录,否则动态编译时会引发异常,很简单,在工程引用中添加System.ServiceModel的引用,然后在属性中将拷贝到本地属性设置为true。

  到此,我们就可以直接通过传入的服务地址、服务方法名称以及相关的命名空间,即可调用服务(尽管我们只能调用无参服务,并且尽管我们也只能调用使用BasicHttpBinding绑定的服务,这些限制的原因是…我懒,好吧,相信只要经过一点改动即可去掉这些限制)。

  可惜,我们的程序还很傻:每次调用服务都需要去生成代码、编译、创建代理实例最后再调用,嗯…那就缓存吧:

  在WebServiceParameters类中重写GetHashCode方法:

  

复制代码 代码如下:

  public override int GetHashCode()

  {

  return String.Concat(serviceNamespace, methodAction, methodReplyAction, methodName, returnType).GetHashCode();

  }

  然后在WebServiceProxyCreator中加入缓存机制:

  

复制代码 代码如下:

  public class WebServiceProxyCreator

  {

  private static Dictionary<int, Type> proxyTypeCatch = new Dictionary<int, Type>();

  public Object WebServiceCaller(WebServiceParamaters parameters)

  {

  int key = parameters.GetHashCode();

  Type clientType = null;

  if (proxyTypeCatch.ContainsKey(key))

  {

  clientType = proxyTypeCatch[key];

  Debug.WriteLine("使用缓存");

  }

  else

  {

  CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");

  CompilerParameters codeParameters = new CompilerParameters();

  codeParameters.GenerateExecutable = false;

  codeParameters.GenerateInMemory = true;

  StringBuilder code = new StringBuilder();

  CreateProxyCode(code, parameters);

  codeParameters.ReferencedAssemblies.Add("System.dll");

  codeParameters.ReferencedAssemblies.Add("System.ServiceModel.dll");

  CompilerResults results = provider.CompileAssemblyFromSource(codeParameters, code.ToString());

  Assembly assembly = null;

  if (!results.Errors.HasErrors)

  {

  assembly = results.CompiledAssembly;

  }

  clientType = assembly.GetType("RuntimeServiceClient");

  proxyTypeCatch.Add(key, clientType);

  }

  ConstructorInfo ci = clientType.GetConstructor(new Type[] { typeof(Binding), typeof(EndpointAddress) });

  BasicHttpBinding binding = new BasicHttpBinding(); //只演示传统的WebService调用

  EndpointAddress address = new EndpointAddress(parameters.address);

  Object client = ci.Invoke(new object[] { binding, address });

  MethodInfo mi = clientType.GetMethod(parameters.MethodName);

  Object result = mi.Invoke(client, null);

  mi = clientType.GetMethod("Close"); //关闭代理

  mi.Invoke(client, null);

  return result;

  }

  }