使用 SOAP 擴充功能改變 SOAP 訊息
開發人員可利用 SOAP 擴充功能,改變傳入傳出 XML Web Service 或 XML Web Service 用戶端的 SOAP 訊息,來擴大 XML Web Service 的功能。例如,您可以套用加密或壓縮演算法,與現有 XML Web Service 一塊執行。
若要瞭解 SOAP 擴充功能的運作方式,最好先瞭解 XML Web Service 的存留期。如需 XML Web Service 存留期的概觀,請參閱 XML Web Service 存留期深入探索。下列圖形綱要列出從用戶端向 XML Web Service 發出呼叫的主要階段。
XML Web Service 存留期深入探索
如圖所示,在 XML Web Service 電腦及 XML Web Service 用戶端電腦階段,ASP.NET 會將 XML 序列化並還原序列化。SOAP 擴充功能可在每一個序列化和還原序列化階段前後,插入基礎架構來檢查或修改 SOAP 訊息。例如,加密 SOAP 擴充功能可在 ASP.NET 將用戶端引數序列化後,將 SOAP 訊息的 XML 部分加密。並可在ASP.NET 將 SOAP 訊息還原序列化前,將 SOAP 訊息解密至 Web 伺服器。這些階段中,SOAP 擴充功能檢查或修改 SOAP 訊息的地方定義在 SoapMessageStage 列舉型別中。這種情況下,SOAP 擴充功能會在 AfterSerialize 階段進行加密,並在 BeforeDeserialize 階段進行解密。
一般而言,SOAP 擴充功能修改 SOAP 訊息的內容時,修改必須在用戶端和伺服器進行。換言之,如果 SOAP 擴充功能要在用戶端上執行並加密 SOAP 訊息,則相對應的 SOAP 擴充功能必須在伺服器上將 SOAP 訊息解密。如果 SOAP 訊息未解密,ASP.NET 基礎架構就無法將 SOAP 訊息還原序列化為物件。當然,如果 SOAP 擴充功能沒有修改 SOAP 訊息,例如只用來記錄 SOAP 訊息的 SOAP 擴充功能,可以只在用戶端或伺服器上執行。這種情況下,接收者所收到的 SOAP 訊息,等於和未執行 SOAP 擴充功能且 ASP.NET 基礎架構可將 SOAP 訊息還原序列化時的 SOAP 訊息相同。此外,若 SOAP 擴充功能未將 SOAP 修改為無法進行還原序列化,SOAP 擴充功能便不需要在用戶端和伺服器上執行。
既然您知道了 SOAP 擴充功能能夠執行的工作及時機,就讓我們來瞭解如何建置 SOAP 擴充功能。下列為建置 SOAP 擴充功能並使用 XML Web Service 來執行的步驟:
- 從 SoapExtension 衍生類別。
- 將參考儲存至表示未來 SOAP 訊息的資料流。
- 初始化 SOAP 擴充功能的特定資料
- 在相關的 SoapMessageStage 或多個階段處理 SOAP 訊息。
- 設定 SOAP 擴充功能以使用特定 XML Web Service 方法來執行。
從 SoapExtension 衍生類別
從 SoapExtension 衍生的類別是用來執行 SOAP 擴充功能的功能類別。也就是說,如果 SOAP 擴充功能為加密的 SOAP 擴充功能,則從 SoapExtension 類別衍生而來的類別會執行加密及相對的解密。
將參考儲存至表示未來 SOAP 訊息的 Stream
若要修改 SOAP 訊息,您必須覆寫 ChainStream 方法,唯有如此,才能接收連至用來取得未來 SOAP 訊息內容資料流的參考。
Stream 的參考會在任何 SoapMessageStage 之前傳遞到 ChainStream 一次。這個 Stream 會在較低優先權的 SOAP 擴充功能已經執行並變更 SOAP 訊息後,參考 SOAP 訊息的 XML (請參閱設定 SOAP 擴充功能以配合 XML Web Service 方法來執行,取得 SOAP 擴充功能優先權的詳細資訊)。所以,SOAP 擴充功能應該將這個參考納入成員變數,以供後來 SOAP 擴充功能在檢查或修改 SOAP 訊息時在 SoapMessageStage 期間存取。
傳遞給 ChainStream 的 Stream 並非 SOAP 擴充功能應該修改的 Stream。SOAP 擴充功能應建立 Stream 的新執行個體,並將它儲存在私用 (Private) 成員變數,再將其傳回 ChainStream 方法中。SOAP 擴充功能在每個 SoapMessageStage 期間執行,並修改 SOAP 訊息時,SOAP 擴充功能應該讀取傳遞給 ChainStream 的 Stream,並寫入 ChainStream 的 Stream 傳回值。所以,您不要忘了將兩個 Stream 參考儲存在 ChainStream 方法中。
下列範例示範 ChainStream 方法的共通實作。
' Save the Stream representing the SOAP request or SOAP response
' into a local memory buffer.
Public Overrides Function ChainStream(stream As Stream) As Stream
' Save the passed in Stream in a member variable.
oldStream = stream
' Create a new instance of a Stream and save that in a member
' variable.
newStream = New MemoryStream()
Return newStream
End Function
[C#]
// Save the Stream representing the SOAP request or SOAP response into
// a local memory buffer.
public override Stream ChainStream( Stream stream ){
// Save the passed in Stream in a member variable.
oldStream = stream;
// Create a new instance of a Stream and save that in a member
// variable.
newStream = new MemoryStream();
return newStream;
}
初始化 SOAP 擴充功能的特定資料
SOAP 擴充功能可根據套用的 XML Web Service 或 XML Web Service 方法,將內部資料初始化。例如,記錄傳入傳出 XML Web Service 方法之 SOAP 訊息的 SOAP 擴充功能,可用來初始化檔案名稱,以儲存記錄資訊 (根據 XML Web Service 的名稱,或是 SOAP 擴充功能共同執行的 XML Web Service 方法)。
衍生自 SoapExtension 的類別有兩種初始化資料的方法:GetInitializer 和 Initialize。根據 SOAP 擴充功能組態的不同,第一次存取 XML Web Service 或 XML Web Service 方法,讓 SOAP 擴充功能設定為用其執行時,才會呼叫 GetInitializer (請參閱設定 SOAP 擴充功能以配合 XML Web Service 方法來執行)。如果 SOAP 擴充功能是以屬性設定,ASP.NET 基礎結構會根據每個 XML Web Service 方法呼叫 GetInitializer。如果 SOAP 擴充功能設定在組態檔中,ASP.NET 基礎架構僅會在首次存取 XML Web Service 時呼叫 GetInitializer。ASP.NET 基礎架構會攔截透過 SOAP 擴充功能從 GetInitializer 傳回的資料,供 SOAP 擴充功能日後使用。 每次 SOAP 擴充功能與 Initialize 方法的 XML Web Service 或 XML Web Service 方法共同執行時,SOAP 擴充功能會傳遞快取資料。
GetInitializer 方法中 SOAP 擴充功能可用的資訊為何,要視 SOAP 擴充功能設定的方式而定。如果 SOAP 擴充功能以屬性設定,ASP.NET 基礎架構會將屬性、與其關聯的所有自訂屬性,以及 LogicalMethodInfo 傳遞給 GetInitializer。LogicalMethodInfo 提供 XML Web Service 方法的相關原型 (Prototype) 詳細資訊,例如參數數目及其資料型別。如果 SOAP 擴充功能是以組態檔設定,只有實作 XML Web Service 的類別 Type 才會傳遞給 GetInitializer。
下列程式碼範例根據 SOAP 擴充功能最終設定的方式,以不同方式將 GetInitializer 方法內的快取資料初始化。如果 SOAP 擴充功能是以屬性設定,本例為 TraceExtensionAttribute
,則會快取屬性內指定的檔名。如果 SOAP 擴充功能是以組態檔設定,會根據 XML Web Service 型別來計算快取檔名。
' When the SOAP extension is accessed for the first time, the XML
' Web service method it is applied to is accessed to store the file
' name passed in, using the corresponding SoapExtensionAttribute.
Public Overloads Overrides Function GetInitializer(methodInfo As _
LogicalMethodInfo, attribute As SoapExtensionAttribute) As Object
Return CType(attribute, TraceExtensionAttribute).Filename
End Function
' The extension was configured to run using a configuration file
' instead of an attribute applied to a specific XML Web service method.
' Return a file name, based on the class implementing the XML Web
' service's type.
Public Overloads Overrides Function GetInitializer(WebServiceType As _
Type) As Object
' Return a file name to log the trace information, based on the type.
Return "C:\" + WebServiceType.FullName + ".log"
End Function
[C#]
// When the SOAP extension is accessed for the first time, the XML
// Web service method it is applied to is accessed to store the file
// name passed in, using the corresponding SoapExtensionAttribute.
public override object GetInitializer(LogicalMethodInfo methodInfo,
SoapExtensionAttribute attribute)
{
return ((TraceExtensionAttribute) attribute).Filename;
}
// The extension was configured to run using a configuration file instead of
// an attribute applied to a specific XML Web service method.
public override object GetInitializer(Type WebServiceType)
{
// Return a file name to log the trace information, based on the type.
return "C:\\" + WebServiceType.FullName + ".log";}
處理 SOAP 訊息
在從 SoapExtension 衍生的類別中,實作的核心片段是 SoapExtension.ProcessMessage 方法。這個方法會於 SoapMessageStage 列舉型別中定義的每個階段,由 ASP.NET 呼叫多次。每次 SoapExtension.ProcessMessage 方法被呼叫時,SoapMessage 或自其衍生的類別,會於該特定階段連同 SOAP 訊息的相關資訊一起傳遞進來。如果 SOAP 擴充功能配合 XML Web Service 執行,會傳入 SoapServerMessage。如果 SOAP 擴充功能配合 XML Web Service 用戶端執行,則會傳入 SoapClientMessage。
下列程式碼範例為追蹤傳至 XML Web Service 呼叫之 SOAP 擴充功能的 ProcessStage。追蹤期間,如果 SoapMessageStage 是參數序列化為 XML 的地方,則 XML 會寫入至檔案。
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
// Write the SOAP message out to a file.
WriteOutput( message );
break;
case SoapMessageStage.BeforeDeserialize:
//Write the SOAP message out to a file.
WriteInput( message );
break;
case SoapMessageStage.AfterDeserialize:
break;
default:
throw new Exception("invalid stage");
}
}
[Visual Basic]
Public Overrides Sub ProcessMessage(message As SoapMessage)
Select Case message.Stage
Case SoapMessageStage.BeforeSerialize
Case SoapMessageStage.AfterSerialize ' Write the SOAP message out to a file.
WriteOutput(message)Case SoapMessageStage.BeforeDeserialize' Write the SOAP messae out to a file.
WriteInput(message)
Case SoapMessageStage.AfterDeserialize
Case Else
Throw New Exception("invalid stage")
End Select
End Sub
SOAP 擴充功能方法的叫用順序
既然您已經看過必須覆寫 SOAP 擴充功能時的方法,我們接著來看看叫用 XML Web Service 方法期間,ASP.NET 何時會叫用 SOAP 擴充功能方法。下列步驟假設 SOAP 擴充功能在用戶端和伺服器上都會執行。如果 SOAP 擴充功能未在用戶端和伺服器上執行,ASP.NET 會忽略在兩邊執行的 SOAP 擴充功能關聯步驟。
用戶端
- 用戶端叫用 Proxy 類別上的方法。
- 在用戶端建立 SOAP 擴充功能的新執行個體。
- 如果這是 SOAP 擴充功能首次使用用戶端的 XML Web Service 來執行,則於用戶端執行的 SOAP 擴充功能會叫用 GetInitializer 方法。
- Initialize 方法被叫用。
- ChainStream 方法被叫用。
- ProcessMessage 方法被叫用,而且 SoapMessageStage 設定為 BeforeSerialize。
- 用戶端電腦上的 ASP.NET 將 XML Web Service 方法的引數序列化為 XML。
- ProcessMessage 方法被叫用,而且 SoapMessageStage 設定為 AfterSerialize。
- 用戶端電腦上的 ASP.NET 透過網路,將 SOAP 訊息傳送給裝載 XML Web Service 的 Web 伺服器。
伺服器端
- Web 伺服器上的 ASP.NET 收到 SOAP 訊息。
- SOAP 擴充功能的新執行個體在 Web 伺服器上建立。
- 以 Web 伺服器而言,如果這是 SOAP 擴充功能首次使用用戶端的 XML Web Service 來執行,則於伺服器執行的 SOAP 擴充功能會叫用 GetInitializer 方法。
- Initialize 方法被叫用。
- ChainStream 方法被叫用。
- ProcessMessage 方法被叫用,而且 SoapMessageStage 設定為 BeforeDeserialize。
- ASP.NET 將 XML 內的引數還原序列化。
- ProcessMessage方法被叫用,而且 SoapMessageStage 設定為 AfterDeserialize。
- ASP.NET 建立實作 XML Web Service 之類別的新執行個體,並叫用 XML Web Service 方法,傳入還原序列化文件。這個物件與 Web 伺服器位於同一台電腦。
- XML Web Service 方法執行其程式碼,最後設定傳回值,以及任何輸出參數。
- ProcessMessage 方法被叫用,而且 SoapMessageStage 設定為 BeforeSerialize。
- Web 伺服器上的 ASP.NET 將傳回值及 out 參數序列化為 XML。
- ProcessMessage 方法被叫用,而且 SoapMessageStage 設定為 AfterSerialize。
- ASP.NET 透過網路將 SOAP 回應訊息傳回 XML Web Service 用戶端。
用戶端
- 用戶端電腦上的 ASP.NET接收 SOAP 訊息。
- ProcessMessage 方法被叫用,而且 SoapMessageStage 設定為 BeforeDeserialize。
- ASP.NET 將 XML 還原序列化為傳回值和任何 out 參數。
- ProcessMessage方法被叫用,而且 SoapMessageStage 設定為 AfterDeserialize。
- ASP.NET 將傳回值及任何 out 參數傳遞給 Proxy 類別的執行個體。
- 用戶端會收到傳回值和任何輸出參數。
設定 SOAP 擴充功能以配合 XML Web Service 方法來執行
SOAP 擴充功能可經過設定,使用自訂屬性執行,或透過修改組態檔來執行。若要使用自訂屬性,請將其套用至您希望 SOAP 擴充功能配合執行的每個 XML Web Service 方法。使用組態檔時,SOAP 擴充功能會配合組態檔包含的所有 XML Web Service 來執行。如需組態檔運作方式的詳細資訊,請參閱設定應用程式。
若要使用自訂屬性,請從 SoapExtensionAttribute 衍生類別。SoapExtensionAttribute 具有兩個屬性:ExtensionType 及 Priority。SOAP 擴充功能應該傳回 ExtensionType 屬性中的 SOAP 擴充功能型別。Priority 屬性表示 SOAP 擴充功能的相對優先權,前面已經簡略提過。
若要指定 SOAP 擴充功能配合組態檔內所有 XML Web Service 來執行,請將項目加入適當的 App.config 或 Web.config 檔案。有一點要注意,必須將 soapExtensionTypes XML 項目加入組態檔內的 webServices 區段。在 soapExtensionTypes XML 項目中,將您希望配合每個 XML Web Service 來執行的每個 SOAP 擴充功能 XML 項目加入組態檔範圍內。add XML 項目具有下列屬性:
屬性 | 說明 |
---|---|
type | SOAP 擴充功能的型別和所在的組件。 |
priority | 指定 SOAP 擴充功能在其群組內的相對優先權。 |
group | 指定 SOAP 擴充功能為其成員的群組。請參閱表下關於優先權的詳細說明。 |
SOAP 擴充功能具有指定的優先權,設定多重 SOAP 擴充功能配合 XML Web Service 方法來執行時,可用來規定執行的相對順序。SOAP 擴充功能的優先權越高,它執行的位置會越接近網路上傳送或接收的 SOAP 訊息。SOAP 擴充功能是三個優先權群組中的其中一個。每個群組中,priority 屬性可用來辨別每個成員。priority 屬性越低,相對優先權越高 (0 為最高)。
SOAP 擴充功能的三個相對優先權群組為:使用屬性設定的 SOAP 擴充功能、組態檔中使用 group 設定 0 及 1 指定的 SOAP 擴充功能。而使用屬性設定的 SOAP 擴充功能為中間群組的成員。使用組態檔及 Group 設定為 0 來設定的 SOAP 擴充功能具有最高的相對優先權,而 Group 設定為 1 的則具有較低的相對優先權。
下列程式碼範例是組態檔,指定 Logger.LoggerExtension
SOAP 擴充功能在相對優先權群組 0
中執行,且優先權為 1
。
<configuration>
<system.web>
<webServices>
<soapExtensionTypes> <add type="Logger.LoggerExtension,logger" priority="1" group="0" /> </soapExtensionTypes>
</webServices>
</system.web>
</configuration>
下列程式碼範例為 SOAP 擴充功能,記錄傳入傳出 XML Web Service 或 XML Web Service 用戶端的 SOAP 訊息。如果下列 SOAP 擴充功能的安裝是要配合 XML Web Service 執行,那麼 ASP.NET 使用者帳戶必須具有寫入記錄檔之目錄的寫入使用權限。
Imports System
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.IO
' Define a SOAP Extension that traces the SOAP request and SOAP response
' for the XML Web service method the SOAP extension is applied to.
Public Class TraceExtension
Inherits SoapExtension
Private oldStream As Stream
Private newStream As Stream
Private m_filename As String
' Save the Stream representing the SOAP request or SOAP response into
' a local memory buffer.
Public Overrides Function ChainStream(stream As Stream) As Stream
oldStream = stream
newStream = New MemoryStream()
Return newStream
End Function
' When the SOAP extension is accessed for the first time, the XML Web
' service method it is applied to is accessed to store the file
' name passed in, using the corresponding SoapExtensionAttribute.
Public Overloads Overrides Function GetInitializer(methodInfo As _
LogicalMethodInfo, _
attribute As SoapExtensionAttribute) As Object
Return CType(attribute, TraceExtensionAttribute).Filename
End Function
' The SOAP extension was configured to run using a configuration file
' instead of an attribute applied to a specific XML Web service
' method. Return a file name based on the class implementing the Web
' Service's type.
Public Overloads Overrides Function GetInitializer(WebServiceType As _
Type) As Object
' Return a file name to log the trace information to, based on the
' type.
Return "C:\" + WebServiceType.FullName + ".log"
End Function
' Receive the file name stored by GetInitializer and store it in a
' member variable for this specific instance.
Public Overrides Sub Initialize(initializer As Object)
m_filename= CStr(initializer)
End Sub
' If the SoapMessageStage is such that the SoapRequest or SoapResponse
' is still in the SOAP format to be sent or received over the network,
' save it out to file.
Public Overrides Sub ProcessMessage(message As SoapMessage)
Select Case message.Stage
Case SoapMessageStage.BeforeSerialize
Case SoapMessageStage.AfterSerialize
WriteOutput(message)
Case SoapMessageStage.BeforeDeserialize
WriteInput(message)
Case SoapMessageStage.AfterDeserialize
Case Else
Throw New Exception("invalid stage")
End Select
End Sub
' Write the SOAP message out to a file.
Public Sub WriteOutput(message As SoapMessage)
newStream.Position = 0
Dim fs As New FileStream(m_filename, FileMode.Append, _
FileAccess.Write)
Dim w As New StreamWriter(fs)
w.WriteLine("-----Response at " + DateTime.Now.ToString())
w.Flush()
Copy(newStream, fs)
w.Close()
newStream.Position = 0
Copy(newStream, oldStream)
End Sub
' Write the SOAP message out to a file.
Public Sub WriteInput(message As SoapMessage)
Copy(oldStream, newStream)
Dim fs As New FileStream(m_filename, FileMode.Append, _
FileAccess.Write)
Dim w As New StreamWriter(fs)
w.WriteLine("----- Request at " + DateTime.Now.ToString())
w.Flush()
newStream.Position = 0
Copy(newStream, fs)
w.Close()
newStream.Position = 0
End Sub
Sub Copy(fromStream As Stream, toStream As Stream)
Dim reader As New StreamReader(fromStream)
Dim writer As New StreamWriter(toStream)
writer.WriteLine(reader.ReadToEnd())
writer.Flush()
End Sub
End Class
' Create a SoapExtensionAttribute for our SOAP Extension that can be
' applied to an XML Web service method.
<AttributeUsage(AttributeTargets.Method)> _
Public Class TraceExtensionAttribute
Inherits SoapExtensionAttribute
Private m_filename As String = "c:\log.txt"
Private m_priority As Integer
Public Overrides ReadOnly Property ExtensionType() As Type
Get
Return GetType(TraceExtension)
End Get
End Property
Public Overrides Property Priority() As Integer
Get
Return m_priority
End Get
Set
m_priority = value
End Set
End Property
Public Property Filename() As String
Get
Return m_filename
End Get
Set
m_filename= value
End Set
End Property
End Class
[C#]
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.IO;
using System.Net;
// Define a SOAP Extension that traces the SOAP request and SOAP
// response for the XML Web service method the SOAP extension is
// applied to.
public class TraceExtension : SoapExtension
{
Stream oldStream;
Stream newStream;
string filename;
// Save the Stream representing the SOAP request or SOAP response into
// a local memory buffer.
public override Stream ChainStream( Stream stream ){
oldStream = stream;
newStream = new MemoryStream();
return newStream;
}
// When the SOAP extension is accessed for the first time, the XML Web
// service method it is applied to is accessed to store the file
// name passed in, using the corresponding SoapExtensionAttribute.
public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{
return ((TraceExtensionAttribute) attribute).Filename;
}
// The SOAP extension was configured to run using a configuration file
// instead of an attribute applied to a specific XML Web service
// method.
public override object GetInitializer(Type WebServiceType)
{
// Return a file name to log the trace information to, based on the
// type.
return "C:\\" + WebServiceType.FullName + ".log";
}
// Receive the file name stored by GetInitializer and store it in a
// member variable for this specific instance.
public override void Initialize(object initializer)
{
filename = (string) initializer;
}
// If the SoapMessageStage is such that the SoapRequest or
// SoapResponse is still in the SOAP format to be sent or received,
// save it out to a file.
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage) {
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
WriteOutput(message);
break;
case SoapMessageStage.BeforeDeserialize:
WriteInput(message);
break;
case SoapMessageStage.AfterDeserialize:
break;
default:
throw new Exception("invalid stage");
}
}
public void WriteOutput(SoapMessage message){
newStream.Position = 0;
FileStream fs = new FileStream(filename, FileMode.Append,
FileAccess.Write);
StreamWriter w = new StreamWriter(fs);
string soapString = (message is SoapServerMessage) ? "SoapResponse" : "SoapRequest";
w.WriteLine("-----" + soapString + " at " + DateTime.Now);
w.Flush();
Copy(newStream, fs);
w.Close();
newStream.Position = 0;
Copy(newStream, oldStream);
}
public void WriteInput(SoapMessage message){
Copy(oldStream, newStream);
FileStream fs = new FileStream(filename, FileMode.Append,
FileAccess.Write);
StreamWriter w = new StreamWriter(fs);
string soapString = (message is SoapServerMessage) ?
"SoapRequest" : "SoapResponse";
w.WriteLine("-----" + soapString +
" at " + DateTime.Now);
w.Flush();
newStream.Position = 0;
Copy(newStream, fs);
w.Close();
newStream.Position = 0;
}
void Copy(Stream from, Stream to)
{
TextReader reader = new StreamReader(from);
TextWriter writer = new StreamWriter(to);
writer.WriteLine(reader.ReadToEnd());
writer.Flush();
}
}
// Create a SoapExtensionAttribute for the SOAP Extension that can be
// applied to an XML Web service method.
[AttributeUsage(AttributeTargets.Method)]
public class TraceExtensionAttribute : SoapExtensionAttribute {
private string filename = "c:\\log.txt";
private int priority;
public override Type ExtensionType {
get { return typeof(TraceExtension); }
}
public override int Priority {
get { return priority; }
set { priority = value; }
}
public string Filename {
get {
return filename;
}
set {
filename = value;
}
}
}
請參閱
SoapExtension | SoapExtensionAttribute | SoapMessageStage | LogicalMethodInfo | XML Web Service 存留期深入探索 | 設定應用程式 | 使用 ASP.NET 建置 XML Web Service | 建置 XML Web Service 用戶端