http动态调用webserive
前言
传统方式调用WebService是直接引用服务,生成客户端代理类类,这种方式将ws进行了再次封装,并以代理的方式进行调用,这种方式的优点是简单,方便。
但是此种方式不足的地方是,当对方ws接口变更时,这边引用的服务同时也需要编译并部署,或者我调用方这边想动态指定不同服务地址时,此方式就不太适应这种场景了。
有一种方式是动态调用ws的方式。就是通过ws的服务描述获取生成客户端的代理并动态编译,通过反射生成实例对象,并完成ws的调用。大致代码如下
1 /// <summary> 2 /// 动态调用WebService 3 /// </summary> 4 /// <param name="url">WebService地址</param> 5 /// <param name="classname">类名</param> 6 /// <param name="methodname">方法名(模块名)</param> 7 /// <param name="args">参数列表</param> 8 /// <returns>object</returns> 9 public static object InvokeWebService(string url, string classname, string methodname, object[] args) 10 { 11 string @namespace = "ServiceBase.WebService.DynamicWebLoad"; 12 if (classname == null || classname == "") 13 { 14 classname = WebServiceHelper.GetClassName(url); 15 } 16 //获取服务描述语言(WSDL) 17 WebClient wc = new WebClient(); 18 Stream stream = wc.OpenRead(url + "?WSDL"); 19 ServiceDescription sd = ServiceDescription.Read(stream); 20 ServiceDescriptionImporter sdi = new ServiceDescriptionImporter(); 21 sdi.AddServiceDescription(sd, "", ""); 22 CodeNamespace cn = new CodeNamespace(@namespace); 23 //生成客户端代理类代码 24 CodeCompileUnit ccu = new CodeCompileUnit(); 25 ccu.Namespaces.Add(cn); 26 sdi.Import(cn, ccu); 27 CSharpCodeProvider csc = new CSharpCodeProvider(); 28 ICodeCompiler icc = csc.CreateCompiler(); 29 //设定编译器的参数 30 CompilerParameters cplist = new CompilerParameters(); 31 cplist.GenerateExecutable = false; 32 cplist.GenerateInMemory = true; 33 cplist.ReferencedAssemblies.Add("System.dll"); 34 cplist.ReferencedAssemblies.Add("System.XML.dll"); 35 cplist.ReferencedAssemblies.Add("System.Web.Services.dll"); 36 cplist.ReferencedAssemblies.Add("System.Data.dll"); 37 //编译代理类 38 CompilerResults cr = icc.CompileAssemblyFromDom(cplist, ccu); 39 if (true == cr.Errors.HasErrors) 40 { 41 System.Text.StringBuilder sb = new StringBuilder(); 42 foreach (CompilerError ce in cr.Errors) 43 { 44 sb.Append(ce.ToString()); 45 sb.Append(System.Environment.NewLine); 46 } 47 throw new Exception(sb.ToString()); 48 } 49 //生成代理实例,并调用方法 50 System.Reflection.Assembly assembly = cr.CompiledAssembly; 51 Type t = assembly.GetType(@namespace + "." + classname, true, true); 52 object obj = Activator.CreateInstance(t); 53 System.Reflection.MethodInfo mi = t.GetMethod(methodname); 54 return mi.Invoke(obj, args); 55 } 56 57 private static string GetClassName(string url) 58 { 59 string[] parts = url.Split('/'); 60 string[] pps = parts[parts.Length - 1].Split('.'); 61 return pps[0]; 62 }
但是这种方式,最明显的方式就是性能较差,每次都要重新生成反射调用,所以一般情况不用它。
那还有别的方法吗?
那我就来说说今天在工作中用到的方法。
soap请求
首先需要知道传统的调用方式的通行协议是关键,ws底层也是走的http方式,只是加了多加了soap协议在里面,那怎么拿到它的交互协议呢,有一个神器叫fiddler,它可以监测http请求和响应信息。有了它,就有了协议了,剩下就是拼装协议了。
仔细观察协议后 发现它的交互结构如下
POST /WebServiceTest/Service1.asmx HTTP/1.1 Host: localhost Content-Type: text/xml; charset=utf-8 Content-Length: length SOAPAction: "http://tempuri.org/HelloWorld" <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <HelloWorld xmlns="http://tempuri.org/"> <StudentName>string</StudentName> <PassWord>string</PassWord> </HelloWorld> </soap:Body> </soap:Envelope>
SOAPAction这个就是表示请求的具体方法,在请求流中,在写入上面的基于xml的soap协议,接下来就可以发起请求了,顺利的话就能拿到响应了。
做事有根有据,仔细并专注,不经意间,就能成功不远了。