前言
站在开发者的角度,WebService 技术确实是不再“时髦”。甚至很多人会说,我们不再用它。当然,为了使软件可以更简洁,更有层次,更易于实现缓存等机制,我是非常建议将 SOAP 转为 RESTful 架构风格的。但到目前为止,WebService 在一些Public Institution 中使用还是十分广泛的。
这里主要讨论一下关于WebService的调用问题。关于WebService 的调用分为静态调用和动态调用两种,外加直接Http请求方式
静态调用
静态调用的方式是通过“Add Service Reference...”创建客户端代理类。这种方式让VS.NET环境来为我们生成服务代理,然后调用对应的Web服务。这样是使工作简单了,但是却将提供Web服务的URL、方法名、参数绑定在一起了,这是VS.NET自动为我们生成Web服务代理的限制。如果发布Web服务的URL改变了,则我们需要重新让VS.NET生成代理,并重新编译。很常见的一个场景,某银行Web服务,因为部署的URL更改,而不得不去重新编译生成代理,这将会带来很多不必要的工作量。如果我们使用动态调用就可以避免这种情况。关于静态调用,不是这篇文章的重点,故不作详细介绍。
动态调用
在某些情况下我们需要在程序运行期间动态调用一个服务。在 .NET Framework 的 System.Web.Services.Description 命名空间中有我们需要的东西。动态调用有动态调用 WebService、生成客户端代理程序集文件、生成客户端代理类源代码3种方式。
动态调用的具体步骤为:
1)从目标 URL 下载 WSDL 数据;
2)使用 ServiceDescription 创建和格式化 WSDL 文档文件;
3)使用 ServiceDescriptionImporter 创建客户端代理类;
4)使用 CodeDom 动态创建客户端代理类程序集;
5)利用反射调用相关 WebService 方法。
第一种方式通过在内存中创建动态程序集的方式完成了动态调用过程;
第二种方式将客户端代理类生成程序集文件保存到硬盘,然后可以通过 Assembly.LoadFrom() 载入并进行反射调用。对于需要多次调用的系统,要比每次生成动态程序集效率高出很多;
第三种方式是保存源码文件到硬盘中,然后再进行反射调用。
第一种方式 内存中动态程序集调用
通常我们在程序中需要调用WebService时,都是通过“添加Web引用”,让VS.NET环境来为我们生成服务代理,然后调用对应的Web服务。这样是使工作简单了,但是却和提供Web服务的URL、方法名、参数绑定在一起了,这是VS.NET自动为我们生成Web服务代理的限制。如果哪一天发布Web服务的URL改变了,则我们需要重新让VS.NET生成代理,并重新编译。在某些情况下,这可能是不能忍受的,我们需要动态调用WebService的能力。比如我们可以把Web服务的URL保存在配置文件中,这样,当服务URL改变时,只需要修改配置文件就可以了。
说了这么多,实际上我们要实现这样的功能:
public static object InvokeWebService(string url, string methodname, object[] args)
其中,url是Web服务的地址,methodname是要调用服务方法名,args是要调用Web服务所需的参数,返回值就是web服务返回的结果了。
要实现这样的功能,你需要这几个方面的技能:反射、CodeDom、编程使用C#编译器、WebService。在了解这些知识后,就可以容易的实现web服务的动态调用了:
public class WebServiceHelper { #region InvokeWebService //动态调用web服务 public static object InvokeWebService(string url, string methodname, object[] args) { return WebServiceHelper.InvokeWebService(url, null, methodname, args); } public static object InvokeWebService(string url, string classname, string methodname, object[] args) { string @namespace = "EnterpriseServerBase.WebService.DynamicWebCalling"; if ((classname == null) || (classname == "")) { classname = WebServiceHelper.GetWsClassName(url); } try { //获取WSDL WebClient wc = new WebClient(); Stream stream = wc.OpenRead(url + "?WSDL"); ServiceDescription sd = ServiceDescription.Read(stream); ServiceDescriptionImporter sdi = new ServiceDescriptionImporter(); sdi.AddServiceDescription(sd, "", ""); CodeNamespace cn = new CodeNamespace(@namespace); //生成客户端代理类代码 CodeCompileUnit ccu = new CodeCompileUnit(); ccu.Namespaces.Add(cn); sdi.Import(cn, ccu); CSharpCodeProvider csc = new CSharpCodeProvider(); ICodeCompiler icc = csc.CreateCompiler(); //设定编译参数 CompilerParameters cplist = new CompilerParameters(); cplist.GenerateExecutable = false; cplist.GenerateInMemory = true; cplist.ReferencedAssemblies.Add("System.dll"); cplist.ReferencedAssemblies.Add("System.XML.dll"); cplist.ReferencedAssemblies.Add("System.Web.Services.dll"); cplist.ReferencedAssemblies.Add("System.Data.dll"); //编译代理类 CompilerResults cr = icc.CompileAssemblyFromDom(cplist, ccu); if (true == cr.Errors.HasErrors) { System.Text.StringBuilder sb = new System.Text.StringBuilder(); foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors) { sb.Append(ce.ToString()); sb.Append(System.Environment.NewLine); } throw new Exception(sb.ToString()); } //生成代理实例,并调用方法 System.Reflection.Assembly assembly = cr.CompiledAssembly; Type t = assembly.GetType(@namespace + "." + classname, true, true); object obj = Activator.CreateInstance(t); System.Reflection.MethodInfo mi = t.GetMethod(methodname); return mi.Invoke(obj, args); } catch (Exception ex) { throw new Exception(ex.InnerException.Message, new Exception(ex.InnerException.StackTrace)); } } private static string GetWsClassName(string wsUrl) { string[] parts = wsUrl.Split('/'); string[] pps = parts[parts.Length - 1].Split('.'); return pps[0]; } #endregion }
上面的注释已经很好的说明了各代码段的功能,下面给个例子看看,这个例子是通过访问http://www.webservicex.net/globalweather.asmx 服务来获取各大城市的天气状况。
string url = "http://www.webservicex.net/globalweather.asmx" ; string[] args = new string[2] ; args[0] = "" ; args[1] = "China" ; object result = WebServiceHelper.InvokeWebService(url ,"GetWeather" ,args) ;
上述的例子中,调用web服务使用了两个参数,第一个是城市的名字,第二个是国家的名字,Web服务返回的是XML文档,可以从其中解析出温度、风力等天气情况。
最后说一下,C#虽然仍属于静态语言之列,但是其动态能力也是很强大的,不信,你可以看看spring.NET的AOP实现,这种“无侵入”的AOP实现比通常的.NET声明式AOP实现(一般是通过AOP Attribute)要漂亮的多。
/// <summary< /// WebService代理类 /// </summary< public class WebServiceAgent { private object agent; private Type agentType; private const string CODE_NAMESPACE = "Beyondbit.WebServiceAgent.Dynamic"; /// <summary< /// 构造函数 /// </summary< /// <param name="url"<</param< public WebServiceAgent(string url) { XmlTextReader reader = new XmlTextReader(url + "?wsdl"); //创建和格式化 WSDL 文档 ServiceDescription sd = ServiceDescription.Read(reader); //创建客户端代理代理类 ServiceDescriptionImporter sdi = new ServiceDescriptionImporter(); sdi.AddServiceDescription(sd, null, null); //使用 CodeDom 编译客户端代理类 CodeNamespace cn = new CodeNamespace(CODE_NAMESPACE); CodeCompileUnit ccu = new CodeCompileUnit(); ccu.Namespaces.Add(cn); sdi.Import(cn, ccu); Microsoft.CSharp.CSharpCodeProvider icc = new Microsoft.CSharp.CSharpCodeProvider(); CompilerParameters cp = new CompilerParameters(); CompilerResults cr = icc.CompileAssemblyFromDom(cp, ccu); agentType = cr.CompiledAssembly.GetTypes()[0]; agent = Activator.CreateInstance(agentType); } ///<summary< ///调用指定的方法 ///</summary< ///<param name="methodName"<方法名,大小写敏感</param< ///<param name="args"<参数,按照参数顺序赋值</param< ///<returns<Web服务的返回值</returns< public object Invoke(string methodName, params object[] args) { MethodInfo mi = agentType.GetMethod(methodName); return this.Invoke(mi, args); } ///<summary< ///调用指定方法 ///</summary< ///<param name="method"<方法信息</param< ///<param name="args"<参数,按照参数顺序赋值</param< ///<returns<Web服务的返回值</returns< public object Invoke(MethodInfo method, params object[] args) { return method.Invoke(agent, args); } public MethodInfo[] Methods { get { return agentType.GetMethods(); } } }
第二种方式 客户端代理类生成程序集文件保存到硬盘
第二种方式,这种方式也是我们在实际应用中最常用的。这种方式只下载 一次 WSDL 信息并创建代理类的程序集。往后程序每次启动都会反射之前创建好的程序集。如果是 Web服务 URL 变更,只需要修改 App.config 中的 WebServiceUrl 和 ProxyClassName 配置项,并将程序根目录下生成的程序集删除即可。下次程序启动又会重新下载WSDL信息并创建代理类的程序集,App.config文件。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!--WebService地址-->
<add key="WebServiceUrl" value="http://localhost:25060/testService/" />
<!--WebService输出dll文件名称-->
<add key="OutputDllFilename" value="TestWebService.dll" />
<!--WebService代理类名称-->
<add key="ProxyClassName" value="TestService" />
</appSettings>
</configuration>
具体代码如下
public class WSHelper { /// <summary> /// 输出的dll文件名称 /// </summary> private static string m_OutputDllFilename; /// <summary> /// WebService代理类名称 /// </summary> private static string m_ProxyClassName; /// <summary> /// WebService代理类实例 /// </summary> private static object m_ObjInvoke; /// <summary> /// 接口方法字典 /// </summary> private static Dictionary<EMethod, MethodInfo> m_MethodDic = new Dictionary<EMethod, MethodInfo>(); /// <summary> /// 创建WebService,生成客户端代理程序集文件 /// </summary> /// <param name="error">错误信息</param> /// <returns>返回:true或false</returns> public static bool CreateWebService(out string error) { try { error = string.Empty; m_OutputDllFilename = ConfigurationManager.AppSettings["OutputDllFilename"]; m_ProxyClassName = ConfigurationManager.AppSettings["ProxyClassName"]; string webServiceUrl = ConfigurationManager.AppSettings["WebServiceUrl"]; webServiceUrl += "?WSDL"; // 如果程序集已存在,直接使用 if (File.Exists(Path.Combine(Environment.CurrentDirectory, m_OutputDllFilename))) { BuildMethods(); return true; } //使用 WebClient 下载 WSDL 信息。 WebClient web = new WebClient(); Stream stream = web.OpenRead(webServiceUrl); //创建和格式化 WSDL 文档。 if (stream != null) { // 格式化WSDL ServiceDescription description = ServiceDescription.Read(stream); // 创建客户端代理类。 ServiceDescriptionImporter importer = new ServiceDescriptionImporter { ProtocolName = "Soap", Style = ServiceDescriptionImportStyle.Client, CodeGenerationOptions = CodeGenerationOptions.GenerateProperties | CodeGenerationOptions.GenerateNewAsync }; // 添加 WSDL 文档。 importer.AddServiceDescription(description, null, null); //使用 CodeDom 编译客户端代理类。 CodeNamespace nmspace = new CodeNamespace(); CodeCompileUnit unit = new CodeCompileUnit(); unit.Namespaces.Add(nmspace); ServiceDescriptionImportWarnings warning = importer.Import(nmspace, unit); CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp"); CompilerParameters parameter = new CompilerParameters { GenerateExecutable = false, // 指定输出dll文件名。 OutputAssembly = m_OutputDllFilename }; parameter.ReferencedAssemblies.Add("System.dll"); parameter.ReferencedAssemblies.Add("System.XML.dll"); parameter.ReferencedAssemblies.Add("System.Web.Services.dll"); parameter.ReferencedAssemblies.Add("System.Data.dll"); // 编译输出程序集 CompilerResults result = provider.CompileAssemblyFromDom(parameter, unit); // 使用 Reflection 调用 WebService。 if (!result.Errors.HasErrors) { BuildMethods(); return true; } else { error = "反射生成dll文件时异常"; } stream.Close(); stream.Dispose(); } else { error = "打开WebServiceUrl失败"; } } catch (Exception ex) { error = ex.Message; } return false; } /// <summary> /// 反射构建Methods /// </summary> private static void BuildMethods() { Assembly asm = Assembly.LoadFrom(m_OutputDllFilename); //var types = asm.GetTypes(); Type asmType = asm.GetType(m_ProxyClassName); m_ObjInvoke = Activator.CreateInstance(asmType); //var methods = asmType.GetMethods(); var methods = Enum.GetNames(typeof(EMethod)).ToList(); foreach (var item in methods) { var methodInfo = asmType.GetMethod(item); if (methodInfo != null) { var method = (EMethod)Enum.Parse(typeof(EMethod), item); m_MethodDic.Add(method, methodInfo); } } } /// <summary> /// 获取请求响应 /// </summary> /// <param name="method">方法</param> /// <param name="para">参数</param> /// <returns>返回:Json串</returns> public static string GetResponseString(EMethod method, params object[] para) { string result = null; if (m_MethodDic.ContainsKey(method)) { var temp = m_MethodDic[method].Invoke(m_ObjInvoke, para); if (temp != null) { result = temp.ToString(); } } return result; } }
// SOAP 请求响应方式 TextBox3.Text = WSHelper.GetResponseString(EMethod.Add, Convert.ToInt32(TextBox1.Text), Convert.ToInt32(TextBox2.Text));
第三种方式 略
Http请求
除了静态调用和动态调用,我们还可以发送HttpPost请求来调用WebService的方法。Soap请求就是HTTP POST的一个专用版本,遵循一种特殊的xml消息格式。使用HttpPost请求,对返回结果我们可以手动解析。下面的实现其实和调用WebAPI是完全一样的。
前题是服务器端必须支持GET与POST方式
//webServices 应该支持Get和Post调用,在web.config应该增加以下代码 //<webServices> // <protocols> // <add name="HttpGet"/> // <add name="HttpPost"/> // </protocols> //</webServices>
1 /// <summary> 2 /// 请求信息帮助 3 /// </summary> 4 public partial class HttpHelper 5 { 6 private static HttpHelper m_Helper; 7 /// <summary> 8 /// 单例 9 /// </summary> 10 public static HttpHelper Helper 11 { 12 get { return m_Helper ?? (m_Helper = new HttpHelper()); } 13 } 14 15 /// <summary> 16 /// 获取请求的数据 17 /// </summary> 18 /// <param name="strUrl">请求地址</param> 19 /// <param name="requestMode">请求方式</param> 20 /// <param name="parameters">参数</param> 21 /// <param name="requestCoding">请求编码</param> 22 /// <param name="responseCoding">响应编码</param> 23 /// <param name="timeout">请求超时时间(毫秒)</param> 24 /// <returns>返回:请求成功响应信息,失败返回null</returns> 25 public string GetResponseString(string strUrl, ERequestMode requestMode, Dictionary<string, string> parameters, Encoding requestCoding, Encoding responseCoding, int timeout = 300) 26 { 27 string url = VerifyUrl(strUrl); 28 HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(new Uri(url)); 29 30 HttpWebResponse webResponse = null; 31 switch (requestMode) 32 { 33 case ERequestMode.Get: 34 webResponse = GetRequest(webRequest, timeout); 35 break; 36 case ERequestMode.Post: 37 webResponse = PostRequest(webRequest, parameters, timeout, requestCoding); 38 break; 39 } 40 41 if (webResponse != null && webResponse.StatusCode == HttpStatusCode.OK) 42 { 43 using (Stream newStream = webResponse.GetResponseStream()) 44 { 45 if (newStream != null) 46 using (StreamReader reader = new StreamReader(newStream, responseCoding)) 47 { 48 string result = reader.ReadToEnd(); 49 return result; 50 } 51 } 52 } 53 return null; 54 } 55 56 57 /// <summary> 58 /// get 请求指定地址返回响应数据 59 /// </summary> 60 /// <param name="webRequest">请求</param> 61 /// <param name="timeout">请求超时时间(毫秒)</param> 62 /// <returns>返回:响应信息</returns> 63 private HttpWebResponse GetRequest(HttpWebRequest webRequest, int timeout) 64 { 65 try 66 { 67 webRequest.Accept = "text/html, application/xhtml+xml, application/json, text/javascript, */*; q=0.01"; 68 webRequest.Headers.Add("Accept-Language", "zh-cn,en-US,en;q=0.5"); 69 webRequest.Headers.Add("Cache-Control", "no-cache"); 70 webRequest.UserAgent = "DefaultUserAgent"; 71 webRequest.Timeout = timeout; 72 webRequest.Method = "GET"; 73 74 // 接收返回信息 75 HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse(); 76 return webResponse; 77 } 78 catch (Exception ex) 79 { 80 return null; 81 } 82 } 83 84 85 /// <summary> 86 /// post 请求指定地址返回响应数据 87 /// </summary> 88 /// <param name="webRequest">请求</param> 89 /// <param name="parameters">传入参数</param> 90 /// <param name="timeout">请求超时时间(毫秒)</param> 91 /// <param name="requestCoding">请求编码</param> 92 /// <returns>返回:响应信息</returns> 93 private HttpWebResponse PostRequest(HttpWebRequest webRequest, Dictionary<string, string> parameters, int timeout, Encoding requestCoding) 94 { 95 try 96 { 97 // 拼接参数 98 string postStr = string.Empty; 99 if (parameters != null) 100 { 101 parameters.All(o => 102 { 103 if (string.IsNullOrEmpty(postStr)) 104 postStr = string.Format("{0}={1}", o.Key, o.Value); 105 else 106 postStr += string.Format("&{0}={1}", o.Key, o.Value); 107 108 return true; 109 }); 110 } 111 112 byte[] byteArray = requestCoding.GetBytes(postStr); 113 webRequest.Accept = "text/html, application/xhtml+xml, application/json, text/javascript, */*; q=0.01"; 114 webRequest.Headers.Add("Accept-Language", "zh-cn,en-US,en;q=0.5"); 115 webRequest.Headers.Add("Cache-Control", "no-cache"); 116 webRequest.UserAgent = "DefaultUserAgent"; 117 webRequest.Timeout = timeout; 118 webRequest.ContentType = "application/x-www-form-urlencoded"; 119 webRequest.ContentLength = byteArray.Length; 120 webRequest.Method = "POST"; 121 122 // 将参数写入流 123 using (Stream newStream = webRequest.GetRequestStream()) 124 { 125 newStream.Write(byteArray, 0, byteArray.Length); 126 newStream.Close(); 127 } 128 129 // 接收返回信息 130 HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse(); 131 return webResponse; 132 } 133 catch (Exception ex) 134 { 135 return null; 136 } 137 } 138 139 140 /// <summary> 141 /// 验证URL 142 /// </summary> 143 /// <param name="url">待验证 URL</param> 144 /// <returns></returns> 145 private string VerifyUrl(string url) 146 { 147 if (string.IsNullOrEmpty(url)) 148 throw new Exception("URL 地址不可以为空!"); 149 150 if (url.StartsWith("http://", StringComparison.CurrentCultureIgnoreCase)) 151 return url; 152 153 return string.Format("http://{0}", url); 154 } 155 }
HttpPost 请求响应方式调用接口。
1 // Http Post 请求响应方式 2 string url = m_WebServiceUrl + EMethod.Add.ToString(); //@"http://localhost:25060/testService.asmx/Add"; 3 Dictionary<string, string> parameters = new Dictionary<string, string> { { "parameter1", TextBox1.Text }, { "parameter2", TextBox2.Text } }; 4 string result = HttpHelper.Helper.GetResponseString(url, ERequestMode.Post, parameters, Encoding.Default, Encoding.UTF8); 5 XElement root = XElement.Parse(result); 6 TextBox3.Text = root.Value;
关于SOAP和REST
我们都知道REST相比SOAP建议的标准更轻量级,甚到用Javascript都可以调用,使用方更方便、高效、简单。但并不是说REST就是SOAP的替代者。他们都只是实现Web Service(web服务)的两种不同的架构风格。就安全性等方面来说,SOAP还是更好的。
参考文档:
http://www.cnblogs.com/leolion/p/4757320.html
http://blog.csdn.net/ysq5202121/article/details/6942813