動態(tài)調(diào)用WebService(C#)
轉(zhuǎn)自:
http://www.cnblogs.com/xuwb/archive/2012/09/25/2701629.html
通常我們在程序中需要調(diào)用WebService時,都是通過“添加Web引用”,讓VS.NET環(huán)境來為我們生成服務(wù)代理,然后調(diào)用對應(yīng)的Web服務(wù)。這樣是使工作簡單了,但是卻和提供Web服務(wù)的URL、方法名、參數(shù)綁定在一起了,這是VS.NET自動為我們生成Web服務(wù)代理的限制。如果哪一天發(fā)布Web服務(wù)的URL改變了,則我們需要重新讓VS.NET生成代理,并重新編譯。在某些情況下,這可能是不能忍受的,我們需要動態(tài)調(diào)用WebService的能力。比如我們可以把Web服務(wù)的URL保存在配置文件中,這樣,當(dāng)服務(wù)URL改變時,只需要修改配置文件就可以了。
說了這么多,實(shí)際上我們要實(shí)現(xiàn)這樣的功能:
public static object InvokeWebService(string url, string methodname, object[] args)
其中,url是Web服務(wù)的地址,methodname是要調(diào)用服務(wù)方法名,args是要調(diào)用Web服務(wù)所需的參數(shù),返回值就是web服務(wù)返回的結(jié)果了。 要實(shí)現(xiàn)這樣的功能,你需要這幾個方面的技能:反射、CodeDom、編程使用C#編譯器、WebService。在了解這些知識后,就可以容易的實(shí)現(xiàn)web服務(wù)的動態(tài)調(diào)用了:
#region InvokeWebService
//動態(tài)調(diào)用web服務(wù)
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);
CodeDomProvider provider = new CSharpCodeProvider();//設(shè)定編譯參數(shù)
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 = provider.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());
}
//生成代理實(shí)例,并調(diào)用方法
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
上面的注釋已經(jīng)很好的說明了各代碼段的功能,下面給個例子看看,這個例子是通過訪問http://www.webservicex.net/globalweather.asmx 服務(wù)來獲取各大城市的天氣狀況。
string url = "http://www.webservicex.net/globalweather.asmx" ;
string[] args = new string[2] ;
args[0] = this.textBox_CityName.Text ;
args[1] = "China" ;
object result = WebServiceHelper.InvokeWebService(url ,"GetWeather" ,args) ;
this.label_Result.Text = result.ToString() ;
上述的例子中,調(diào)用web服務(wù)使用了兩個參數(shù),第一個是城市的名字,第二個是國家的名字,Web服務(wù)返回的是XML文檔,可以從其中解析出溫度、風(fēng)力等天氣情況。
最后說一下,C#雖然仍屬于靜態(tài)語言之列,但是其動態(tài)能力也是很強(qiáng)大的,不信,你可以看看Spring.net的AOP實(shí)現(xiàn),這種“無侵入”的AOP實(shí)現(xiàn)比通常的.NET聲明式AOP實(shí)現(xiàn)(一般是通過AOP Attribute)要漂亮的多。
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Net;
using System.Web.Services.Description;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Reflection;
namespace WindowsServiceWebDefaultHotCity
{
/// <summary<
/// WebService代理類
/// </summary<
public class WebServiceAgent
{
private object agent;
private Type agentType;
private const string CODE_NAMESPACE = "Beyondbit.WebServiceAgent.Dynamic";
/// <summary<
/// 構(gòu)造函數(shù)
/// </summary<
/// <param name="url"<</param<
public WebServiceAgent(string url)
{
XmlTextReader reader = new XmlTextReader(url + "?wsdl");
//創(chuàng)建和格式化 WSDL 文檔
ServiceDescription sd = ServiceDescription.Read(reader);
//創(chuàng)建客戶端代理代理類
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<
///調(diào)用指定的方法
///</summary<
///<param name="methodName"<方法名,大小寫敏感</param<
///<param name="args"<參數(shù),按照參數(shù)順序賦值</param<
///<returns<Web服務(wù)的返回值</returns<
public object Invoke(string methodName, params object[] args)
{
MethodInfo mi = agentType.GetMethod(methodName);
return this.Invoke(mi, args);
}
///<summary<
///調(diào)用指定方法
///</summary<
///<param name="method"<方法信息</param<
///<param name="args"<參數(shù),按照參數(shù)順序賦值</param<
///<returns<Web服務(wù)的返回值</returns<
public object Invoke(MethodInfo method, params object[] args)
{
return method.Invoke(agent, args);
}
public MethodInfo[] Methods
{
get
{
return agentType.GetMethods();
}
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace WindowsApplication1
{
public partial class Form1 : Form
{
private string _url = "http://www.baidu.com";
public Form1()
{
InitializeComponent();
init_Data();
}
public void init_Data()
{
WindowsServiceWebDefaultHotCity.WebServiceAgent agent = new WindowsServiceWebDefaultHotCity.WebServiceAgent(_url);
object[] args = new object[6];
args[0] = "PEK";
args[1] = "CAN";
args[2] = "";
args[3] = "2008-08-02";
args[4] = "00:00";
args[5] = "own_9588";
string text=agent.Invoke("GetAllFlight", args).ToString();
textBox1.Text = text;
}
}
}
我們都知道,調(diào)用WS可以在工程中添加對WS的WEB引用。
但是,如果我們不想通過添加引用的方式,而是在代碼中動態(tài)引用該怎么辦呢?
首先,我們該想到WS的實(shí)現(xiàn)也是一個類的形式。
其次,WS在傳輸過程中是通過WSDL來進(jìn)行描述的(使用SOAP協(xié)議)。
因此,我們需要獲取WS的WSDL描述,并通過該描述來動態(tài)生成程序集。
最后:通過反射來獲取新生成的程序集,并調(diào)用其方法!
上述步驟需要引用如下四個名稱空間:
using System.Web.Services.Description; //WS的描述
//以下幾個用于根據(jù)描述動態(tài)生成代碼并動態(tài)編譯獲取程序集
using System.CodeDom;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
上述幾個名稱空間中包括如下幾個重要的類:
using System.Web.Services.Description下:
ServiceDescription //WS描述
ServiceDescriptionImporter //通過描述生成客戶端代理類,特別注意其中的Style
以下是MSDN對其的描述:
XML Web services 的接口通常由 Web 服務(wù)描述語言 (WSDL) 文件來說明。例如,若要獲取有關(guān)使用 http://localhost/service.asmx 處公開的 ASP.NET 的 Web 服務(wù)的 WSDL 說明,只需導(dǎo)航到 http://localhost/service.asmx?WSDL。使用 ServiceDescriptionImporter 類可以方便地將 WSDL 說明中包含的信息導(dǎo)入到 System.CodeDom.CodeCompileUnit 對象。通過調(diào)整 Style 參數(shù)的值,可以指示 ServiceDescriptionImporter 實(shí)例生成客戶端代理類(通過透明調(diào)用該類可提供 Web 服務(wù)的功能)或生成抽象類(該類封裝 Web 服務(wù)的功能而不實(shí)現(xiàn)該功能)。如果將 Style 屬性設(shè)置為 Client,則 ServiceDescriptionImporter 生成客戶端代理類,通過調(diào)用這些類來提供說明的 Web 服務(wù)的功能。如果將 Style 屬性設(shè)置為 Server,則 ServiceDescriptionImporter 實(shí)例生成抽象類,這些類表示所說明的 XML Web services 的功能而不進(jìn)行實(shí)現(xiàn)。然后,可以通過編寫從這些抽象類繼承的類來對其進(jìn)行實(shí)現(xiàn),并實(shí)現(xiàn)相關(guān)的方法。
using System.CodeDom下:
CodedomUnit //它用于設(shè)定動態(tài)代碼的名稱空間,類名等,可以通過ServiceDescriptionImporter.Import()方法將WS的描述代碼寫入該類,以作動態(tài)編譯用
using System.CodeDom.Compiler下:
CodedomProvider //用于創(chuàng)建和檢索代碼生成器和代碼編譯器的實(shí)例,我們主要用到其實(shí)現(xiàn)子類CShareCodeProvider
可以直接用CShareCodeProvider provider=new CShareCodeProvider()來生成,或者用CodedomProvider.CreateProvider("CSharp")來生成
ICodeCompiler //用于編譯基于 System.CodeDom 的源代碼表示形式。
它通過CodedomProvider的CreateCompiler()方法來
CompilerResults //表示從編譯器返回的編譯結(jié)果。 它由ICodeCompiler根據(jù)指定的編譯器設(shè)置從指定的 CodeCompileUnit 所包含的 System.CodeDom 樹中編譯程序集并返回。CompiledAssembly 屬性指示編譯的程序集。
了解如上信息后,就可動態(tài)調(diào)用WS了。
如下是摘自http://www.cnblogs.com/ruochen/archive/2007/12/11/990427.html的代碼演示:
Code
該方法可以使程序不通過web引用的方式去調(diào)用webservices方法,直接在代碼里調(diào)用該方法就能達(dá)到動態(tài)調(diào)用webservices的目的。使用前先引用System.Web.Services動態(tài)鏈接庫,是.net自帶的dll。
方法如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Web.Services.Description;
using System.CodeDom;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
namespace TestSkin
{
class Webservices
{
/// <summary<
/// 實(shí)例化WebServices
/// </summary<
/// <param name="url"<WebServices地址</param<
/// <param name="methodname"<調(diào)用的方法</param<
/// <param name="args"<把webservices里需要的參數(shù)按順序放到這個object[]里</param<
public static object InvokeWebService(string url, string methodname, object[] args)
{
//這里的namespace是需引用的webservices的命名空間,在這里是寫死的,大家可以加一個參數(shù)從外面?zhèn)鬟M(jìn)來。
string @namespace = "client";
try
{
//獲取WSDL
WebClient wc = new WebClient();
Stream stream = wc.OpenRead(url + "?WSDL");
ServiceDescription sd = ServiceDescription.Read(stream);
string classname = sd.Services[0].Name;
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();
//設(shè)定編譯參數(shù)
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());
}
//生成代理實(shí)例,并調(diào)用方法
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
{
return null;
}
}
}
}
===了解上述類和方法后,基本就可以動態(tài)調(diào)用WS了。
特別注意的是:動態(tài)編譯后需要用到反射來讀取并執(zhí)行。因此需要您了解什么是反射及如何反射。
web service的動態(tài)調(diào)用,主要有三種方法。
1、修改config文件。只要你引用了web service,就會在config文件中出現(xiàn)asmx文件的地址。只需要修改該地址即可。
2、程序修改url。web service是集成了System.Web.Service.WebService類的,而該類有一個Url屬性。通過修改該屬性可以達(dá)到與方法一一樣的效果,并且比它還要靈活。有的時候,我們需要提供一個列表,有很多的服務(wù)器讓用戶選擇。程序根據(jù)用戶的選擇連接到不同的服務(wù)器上調(diào)用web service。這時,就可以用這個方法了。
3、接口引用。有的時候,我們需要調(diào)用不同服務(wù)器上的web service,但他們彼此又不一樣,只是都實(shí)現(xiàn)了同一個接口。這時候,就可以考慮下面的這個方法。只要先引用所有的web service,然后用接口實(shí)例來保存創(chuàng)建出來的web service對象即可。
4、動態(tài)編譯。這個應(yīng)該算得上是真正意義上的動態(tài)了。有的時候,各個服務(wù)器上的web service更新比較快,我們不可能天天去更新代理類的,這個時候就可以用這個方法了。
在該方法中,有一點(diǎn)限制。就是各個服務(wù)器的web service,要么是都繼承了同一個接口,要么是都有一些同樣簽名的方法,而且service的類名最好是一樣的。不過就算不符合這條件也沒關(guān)系,后面我會在注釋中注明的。看代碼:
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Net;
using System.Reflection;
using System.Web.Services.Description;
using Microsoft.CSharp;
//獲取Web Service描述
WebClient wc= new WebClient();
Stream stream = wc.OpenRead("http://localhost/TestService.asmx?WSDL"); //這里指定你自己的web service url,一定要以?WSDL結(jié)尾
ServiceDescription sd = ServiceDescription.Read(stream);
ServiceDescriptionImporter sdi = new ServiceDescriptionImporter();
sdi.ProtocolName = "soap";
sdi.Style = ServiceDescriptionImportStyle.Client;
sdi.AddServiceDescription(sd, null, null);
//指定命名空間
CodeNamespace cn = new CodeNamespace("Test"); //這里隨便指定一個命名空間,但要與后面的一致
CodeCompileUnit ccu = new CodeCompileUnit();
ccu.Namespaces.Add(cn);
sdi.Import(cn, ccu);
建立C#編譯器
CSharpCodeProvider csc = new CSharpCodeProvider();
ICodeCompiler icc = csc.CreateCompiler();
CompilerParameters cp = new CompilerParameters();
cp.GenerateExecutable = false;
cp.GenerateInMemory = true;
//添加編譯條件
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.XML.dll");
cp.ReferencedAssemblies.Add("System.Web.Services.dll");
//編譯程序集
CompilerResults cr = icc.CompileAssemblyFromDom(cp, ccu);
//檢查是否編譯成功
if (!cr.Errors.HasErrors)
{
//編譯成功
//獲取程序集
Assembly assembly = cr.CompiledAssembly;
//獲取程序集類型
//前面的Test就是命名空間,必須要與前面指定的一致
//后面的TestService就是service的類名
//如果所有的服務(wù)器都是一致的類名,這里就可以寫死,否則要動態(tài)提供類名
Type type = assembly.GetType("Test.TestService", true);
object service = Activator.CreateInstance(type);
//獲取方法
//如果所有的服務(wù)器都是一致的方法名,這里可以寫死,否則就要動態(tài)提供方法名
MethodInfo mi = type.GetMethod("HelloWorld");
//調(diào)用方法
//如果方法沒有參數(shù),第二個參數(shù)可以傳遞null,否則就要傳遞object數(shù)組,數(shù)組元素的順序要與參數(shù)的順序一致
//如果所有服務(wù)器的方法簽名都是一致的,object數(shù)組的順序就可以寫死了,否則還要動態(tài)調(diào)整元素的數(shù)量及順序
mi.Invoke(service, null);
//最后,返回的是object類型,根據(jù)方法的簽名,把返回值轉(zhuǎn)換成不同的對象即可。
}
else
{
//這里自己處理編譯錯誤
}