C# WebService動態調用
靜態調用
靜態調用的方式是通過“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() 載入並進行反射調用。對於需要多次調用的系統,要比每次生成動態程序集效率高出很多;第三種方式是保存源碼文件到硬盤中,然后再進行反射調用。
這里將只討論第二種方式,這種方式也是我們在實際應用中最常用的。這種方式只下載 一次 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));
除了靜態調用和動態調用,我們還可以發送HttpPost請求來調用WebService的方法。Soap請求就是HTTP POST的一個專用版本,遵循一種特殊的xml消息格式。使用HttpPost請求,對返回結果我們可以手動解析。下面的實現其實和調用WebAPI是完全一樣的。
Http請求
/// <summary> /// 請求信息幫助 /// </summary> public partial class HttpHelper { private static HttpHelper m_Helper; /// <summary> /// 單例 /// </summary> public static HttpHelper Helper { get { return m_Helper ?? (m_Helper = new HttpHelper()); } } /// <summary> /// 獲取請求的數據 /// </summary> /// <param name="strUrl">請求地址</param> /// <param name="requestMode">請求方式</param> /// <param name="parameters">參數</param> /// <param name="requestCoding">請求編碼</param> /// <param name="responseCoding">響應編碼</param> /// <param name="timeout">請求超時時間(毫秒)</param> /// <returns>返回:請求成功響應信息,失敗返回null</returns> public string GetResponseString(string strUrl, ERequestMode requestMode, Dictionary<string, string> parameters, Encoding requestCoding, Encoding responseCoding, int timeout = 300) { string url = VerifyUrl(strUrl); HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(new Uri(url)); HttpWebResponse webResponse = null; switch (requestMode) { case ERequestMode.Get: webResponse = GetRequest(webRequest, timeout); break; case ERequestMode.Post: webResponse = PostRequest(webRequest, parameters, timeout, requestCoding); break; } if (webResponse != null && webResponse.StatusCode == HttpStatusCode.OK) { using (Stream newStream = webResponse.GetResponseStream()) { if (newStream != null) using (StreamReader reader = new StreamReader(newStream, responseCoding)) { string result = reader.ReadToEnd(); return result; } } } return null; } /// <summary> /// get 請求指定地址返回響應數據 /// </summary> /// <param name="webRequest">請求</param> /// <param name="timeout">請求超時時間(毫秒)</param> /// <returns>返回:響應信息</returns> private HttpWebResponse GetRequest(HttpWebRequest webRequest, int timeout) { try { webRequest.Accept = "text/html, application/xhtml+xml, application/json, text/javascript, */*; q=0.01"; webRequest.Headers.Add("Accept-Language", "zh-cn,en-US,en;q=0.5"); webRequest.Headers.Add("Cache-Control", "no-cache"); webRequest.UserAgent = "DefaultUserAgent"; webRequest.Timeout = timeout; webRequest.Method = "GET"; // 接收返回信息 HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse(); return webResponse; } catch (Exception ex) { return null; } } /// <summary> /// post 請求指定地址返回響應數據 /// </summary> /// <param name="webRequest">請求</param> /// <param name="parameters">傳入參數</param> /// <param name="timeout">請求超時時間(毫秒)</param> /// <param name="requestCoding">請求編碼</param> /// <returns>返回:響應信息</returns> private HttpWebResponse PostRequest(HttpWebRequest webRequest, Dictionary<string, string> parameters, int timeout, Encoding requestCoding) { try { // 拼接參數 string postStr = string.Empty; if (parameters != null) { parameters.All(o => { if (string.IsNullOrEmpty(postStr)) postStr = string.Format("{0}={1}", o.Key, o.Value); else postStr += string.Format("&{0}={1}", o.Key, o.Value); return true; }); } byte[] byteArray = requestCoding.GetBytes(postStr); webRequest.Accept = "text/html, application/xhtml+xml, application/json, text/javascript, */*; q=0.01"; webRequest.Headers.Add("Accept-Language", "zh-cn,en-US,en;q=0.5"); webRequest.Headers.Add("Cache-Control", "no-cache"); webRequest.UserAgent = "DefaultUserAgent"; webRequest.Timeout = timeout; webRequest.ContentType = "application/x-www-form-urlencoded"; webRequest.ContentLength = byteArray.Length; webRequest.Method = "POST"; // 將參數寫入流 using (Stream newStream = webRequest.GetRequestStream()) { newStream.Write(byteArray, 0, byteArray.Length); newStream.Close(); } // 接收返回信息 HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse(); return webResponse; } catch (Exception ex) { return null; } } /// <summary> /// 驗證URL /// </summary> /// <param name="url">待驗證 URL</param> /// <returns></returns> private string VerifyUrl(string url) { if (string.IsNullOrEmpty(url)) throw new Exception("URL 地址不可以為空!"); if (url.StartsWith("http://", StringComparison.CurrentCultureIgnoreCase)) return url; return string.Format("http://{0}", url); } }
HttpPost 請求響應方式調用接口。
// Http Post 請求響應方式 string url = m_WebServiceUrl + EMethod.Add.ToString(); //@"http://localhost:25060/testService.asmx/Add"; Dictionary<string, string> parameters = new Dictionary<string, string> { { "parameter1", TextBox1.Text }, { "parameter2", TextBox2.Text } }; string result = HttpHelper.Helper.GetResponseString(url, ERequestMode.Post, parameters, Encoding.Default, Encoding.UTF8); XElement root = XElement.Parse(result); TextBox3.Text = root.Value;
關於SOAP和REST#
我們都知道REST相比SOAP建議的標准更輕量級,甚至用Javascript都可以調用,使用方更方便、高效、簡單。但並不是說REST就是SOAP的替代者。他們都只是實現Web Service(web服務)的兩種不同的架構風格。就安全性等方面來說,SOAP還是更好的。