.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则编译为execodeParameters.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="//www.zwyuanma.com/")]public interface TestService{ [OperationContract(Action = "//www.zwyuanma.com/HelloWorld", ReplyAction = "//www.zwyuanma.com/HelloWorldResponse")] string HelloWorld();}public class TestServiceClient : ClientBase, 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"); 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 proxyTypeCatch = new Dictionary(); 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; } }