InstanceContextSharing
這個範例示範如何使用 IInstanceContextProvider 介面在多個呼叫和用戶端之間共用 InstanceContext 物件。這個範例示範用戶端應用程式如何建立唯一識別項,再將它傳送至服務。服務接著將該識別項與特定 InstanceContext 物件產生關聯。用戶端因此可以將識別項傳遞給另一個用戶端。然後,這個用戶端就可以在傳送至相同服務的標頭中放置內容識別項。而該服務便會使用內容識別項,將第二個呼叫與第一個執行個體內容物件 (因而也同時與服務物件) 產生關聯。
注意: |
---|
此範例的安裝程序與建置指示位於本主題的結尾。 |
實作 IInstanceContextProvider 介面,即可為系統提供適當的 InstanceContext 物件。一般而言,實作這個介面是為了支援共用工作階段、啟用服務執行個體集區、控制服務執行個體的存留時間,或是群組用戶端之間的內容。
若要插入自訂 IInstanceContextProvider,請建立行為 (例如,IEndpointBehavior 或 IServiceBehavior),並使用該行為將 IInstanceContextProvider 物件指派給 System.ServiceModel.Dispatcher.DispatchRuntime.InstanceContextProvider 屬性。
這個範例會在自訂 ShareableAttribute
屬性上使用 IServiceBehavior 來插入 IInstanceContextProvider。
ShareableAttribute
會產生實作 IInstanceContextProvider 的 CalculatorExtension
物件實體,然後逐一查看每個 EndpointDispatcher,再將每個 InstanceContextProvider 屬性設定為剛才建立的 CalculatorExtension
物件。這在下列範例程式碼中顯示。
//Apply the custom IInstanceContextProvider to the EndpointDispatcher.DispatchRuntime
public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
CalculatorExtension extension = new CalculatorExtension();
foreach (ChannelDispatcherBase dispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher dispatcher = dispatcherBase as ChannelDispatcher;
foreach (EndpointDispatcher endpointDispatcher in dispatcher.Endpoints)
{
endpointDispatcher.DispatchRuntime.InstanceContextProvider = extension;
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(extension);
}
}
}
用戶端的每個呼叫都會經過 CalculatorExtension
物件,以便判斷是使用了哪個 InstanceContext 來服務該特定訊息。
這個範例使用兩個用戶端 Client1
和 Client2
來示範共用。兩個用戶端互動所依照的順序,如下列程式碼所示。請注意,用戶端和伺服器都使用 CustomHeader
來傳達 InstanceContext 的唯一識別項,而且使用亂數產生器公用程式來產生唯一的 32 位元組識別項。
public static class CustomHeader
{
public static readonly String HeaderName = "InstanceId";
public static readonly String HeaderNamespace = "http://Microsoft.ServiceModel.Samples/Sharing";
}
static string NewInstanceId()
{
byte[] random = new byte[256 / 8];
randomNumberGenerator.GetBytes(random);
return Convert.ToBase64String(random);
}
Client1
會建立 OperationContextScope,因此可以新增標頭。它接著產生唯一 ID,再將這個 ID 新增至傳出標頭的清單,做為其中的值。//Create a new 1028 bit strong InstanceContextId that we want the server to associate //the InstanceContext that processes all messages from this client. String uniqueId = NewInstanceId(); MessageHeader Client1InstanceContextHeader = MessageHeader.CreateHeader( CustomHeader.HeaderName, CustomHeader.HeaderNamespace, uniqueId); try { using (new OperationContextScope(client1.InnerChannel)) { //Add the header as a header to the scope so it gets sent for each message. OperationContext.Current.OutgoingMessageHeaders.Add(Client1InstanceContextHeader); ... } }
然後,會叫用
DoCalculations
,以呼叫遠端伺服器上的數個作業。它對所叫用的每個作業,傳送了自訂標頭和產生的 ID。Client1
會叫用Add
作業,而這是此通道上的第一個呼叫。伺服器接收訊息,並使用通道和訊息來叫用
CalculatorExtension.GetExistingInstanceContext
。延伸會檢查通道是否已附加至 InstanceContext。如果沒有,延伸會嘗試查詢自訂標頭,並檢查快取中是否有這個識別碼的 InstanceContext。在本例中,快取是空的,所以會傳回 null。請注意,延伸實際上會儲存AddressableInstanceContextInfo
(稍後在來源中定義),以便保存InstanceContext
的額外資訊,並在建立InstanceContext
之前協調多個執行緒。// If the channel has a session, we bind the session to a particular InstanceContext // based on the first message, and then route all subsequent messages on that session to // the same InstanceContext. bool hasSession = (channel.SessionId != null); if (hasSession) { info = channel.Extensions.Find<AddressableInstanceContextInfo>(); if (info != null) { ... } } // If this is the first message of a session, or is using a datagram channel, look in // the message headers to see if there is a header with an instance id. int headerIndex = message.Headers.FindHeader(CustomHeader.HeaderName, CustomHeader.HeaderNamespace); // If there was a header, extract the instanceId. string instanceId = null; if (headerIndex != -1) { instanceId = message.Headers.GetHeader<string>(headerIndex); } ... // Check our table to see if we recognize the instance id. lock (this.ThisLock) { if ((instanceId == null) || !this.contextMap.TryGetValue(instanceId, out info)) { isNew = true; ... } ... } ... if (isNew) { // This tells WCF to create a new InstanceContext and call InitializeInstanceContext. return null; }
伺服器接著建立新的 InstanceContext,再呼叫
CalculatorExtension.InitializeInstanceContext
。InitializeInstanceContext 方法會通知延伸有關新建立的 InstanceContext。這會從通道 (如果是工作階段通道) 或快取 (如果是資料包通道) 擷取AddressableInstanceContextInfo
,並將它新增至 InstanceContext 的延伸集合。這麼做是為了讓伺服器可以快速擷取任何 InstanceContext 的 ID。如果通道有工作階段,延伸就會將通道新增至 InstanceContext 的 IncomingChannels 集合,使得 Windows Communication Foundation (WCF) 不會在通道關閉之前關閉 InstanceContext。這個範例也會攔截 InstanceContext 的 Closed 事件,以便在將它明確關閉時,知道要從快取中加以移除。現在,這個 InstanceContext 是用來處理和回覆訊息。if (hasSession) { // Since this is a new InstanceContext, we could not add the channel in // GetExistingInstanceContext, so add it here. instanceContext.IncomingChannels.Add(channel); // If we have a session, we stored the info in the channel, so just look it up // there. info = channel.Extensions.Find<AddressableInstanceContextInfo>(); } else { // Otherwise, if we don't have a session, look the info up again in the table. ... } // Now that we have the InstanceContext, we can link it to the // AddressableInstanceContextInfo and vice versa. if (info != null) { instanceContext.Extensions.Add(info); info.SetInstanceContext(instanceContext); } // When the InstanceContext starts closing, remove it from the table. // // Generally we will already have the lock because Close will happen inside // CallIdleCallback. However, if someone just closes the InstanceContext explicitly // before it goes idle, we will not have the lock. Since modifying Dictionary is not // thread-safe, we lock here. instanceContext.Closing += delegate(object sender, EventArgs e) { lock (this.ThisLock) { this.contextMap.Remove(info.InstanceId); } };
Client1
接著呼叫其第二項作業Subtract
。再次使用新的訊息呼叫CalculatorExtension.GetExistingInstanceContext
。它擷取了標頭,而這次的查閱作業會成功,並從快取傳回 InstanceContext。WaitForInstance
確認第一個呼叫已完成其InitializeInstanceContext
呼叫。WCF 將使用這個 InstanceContext 來處理其餘的訊息。if (hasSession) { info = channel.Extensions.Find<AddressableInstanceContextInfo>(); if (info != null) { // We may be processing a second message before the first message has finished // initializing the InstanceContext. Wait here until the first message is // done. If the first message has already finished initializing, this returns // immediately. info.IncrementBusyCount(); return info.WaitForInstanceContext(); } }
Client1
會呼叫Subtract
和Delete
作業,並且重複步驟 5 以使用同一個 InstanceContext。用戶端接著建立另一個通道 (
Client2
),然後再次建立 OperationContextScope,並將相同的識別碼新增至其 OutgoingMessageHeaders 集合。因此,Client1
使用的相同識別碼現在會隨著在Client2
上進行的所有呼叫一起傳送。Client2
會呼叫Add()
,Subtract()
、Multiply()
和Divide()
作業,並使用步驟 7 中的相同邏輯,而這些作業全都是由第一個用戶端所建立的 InstanceContext 來服務。伺服器會呼叫CalculatorExtension.GetExistingInstanceContext
。延伸會尋找標頭並查詢與此標頭有關聯的 InstanceContext。這個 InstanceContext 會用來分派訊息。// If this is the first message of a session, or is using a datagram channel, look in // the message headers to see if there is a header with an instance id. int headerIndex = message.Headers.FindHeader(CustomHeader.HeaderName, CustomHeader.HeaderNamespace); // If there was a header, extract the instanceId. string instanceId = null; if (headerIndex != -1) { instanceId = message.Headers.GetHeader<string>(headerIndex); } // Remember if we created a new AddressableInstanceContextInfo. bool isNew = false; // Check our table to see if we recognize the instance id. lock (this.ThisLock) { if ((instanceId == null) || !this.contextMap.TryGetValue(instanceId, out info)) { ... } ... } ... if (isNew) { // ... } else { InstanceContext instanceContext = info.WaitForInstanceContext(); ... return instanceContext; }
若要設定、建置及執行範例
若要建置方案,請遵循建置 Windows Communication Foundation 範例中的指示。
若要在單一或跨電腦的組態中執行本範例,請遵循執行 Windows Communication Foundation 範例中的指示。
Send comments about this topic to Microsoft.
© 2007 Microsoft Corporation. All rights reserved.