logo头像

Edward.K Thinking

動態呼叫不同種類的Web Service

呼叫Web Service最簡單方式就是在專案中把Web Service參考進來就可以使用。不過,今天要設計一個是可以把Web Service資訊放入資料庫中,系統可以針對不同需求呼叫所需要的Web Serivce的Method並傳入參數,取得對應的值,大致架構如下:

這樣好處在於當Web Service是其他單位設計時候,其他同仁可以專注在Web Service開發,開發完畢後將相關資訊註冊到,資料庫中這樣系統就可以動態取得Web Service並呼叫。
註冊該Web Service時,只需要幾個Info就可以

  • Web Service Address
  • Web Service Name
  • Web Server Method及該Method對應的參數值

實作此方式需要先實踐幾個動作

  • 取得該Web Service的WSDL描述
  • 讀取該Web Service WSDL內的XML描述
  • 產生對應Web Service Proxy並動態編譯為Assembly呼叫會使用到的Namespace

會使用的Namespace


  1. 需要語言編譯Assembly的語言,設定為C#

    1
    using Microsoft.CSharp;
  2. 動態原始程式碼的產生和編譯

    1
    2
    using System.CodeDom;
    using System.CodeDom.Compiler;
  3. 取得Web 服務描述語言WSDL,透過機制可以讓我們動態取用Web Service及其Method跟輸入參數

    1
    using System.Web.Services.Description;

產生Client Proxy 類別


要了解整個Web Service內的Method及操作相關資訊,最重要的部分就是要先了解其服務描述,因此透過服務描述就可以開始操作相關Service動作服務描述範例會示範服務如何在執行階段擷取其服務描述資訊。此範例是以使用者入門範例為基礎,同時包含已定義可傳回有關服務之描述資訊的其他服務作業。傳回的資訊會列出服務的基底位址與端點。服務會使用
OperationContextServiceHostServiceDescription類別提供這項資訊。

  1. 取得Web Service的WSDL描述的XML字串,其中_UriAddress為Web Service的Address

    1
    XmlTextReader XTR = new XmlTextReader(_UriAddress.ToString() + "?WSDL")
  2. 讀取到Web Service的WSDL內容轉成用戶端的Proxy類別,這樣才有辦法供自己進行呼叫。要將WSDL轉成Proxy,Assembly則用ServiceDescriptionImporter類別

    1
    ServiceDescriptionImporter drimport = GetWSDLProxy(XTR);
  3. XmlTextReader讀到的xml字串轉換為具有適當命名空間、項目和屬性的有效Web服務描述語言WSDL文件,ServiceDescription類別使用new關鍵字或靜態Read方法加以建立,該方法會剖析WSDL,並將它的值指派至適當的類別成員

    1
    2
    3
    4
    5
    6
    if(!ServiceDescription.CanRead(_XTR))
    {
    throw new Exception("WSDL的Description錯誤");
    }
    //取得WSDL內容
    ServiceDescription Description = ServiceDescription.Read(_XTR);
    • 透過CanRead先判斷XML格式是否可以有效解析
    • Read 方法加以建立物件
  4. 產生用戶端的Web Service Proxy

    1
    ServiceDescriptionImporter oProxy = new ServiceDescriptionImporter();
  5. 設定ProtocoName屬性

    1
    oProxy.ProtocolName = "SOAP";
  6. 設定其他屬性

    1
    2
    3
    oProxy.AddServiceDescription(Description, null, null);
    //Style選擇Client
    oProxy.Style = ServiceDescriptionImportStyle.Client;
  7. 定義XML基礎型別

    1
    oProxy.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties;

這樣就可以取得該Web Service的WSDL並且產生Web Service Proxy的類別,後續再將將此類別編譯成Assembly供系統使用

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private ServiceDescriptionImporter GetWSDLProxy(XmlTextReader _XTR)
{
if(!ServiceDescription.CanRead(_XTR))
{
throw new Exception("WSDL的Description錯誤");
}
//取得WSDL內容
ServiceDescription Description = ServiceDescription.Read(_XTR);
//產生Web Service Proxy
ServiceDescriptionImporter oProxy = new ServiceDescriptionImporter();
oProxy.ProtocolName = "SOAP";
oProxy.AddServiceDescription(Description, null, null);
//Style選擇Client
oProxy.Style = ServiceDescriptionImportStyle.Client;
oProxy.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties;
return oProxy;
}

即時編譯Proxy Class


需要再將類別透過C#編譯為Assembly,供內部程式作為使用。欲做到這樣動作,需要用CodeNamespace & CodeCompileUnit兩個類別。以上兩個類別都是屬於System.CodeDom NameSpace

  1. CodeCompileUnit & CodeNamespace 類別

    1
    2
    CodeNamespace oCodeNamespace = new CodeNamespace();
    CodeCompileUnit oCodeCompileUnit = new CodeCompileUnit();
  2. 新增一個命名空間

    1
    oCodeCompileUnit.Namespaces.Add(oCodeNamespace);
  3. 匯入指定的 ServiceDescriptions 值並產生警告型別的物件

    1
    ServiceDescriptionImportWarnings Warning = _sim.Import(oCodeNamespace, oCodeCompileUnit);
  4. 因為是要編譯是Web Service及XML的Assembly的物件,所以,需要相關性的dll協助我們做這些事情,因此,在後面的編譯器,需要參考到System.Web.Services.dllSystem.Xml.dll

    1
    string[] references = new string[2] { "System.Web.Services.dll", "System.Xml.dll" };
  5. 給定編譯器所需要的參數

    1
    CompilerParameters parameters = new CompilerParameters(references);
  6. 回傳編譯的結果,因為是要編譯成C#,所以,需要再額外參考Microsoft.CSharp Namespace,這樣才可以提供CSharpCodeProvider作為編譯器,這樣可以把CodeCompileUnit物件之指定陣列所包含的System.CodeDom樹狀結構,編譯一個組件

    1
    CompilerResults results = new CSharpCodeProvider().CompileAssemblyFromDom(parameters, oCodeCompileUnit);

若是使用CompileAssemblyFromFile,將可以指定檔案所包含的原始程式碼中編譯組件

  1. 最後回傳Assembly
    1
    2
    3
    4
    5
    foreach (CompilerError oops in results.Errors)
    {
    throw new Exception("Compilation Error Creating Assembly");
    }
    return results.CompiledAssembly;

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
CodeNamespace oCodeNamespace = new CodeNamespace();
CodeCompileUnit oCodeCompileUnit = new CodeCompileUnit();
oCodeCompileUnit.Namespaces.Add(oCodeNamespace);
ServiceDescriptionImportWarnings Warning = _sim.Import(oCodeNamespace, oCodeCompileUnit);

if (Warning == 0)
{

string[] references = new string[2] { "System.Web.Services.dll", "System.Xml.dll" };
CompilerParameters parameters = new CompilerParameters(references);
CompilerResults results = new CSharpCodeProvider().CompileAssemblyFromDom(parameters, oCodeCompileUnit);

foreach (CompilerError oops in results.Errors)
{
throw new Exception("Compilation Error Creating Assembly");
}
return results.CompiledAssembly;
}
else
{
throw new Exception("錯誤的 WSDL");
}

上一篇