[C#]動態叫用Web Service

http://www.dotblogs.com.tw/jimmyyu/archive/2009/04/22/8139.aspx

摘要

Web Service對大家來說想必都不陌生,也大都了解Web Service可以應用在哪些範圍,跨平台系統整合、跨語言整合、跨網域資料處理等繁雜的問題,在沒有Web Service前我們需要進行較繁雜的處理程序才能完成,但有了Web Service後,這樣的問題似乎很輕易的被解決了。

Web Service很方便,在.net領域,只要會寫程式的人大多可以透過VS輕易的完成一個Web Service,而呼叫端只要將這個Web Service加入WebReference後就可以使用這個Web Service中的WebMethod,在一般的應用下是這個樣子的,但在以下這個狀況,我們要使用Web Service會遭遇到一些這個問題:

※在擁有Web Service參考且發行過的站台中再次加入新的Web Service參考

而本文件的做法可以克服到以上問題。

 

一般Web Service的做法

建立Web Service

當我們建立一個Web Service時,我們會看到以下內容,VS會先為我們建立一個HelloWorld的WebMethod。

 

0 1 [C#]動態叫用Web Service[WebService(Namespace = "http://tempuri.org/")]
02 [C#]動態叫用Web Service[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
03 [C#]動態叫用Web Servicepublic class WebService : System.Web.Services.WebService
04 [C#]動態叫用Web Service{
05 [C#]動態叫用Web Service    public WebService()
06 [C#]動態叫用Web Service    {
07 [C#]動態叫用Web Service        //如果使用設計的元件,請取消註解下行程式碼 
08 [C#]動態叫用Web Service        //InitializeComponent(); 
09 [C#]動態叫用Web Service    }

10 [C#]動態叫用Web Service    [WebMethod]
11 [C#]動態叫用Web Service    public string HelloWorld()
12 [C#]動態叫用Web Service    {
13 [C#]動態叫用Web Service        return "Hello World";
14 [C#]動態叫用Web Service    }

15 [C#]動態叫用Web Service}

 

加入Web參考

當我們想要引用上述的WebMethod時,我們會採用『加入Web參考』的方式將Web Service參考進來:

clip_image002

接著將此Web參考命名為HelloService:

clip_image004

加入參考後,我們就可以在站台的App_WebReferences中看到這個Web Service:

clip_image007

使用Web Service

要使用剛剛建立的WebReference只要加入以下的程式碼就可以呼叫這個Web Service了:

1 [C#]動態叫用Web ServiceHelloService.Service tService = new HelloService.Service();
2 [C#]動態叫用Web Service string tMsg = tService.HelloWorld();

 
問題起源

發行網站

若這是一個要提供給客戶的無Source網站,我們通常會進行『發行』,發行後每個網頁的後端程式會被編譯成一個dll檔,而App_WebReferences目錄也會被編譯成一個App_WebReferences.dll,如下圖:

clip_image009

在已發行站台中加入新的Web參考

我們將發行過的網站開啟,我們可以看到這個站台中有一個PrecompiledApp.config的檔案,代表這個站台已先行編譯過了,如下圖:

clip_image011

接著我們執行先行編譯站台的Default.aspx,OK,開的起來,代表這個站台發行過程中沒有出現任何問題,接著我們為這個新站台加入另一個Web Service參考『localhost』,加入過程很順利,也沒有發生任何阻礙,

clip_image013

但當我再次嘗試開啟Default.aspx時會出現這個錯誤:

clip_image015

錯誤的訊息告知:不允許使用目錄’/TestInvokeWebservice/App_WebReferences/’因為已先行編譯應用程式,該站台我們在先前的步驟中已經發行過了,而發行過程我們也已經編譯過該網站。

 

問題發生的原因

ASP.NET 2.0在佈署上提供了兩種方式:

1. Source佈署:直接將Source放在站台上,透過動態編譯的方式compile程式

2. 無Source佈署:即先行編譯,會先將cs端的程式先compile成dll,避免程式外洩

現今架構下,產品多走2.,專案可能多走1.,在2的佈署架構下,我們會將我們的網站進行發行,發行後App_Code、App_WebReferences等ASP.NET目錄都會被各自compile成一個獨立的dll檔,並會直接放在發行後站台的bin目錄下,而dll的名稱是唯一的

當我們嘗試在一個已先行編譯過的網站中(若已包含App_Code.dll、App_WebReferences.dll)加入App_Code、App_WebReferences這兩個目錄,我們就會看到上頭的錯誤畫面。

所以我們遭遇到了一個問題,我們如何在先行編譯過的站台中加入新的Web Service參考?如果我今天接受到的就是別人已經發行過的網站,我如何去添加我想要的Web Service參考呢?

解決方案

針對此問題,我們的解決方案叫:動態叫用Web Service

您甚至不用將Web Service加入參考,你只要知道該Web Service的佈署路徑,要呼叫的function名稱等,這個作法就可以幫您呼叫到該Web Service,以下說明做法。

動態叫用function內容

在此架構下,我們寫了一個function做為動態叫用Web Service的服務,以下先說明此function的參數,本function有五個參數,內容分別如下,要特別說明的是pArgs這個參數,您需要將要呼叫的Web Service function所要的參數組成object[]傳進來:

0 1 [C#]動態叫用Web Service /// <summary> 
02 [C#]動態叫用Web Service        /// 動態呼叫Web Service
03 [C#]動態叫用Web Service        /// </summary> 
04 [C#]動態叫用Web Service        /// <param name="pUrl">WebService的http形式的位址,EX:http://www.yahoo.com/Service/Service.asmx </param> 
05 [C#]動態叫用Web Service        /// <param name="pNamespace">欲呼叫的WebService的namespace</param> 
06 [C#]動態叫用Web Service        /// <param name="pClassname">欲呼叫的WebService的class name</param> 
07 [C#]動態叫用Web Service        /// <param name="pMethodname">欲呼叫的WebService的method name</param> 
08 [C#]動態叫用Web Service        /// <param name="pArgs">參數列表,請將每個參數分別放入object[]中</param> 
09 [C#]動態叫用Web Service        /// <returns>WebService的執行結果</returns> 
10 [C#]動態叫用Web Service        /// <remarks> 
11 [C#]動態叫用Web Service        /// 如果呼叫失敗,將會拋出Exception。請呼叫的時候,適當截獲異常。 
12 [C#]動態叫用Web Service        /// 目前知道有兩個地方可能會發生異常: 
13 [C#]動態叫用Web Service        /// 1、動態構造WebService的時候,CompileAssembly失敗。 
14 [C#]動態叫用Web Service        /// 2、WebService本身執行失敗。 
15 [C#]動態叫用Web Service        /// </remarks> 

16 [C#]動態叫用Web Service        public object InvokeWebservice( string pUrl, string @pNamespace, string pClassname, string pMethodname, object[] pArgs)
17 [C#]動態叫用Web Service               WebClient tWebClient = new WebClient();
18 [C#]動態叫用Web Service                //讀取WSDL檔,確認Web Service描述內容
19 [C#]動態叫用Web Service                Stream tStream = tWebClient.OpenRead(pUrl + "?WSDL");
20 [C#]動態叫用Web Service                ServiceDescription tServiceDesp = ServiceDescription.Read(tStream);
21 [C#]動態叫用Web Service                //將讀取到的WSDL檔描述import近來
22 [C#]動態叫用Web Service                ServiceDescriptionImporter tServiceDespImport = new ServiceDescriptionImporter();
23 [C#]動態叫用Web Service                tServiceDespImport.AddServiceDescription(tServiceDesp, "", "");
24 [C#]動態叫用Web Service                CodeNamespace tCodeNamespace = new CodeNamespace(@pNamespace);
25 [C#]動態叫用Web Service                //指定要編譯程式
26 [C#]動態叫用Web Service                CodeCompileUnit tCodeComUnit = new CodeCompileUnit();
27 [C#]動態叫用Web Service                tCodeComUnit.Namespaces.Add(tCodeNamespace);
28 [C#]動態叫用Web Service                tServiceDespImport.Import(tCodeNamespace, tCodeComUnit);
29 [C#]動態叫用Web Service
30 [C#]動態叫用Web Service                //以C#的Compiler來進行編譯
31 [C#]動態叫用Web Service                CSharpCodeProvider tCSProvider = new CSharpCodeProvider();
32 [C#]動態叫用Web Service                ICodeCompiler tCodeCom = tCSProvider.CreateCompiler();
33 [C#]動態叫用Web Service
34 [C#]動態叫用Web Service                //設定編譯參數
35 [C#]動態叫用Web Service                System.CodeDom.Compiler.CompilerParameters tComPara = new  
36 [C#]動態叫用Web ServiceSystem.CodeDom.Compiler.CompilerParameters();
37 [C#]動態叫用Web Service                tComPara.GenerateExecutable = false;
38 [C#]動態叫用Web Service                tComPara.GenerateInMemory = true;
39 [C#]動態叫用Web Service
40 [C#]動態叫用Web Service                //取得編譯結果
41 [C#]動態叫用Web Service                System.CodeDom.Compiler.CompilerResults tComResult =  
42 [C#]動態叫用Web ServicetCodeCom.CompileAssemblyFromDom(tComPara, tCodeComUnit);
43 [C#]動態叫用Web Service
44 [C#]動態叫用Web Service                //如果編譯有錯誤的話,將錯誤訊息丟出
45 [C#]動態叫用Web Service                if ( true == tComResult.Errors.HasErrors)
46 [C#]動態叫用Web Service                 {
47 [C#]動態叫用Web Service                    System.Text.StringBuilder tStr = new System.Text.StringBuilder();
48 [C#]動態叫用Web Service                    foreach (System.CodeDom.Compiler.CompilerError tComError in tComResult.Errors)
49 [C#]動態叫用Web Service                    {
50 [C#]動態叫用Web Service                        tStr.Append(tComError.ToString());
51 [C#]動態叫用Web Service                        tStr.Append(System.Environment.NewLine);
52 [C#]動態叫用Web Service                    }

53 [C#]動態叫用Web Service                    throw new Exception(tStr.ToString());
54 [C#]動態叫用Web Service                }

55 [C#]動態叫用Web Service
56 [C#]動態叫用Web Service                //取得編譯後產出的Assembly
57 [C#]動態叫用Web Service                System.Reflection.Assembly tAssembly = tComResult.CompiledAssembly;
58 [C#]動態叫用Web Service                Type tType = tAssembly.GetType(@pNamespace + "." + pClassname, true, true);
59 [C#]動態叫用Web Service                object tTypeInstance = Activator.CreateInstance(tType);
60 [C#]動態叫用Web Service                //若WS有overload的話,需明確指定參數內容
61 [C#]動態叫用Web Service                Type[] tArgsType = null;
62 [C#]動態叫用Web Service                if (pArgs == null)
63 [C#]動態叫用Web Service                 {
64 [C#]動態叫用Web Service                    tArgsType = new Type[0];
65 [C#]動態叫用Web Service                }

66 [C#]動態叫用Web Service                else
67 [C#]動態叫用Web Service                 {
68 [C#]動態叫用Web Service                    int tArgsLength = pArgs.Length;
69 [C#]動態叫用Web Service                    tArgsType = new Type[tArgsLength];
70 [C#]動態叫用Web Service                    for (int i = 0; i < tArgsLength; i++)
71 [C#]動態叫用Web Service                    {
72 [C#]動態叫用Web Service                        tArgsType[i] = pArgs[i].GetType();
73 [C#]動態叫用Web Service                    }

74 [C#]動態叫用Web Service                }

75 [C#]動態叫用Web Service
76 [C#]動態叫用Web Service //若沒有overload的話,第二個參數便不需要,這邊要注意的是WsiProfiles.BasicProfile1_1本身不支援Web Service overload,因此需要改成不遵守WsiProfiles.BasicProfile1_1協議
77 [C#]動態叫用Web Service                System.Reflection.MethodInfo tInvokeMethod = tType.GetMethod(pMethodname, tArgsType);
78 [C#]動態叫用Web Service                //實際invoke該method
79 [C#]動態叫用Web Service                return tInvokeMethod.Invoke(tTypeInstance, pArgs);
80 [C#]動態叫用Web Service}

 

動態叫用function的code sample

以上的source您可隨意利用,原則上這個寫法大致滿足多數應用,而以下我們再補充AP段的寫法,當我想要透過以上的function去幫我呼叫一個外部的Web service:

以下範例說明透過DynamicInvokeWebservice. InvokeWebservice去呼叫InvokeWS中的HelloWorld function,程式的寫法如下,傳入要呼叫的WS路徑、namespace、class name、function name、參數列表,然後呼叫InvokeWebservice就可以呼叫到遠端的Web service了。

0 1 [C#]動態叫用Web Service        string tUrl = "http://localhost:8627/InvokeWS/Service.asmx";//WebService的http形式的地址
02 [C#]動態叫用Web Service        string tNamespace = "DynamicWS";//欲呼叫的WebService的命名空間
03 [C#]動態叫用Web Service        string tClassname = "Service";//欲呼叫的WebService的類名(不包括命名空間前綴)
04 [C#]動態叫用Web Service        string tMethodname = "HelloWorld";//欲呼叫的WebService的方法名
05 [C#]動態叫用Web Service        object[] tArgs = new object[2];//參數列表
06 [C#]動態叫用Web Service        tArgs[0] = "Jimmy";
07 [C#]動態叫用Web Service        tArgs[1] = "你好嗎?";
08 [C#]動態叫用Web Service
09 [C#]動態叫用Web Service        DynamicInvokeWebservice tDynamicInvokeWebservice = new DynamicInvokeWebservice();
10 [C#]動態叫用Web Service        object tReturnValue =  
11 [C#]動態叫用Web ServicetDynamicInvokeWebservice.InvokeWebservice(tUrl, tNamespace, tClassname, tMethodname, tArgs);
下面我們下了一個中斷點,經過測試後,我們確實可以呼叫到目標的Web service function,解決了我們前頭所遭遇到的問題:

 

clip_image018

 

後記

原則上這個作法應用的.net中的Reflection(反射)技術,若對這技術有興趣的人可以上MSDN參考。

 

版本修改:

如果要動態Invoke的Web Service是Windows整合驗證,那程式必須要做以下兩點修正,測試結果是沒問題的,如果Web Service有特別限定使用者的話(跨不同主機、跨Domain),請自行指定Credentials的帳號密碼:

 

1 WebClient tWebClient = new WebClient();
2 //要加這行:透過目前預設的使用者登入
3 tWebClient.Credentials = System.Net.CredentialCache.DefaultCredentials;
4 //讀取WSDL檔,確認Web Service描述內容
5 Stream tStream = tWebClient.OpenRead(pUrl + "?WSDL");

 

 

01 //若沒有overload的話,第二個參數便不需要,這邊要注意的是WsiProfiles.BasicProfile1_1本身不支援Web Service overload,因此需要改成不遵守WsiProfiles.BasicProfile1_1協議
02 System.Reflection.MethodInfo tInvokeMethod = tType.GetMethod(pMethodname, tArgsType);
03   
04 //要加這三行:如果是Windows整合驗證的話,透過SoapHttp來對要invoke的目標WS做驗證
05 SoapHttpClientProtocol webRequest = (SoapHttpClientProtocol)tTypeInstance;
06 webRequest.PreAuthenticate = true;
07 webRequest.Credentials = System.Net.CredentialCache.DefaultCredentials;
08   
09 //實際invoke該method
10 return tInvokeMethod.Invoke(tTypeInstance, pArgs);

 

參考資料:

HOW TO: 傳遞目前的憑證至 ASP.NET Web 服務

Dynamic Discovery and Invocation of Web services

baidu: 動態叫用Web Service site:dotblogs.com.tw

你可能感兴趣的:(web Service)