MessageInspectors 範例示範如何實作及設定客戶端和服務訊息偵測器。
訊息偵測器是擴充性物件,可用於服務模型的用戶端運行時間,並以程式設計方式或透過組態分派運行時間,而且可以在收到訊息或傳送訊息之前檢查和改變訊息。
此範例會實作基本客戶端和服務訊息驗證機制,以針對一組可設定的 XML 架構檔驗證傳入訊息。 請注意,此範例不會驗證每個作業的訊息。 這是刻意簡化。
訊息偵測器
用戶端訊息偵測器會實作 IClientMessageInspector 介面,而服務訊息偵測器會實作 IDispatchMessageInspector 介面。 實作可以合併成單一類別,以形成適用於雙方的訊息偵測器。 此範例會實作這類合併的訊息偵測器。 偵測器會建構傳入和傳出訊息的一組架構,讓開發人員指定傳入或傳出訊息是否經過驗證,以及檢查程式是否處於分派或用戶端模式,這會影響本主題稍後討論的錯誤處理。
public class SchemaValidationMessageInspector : IClientMessageInspector, IDispatchMessageInspector
{
XmlSchemaSet schemaSet;
bool validateRequest;
bool validateReply;
bool isClientSide;
[ThreadStatic]
bool isRequest;
public SchemaValidationMessageInspector(XmlSchemaSet schemaSet,
bool validateRequest, bool validateReply, bool isClientSide)
{
this.schemaSet = schemaSet;
this.validateReply = validateReply;
this.validateRequest = validateRequest;
this.isClientSide = isClientSide;
}
任何服務(發送器)訊息偵測器都必須實作這兩 IDispatchMessageInspector 種方法 AfterReceiveRequest 和 BeforeSendReply(Message, Object)。
當收到訊息並由通道堆疊處理及指派給服務時,AfterReceiveRequest 會由調度器叫用,但在資料序列化解除並分派至作業之前。 如果傳入訊息已加密,當訊息到達訊息偵測器時,訊息已經解密。 方法會取得 request
作為參考參數傳遞的訊息,這樣可以視需要來檢查、修改或取代訊息。 傳回值可以是任何對象,當服務傳回目前訊息的回復時,會作為傳遞 BeforeSendReply 至的相互關聯狀態物件。 在此範例中, AfterReceiveRequest 會將訊息的檢查(驗證)委派給私用、本機方法 ValidateMessageBody
,而且不會傳回相互關聯狀態物件。 此方法可確保沒有任何無效的訊息傳入服務。
object IDispatchMessageInspector.AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
if (validateRequest)
{
// inspect the message. If a validation error occurs,
// the thrown fault exception bubbles up.
ValidateMessageBody(ref request, true);
}
return null;
}
BeforeSendReply(Message, Object) 將在準備回復傳送給用戶端時,或在處理單向訊息中收到的訊息時被呼叫。 這可讓擴充套件在不論 MEP 為何的情況下,確保被以對稱方式呼叫。 如同 AfterReceiveRequest,訊息會以參考參數的形式傳遞,並可檢查、修改或取代。 在此範例中執行的訊息驗證會再次委派給 ValidMessageBody
方法,但在此情況下,驗證錯誤的處理方式稍有不同。
如果服務發生驗證錯誤,則 ValidateMessageBody
方法會擲回從 FaultException 衍生的例外狀況。 在 中 AfterReceiveRequest,這些例外狀況可以放入服務模型基礎結構中,這些例外狀況會自動轉換成SOAP錯誤,並轉送至用戶端。 在 BeforeSendReply 中,FaultException 例外狀況不得放入基礎結構中,因為服務擲回的錯誤例外狀況的轉換會在呼叫消息檢查器之前發生。 因此,下列實作會攔截已知的 ReplyValidationFault
例外狀況,並以明確的錯誤訊息取代回復訊息。 此方法可確保服務實作不會傳回無效的訊息。
void IDispatchMessageInspector.BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
if (validateReply)
{
// Inspect the reply, catch a possible validation error
try
{
ValidateMessageBody(ref reply, false);
}
catch (ReplyValidationFault fault)
{
// if a validation error occurred, the message is replaced
// with the validation fault.
reply = Message.CreateMessage(reply.Version,
fault.CreateMessageFault(), reply.Headers.Action);
}
}
用戶端訊息偵測器非常類似。 必須從 IClientMessageInspector 實作的兩種方法是 AfterReceiveReply 和 BeforeSendRequest。
BeforeSendRequest 當訊息是由用戶端應用程式或作業格式器所撰寫時叫用。 如同發送器訊息偵測器,訊息可以只被檢查,也可以被完全取代。 在此範例中,偵測器會委派至同樣用於分派訊息偵測器的相同本機ValidateMessageBody
輔助方法。
用戶端驗證與服務驗證之間的行為差異(如建構函式中所指定),在於用戶端驗證會拋出本機例外狀況,這些例外狀況會放入使用者程式碼中,因為它們是本地發生的,而不是因為服務故障。 一般而言,規則是服務發送器偵測器會擲回錯誤,且用戶端偵測器會擲回例外狀況。
此 BeforeSendRequest 實作可確保不會將無效的訊息傳送至服務。
object IClientMessageInspector.BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
if (validateRequest)
{
ValidateMessageBody(ref request, true);
}
return null;
}
實作 AfterReceiveReply
可確保不會將從服務接收的無效訊息轉寄至用戶端用戶程序代碼。
void IClientMessageInspector.AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
if (validateReply)
{
ValidateMessageBody(ref reply, false);
}
}
這個特定訊息偵測器的核心是 ValidateMessageBody
方法。 若要執行其工作,它會在傳遞訊息的本文內容子樹狀結構周圍包裝驗證 XmlReader 。 讀取器會填入訊息偵測器所保存的架構集合,而驗證回呼會設定為參考這個 InspectionValidationHandler
方法所定義 之的委派。 若要執行驗證,訊息接著會被讀取並緩衝處理到記憶體流支援的 XmlDictionaryWriter。 如果程式中發生驗證錯誤或警告,則會叫用回呼方法。
如果沒有發生錯誤,則會建構新的訊息,以從原始訊息複製屬性和標頭,並使用記憶體數據流中現在驗證的資訊集,該數據流會由 XmlDictionaryReader 包裝並新增至取代訊息。
void ValidateMessageBody(ref System.ServiceModel.Channels.Message message, bool isRequest)
{
if (!message.IsFault)
{
XmlDictionaryReaderQuotas quotas =
new XmlDictionaryReaderQuotas();
XmlReader bodyReader =
message.GetReaderAtBodyContents().ReadSubtree();
XmlReaderSettings wrapperSettings =
new XmlReaderSettings();
wrapperSettings.CloseInput = true;
wrapperSettings.Schemas = schemaSet;
wrapperSettings.ValidationFlags =
XmlSchemaValidationFlags.None;
wrapperSettings.ValidationType = ValidationType.Schema;
wrapperSettings.ValidationEventHandler += new
ValidationEventHandler(InspectionValidationHandler);
XmlReader wrappedReader = XmlReader.Create(bodyReader,
wrapperSettings);
// pull body into a memory backed writer to validate
this.isRequest = isRequest;
MemoryStream memStream = new MemoryStream();
XmlDictionaryWriter xdw =
XmlDictionaryWriter.CreateBinaryWriter(memStream);
xdw.WriteNode(wrappedReader, false);
xdw.Flush(); memStream.Position = 0;
XmlDictionaryReader xdr =
XmlDictionaryReader.CreateBinaryReader(memStream, quotas);
// reconstruct the message with the validated body
Message replacedMessage =
Message.CreateMessage(message.Version, null, xdr);
replacedMessage.Headers.CopyHeadersFrom(message.Headers);
replacedMessage.Properties.CopyProperties(message.Properties);
message = replacedMessage;
}
}
InspectionValidationHandler
方法在發生架構驗證錯誤或警告時會由驗證XmlReader呼叫。 下列實作僅適用於錯誤,並忽略所有警告。
初步考慮時,看似可能會使用訊息檢查器將驗證 XmlReader 插入訊息中,並讓驗證在處理訊息時發生,而不需要緩衝處理訊息。 不過,這表示這個回呼會在服務模型基礎結構或使用者程式代碼中擲回驗證例外狀況,因為偵測到無效的 XML 節點,而導致無法預期的行為。 緩衝處理方法完全保護使用者程式代碼不受無效訊息的防護。
如先前所述,處理程序擲回的例外狀況在客戶端和服務之間有所不同。 在服務上,例外狀況衍生自 FaultException,用戶端上的例外狀況是一般自定義例外狀況。
void InspectionValidationHandler(object sender, ValidationEventArgs e)
{
if (e.Severity == XmlSeverityType.Error)
{
// We are treating client and service side validation errors
// differently here. Client side errors cause exceptions
// and are thrown straight up to the user code. Service side
// validations cause faults.
if (isClientSide)
{
if (isRequest)
{
throw new RequestClientValidationException(e.Message);
}
else
{
throw new ReplyClientValidationException(e.Message);
}
}
else
{
if (isRequest)
{
// this fault is caught by the ServiceModel
// infrastructure and turned into a fault reply.
throw new RequestValidationFault(e.Message);
}
else
{
// this fault is caught and turned into a fault message
// in BeforeSendReply in this class
throw new ReplyValidationFault(e.Message);
}
}
}
}
行為
訊息檢測器是客戶端執行階段或分派執行階段的擴充元件。 使用行為來設定這類擴充功能。 行為是一種類別,可藉由變更預設組態或將擴充功能(例如訊息偵測器)新增至它,來變更服務模型運行時間的行為。
下列 SchemaValidationBehavior
類別是用來將此範例的訊息檢查器新增至客戶端或調度運行環境的行為。 在這兩種情況下,執行方式相當簡單。 在 ApplyClientBehavior 和 ApplyDispatchBehavior中,訊息偵測器會建立並新增至 MessageInspectors 個別運行時間的集合。
public class SchemaValidationBehavior : IEndpointBehavior
{
XmlSchemaSet schemaSet;
bool validateRequest;
bool validateReply;
public SchemaValidationBehavior(XmlSchemaSet schemaSet, bool
inspectRequest, bool inspectReply)
{
this.schemaSet = schemaSet;
this.validateReply = inspectReply;
this.validateRequest = inspectRequest;
}
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint,
System.ServiceModel.Channels.BindingParameterCollection
bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
SchemaValidationMessageInspector inspector =
new SchemaValidationMessageInspector(schemaSet,
validateRequest, validateReply, true);
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.EndpointDispatcher
endpointDispatcher)
{
SchemaValidationMessageInspector inspector =
new SchemaValidationMessageInspector(schemaSet,
validateRequest, validateReply, false);
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
}
public void Validate(ServiceEndpoint endpoint)
{
}
#endregion
}
備註
此特定行為不會加倍做為屬性,因此無法以宣告方式新增至服務類型的合約類型。 這是一個依設計而定的決策,因為架構集合無法在屬性宣告中載入,而在此屬性中參考額外的組態位置,譬如應用程式設定,意味著建立與服務模型組態其他部分不一致的組態專案。 因此,此行為只能透過程式碼和服務模型組態延伸模組,以命令方式新增。
透過設定新增訊息偵測器
若要在應用程式組態檔中的端點上設定自定義行為,服務模型需要實作者建立衍生自 之類別所表示的組態BehaviorExtensionElement。 然後,必須將此延伸模組新增至服務模型的延伸模組組態區段,如本節中所討論的下列延伸模組所示。
<system.serviceModel>
…
<extensions>
<behaviorExtensions>
<add name="schemaValidator" type="Microsoft.ServiceModel.Samples.SchemaValidationBehaviorExtensionElement, MessageInspectors, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
…
</system.serviceModel>
擴充功能可以在應用程式或 ASP.NET 組態檔中新增,這是最常見的選擇,或在計算機組態檔中。
將擴充功能新增至組態範圍時,行為可以新增至行為組態,如下列程式代碼所示。 行為組態是可重複使用的專案,可視需要套用至多個端點。 由於此處要設定的特定行為會實作 IEndpointBehavior,所以只有在組態檔的個別組態區段中才有效。
<system.serviceModel>
<behaviors>
…
<endpointBehaviors>
<behavior name="HelloServiceEndpointBehavior">
<schemaValidator validateRequest="True" validateReply="True">
<schemas>
<add location="messages.xsd" />
</schemas>
</schemaValidator>
</behavior>
</endpointBehaviors>
…
</behaviors>
</system.serviceModel>
設定 <schemaValidator>
訊息偵測器的 元素是由 SchemaValidationBehaviorExtensionElement
類別所支援。 類別會公開兩個名為 ValidateRequest
和 ValidateReply
的布爾公用屬性。 這兩者都會標示為 ConfigurationPropertyAttribute。 這個屬性會構成程式代碼屬性與 XML 屬性之間的連結,這些屬性可以在上述 XML 組態專案上看到。 類別還有一個屬性 Schemas
,除了標記有 ConfigurationCollectionAttribute,其類型是 SchemaCollection
。這個屬性也是此示例的一部分,但為了簡潔起見,省略在本文中。 這個屬性,以及集合和集合項目類別 SchemaConfigElement
,作為上述配置代碼段中 <schemas>
元素的基礎,並允許將一組架構新增至驗證集。
當執行期間在評估設定資料以建置用戶端或端點時,覆寫的方法 CreateBehavior
會將設定資料轉換成行為物件。
public class SchemaValidationBehaviorExtensionElement : BehaviorExtensionElement
{
public SchemaValidationBehaviorExtensionElement()
{
}
public override Type BehaviorType
{
get
{
return typeof(SchemaValidationBehavior);
}
}
protected override object CreateBehavior()
{
XmlSchemaSet schemaSet = new XmlSchemaSet();
foreach (SchemaConfigElement schemaCfg in this.Schemas)
{
Uri baseSchema = new
Uri(AppDomain.CurrentDomain.BaseDirectory);
string location = new
Uri(baseSchema,schemaCfg.Location).ToString();
XmlSchema schema =
XmlSchema.Read(new XmlTextReader(location), null);
schemaSet.Add(schema);
}
return new
SchemaValidationBehavior(schemaSet,ValidateRequest,ValidateReply);
}
[ConfigurationProperty("validateRequest",DefaultValue=false,IsRequired=false)]
public bool ValidateRequest
{
get { return (bool)base["validateRequest"]; }
set { base["validateRequest"] = value; }
}
[ConfigurationProperty("validateReply", DefaultValue = false, IsRequired = false)]
public bool ValidateReply
{
get { return (bool)base["validateReply"]; }
set { base["validateReply"] = value; }
}
//Declare the Schema collection property.
//Note: the "IsDefaultCollection = false" instructs
//.NET Framework to build a nested section of
//the kind <Schema> ...</Schema>.
[ConfigurationProperty("schemas", IsDefaultCollection = true)]
[ConfigurationCollection(typeof(SchemasCollection),
AddItemName = "add",
ClearItemsName = "clear",
RemoveItemName = "remove")]
public SchemasCollection Schemas
{
get
{
SchemasCollection SchemasCollection =
(SchemasCollection)base["schemas"];
return SchemasCollection;
}
}
}
使用命令式方式新增訊息偵測器
除了透過屬性(基於先前引用的原因,此範例中不支援此屬性)和組態之外,使用命令式程式代碼可以輕鬆地將行為新增至用戶端和服務運行時間。 在此範例中,這會在用戶端應用程式中完成,以測試用戶端訊息偵測器。 類別 GenericClient
衍生自 ClientBase<TChannel>,它會將端點組態公開給使用者程序代碼。 在隱含開啟用戶端之前,可以變更端點組態,例如新增行為,如下列程式代碼所示。 在服務上新增行為基本上相當於此處顯示的用戶端技術,而且必須在開啟服務主機之前執行。
try
{
Console.WriteLine("*** Call 'Hello' with generic client, with client behavior");
GenericClient client = new GenericClient();
// Configure client programmatically, adding behavior
XmlSchema schema = XmlSchema.Read(new StreamReader("messages.xsd"),
null);
XmlSchemaSet schemaSet = new XmlSchemaSet();
schemaSet.Add(schema);
client.Endpoint.Behaviors.Add(new
SchemaValidationBehavior(schemaSet, true, true));
Console.WriteLine("--- Sending valid client request:");
GenericCallValid(client, helloAction);
Console.WriteLine("--- Sending invalid client request:");
GenericCallInvalid(client, helloAction);
client.Close();
}
catch (Exception e)
{
DumpException(e);
}
要設定、建置和執行範例,請執行以下步驟:
請確定您已針對 Windows Communication Foundation 範例 執行One-Time 安裝程式。
若要建置解決方案,請遵循 建置 Windows Communication Foundation 範例中的指示。
若要在單一或跨計算機組態中執行範例,請遵循執行 Windows Communication Foundation 範例 中的指示。