InstanceContextSharing

這個範例示範如何使用 IInstanceContextProvider 介面在多個呼叫和用戶端之間共用 InstanceContext 物件。這個範例示範用戶端應用程式如何建立唯一識別項,再將它傳送至服務。服務接著將該識別項與特定 InstanceContext 物件產生關聯。用戶端因此可以將識別項傳遞給另一個用戶端。然後,這個用戶端就可以在傳送至相同服務的標頭中放置內容識別項。而該服務便會使用內容識別項,將第二個呼叫與第一個執行個體內容物件 (因而也同時與服務物件) 產生關聯。

Aa354514.note(zh-tw,VS.90).gif注意:
此範例的安裝程序與建置指示位於本主題的結尾。

實作 IInstanceContextProvider 介面,即可為系統提供適當的 InstanceContext 物件。一般而言,實作這個介面是為了支援共用工作階段、啟用服務執行個體集區、控制服務執行個體的存留時間,或是群組用戶端之間的內容。

若要插入自訂 IInstanceContextProvider,請建立行為 (例如,IEndpointBehaviorIServiceBehavior),並使用該行為將 IInstanceContextProvider 物件指派給 System.ServiceModel.Dispatcher.DispatchRuntime.InstanceContextProvider 屬性。

這個範例會在自訂 ShareableAttribute 屬性上使用 IServiceBehavior 來插入 IInstanceContextProvider

ShareableAttribute 會產生實作 IInstanceContextProviderCalculatorExtension 物件實體,然後逐一查看每個 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 來服務該特定訊息。

這個範例使用兩個用戶端 Client1Client2 來示範共用。兩個用戶端互動所依照的順序,如下列程式碼所示。請注意,用戶端和伺服器都使用 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);
}
  1. 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。

  2. Client1 會叫用 Add 作業,而這是此通道上的第一個呼叫。

  3. 伺服器接收訊息,並使用通道和訊息來叫用 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;
    }
    
  4. 伺服器接著建立新的 InstanceContext,再呼叫 CalculatorExtension.InitializeInstanceContextInitializeInstanceContext 方法會通知延伸有關新建立的 InstanceContext。這會從通道 (如果是工作階段通道) 或快取 (如果是資料包通道) 擷取 AddressableInstanceContextInfo,並將它新增至 InstanceContext 的延伸集合。這麼做是為了讓伺服器可以快速擷取任何 InstanceContext 的 ID。如果通道有工作階段,延伸就會將通道新增至 InstanceContextIncomingChannels 集合,使得 Windows Communication Foundation (WCF) 不會在通道關閉之前關閉 InstanceContext。這個範例也會攔截 InstanceContextClosed 事件,以便在將它明確關閉時,知道要從快取中加以移除。現在,這個 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);
        }
    };
    
  5. Client1 接著呼叫其第二項作業 Subtract。再次使用新的訊息呼叫 CalculatorExtension.GetExistingInstanceContext。它擷取了標頭,而這次的查閱作業會成功,並從快取傳回 InstanceContextWaitForInstance 確認第一個呼叫已完成其 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();
        }
    } 
    
  6. Client1 會呼叫 SubtractDelete 作業,並且重複步驟 5 以使用同一個 InstanceContext

  7. 用戶端接著建立另一個通道 (Client2),然後再次建立 OperationContextScope,並將相同的識別碼新增至其 OutgoingMessageHeaders 集合。因此,Client1 使用的相同識別碼現在會隨著在 Client2 上進行的所有呼叫一起傳送。

  8. 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;
    }
    

若要設定、建置及執行範例

  1. 請確定您已執行 Windows Communication Foundation 範例的單次安裝程序

  2. 若要建置方案,請遵循建置 Windows Communication Foundation 範例中的指示。

  3. 若要在單一或跨電腦的組態中執行本範例,請遵循執行 Windows Communication Foundation 範例中的指示。

Send comments about this topic to Microsoft.
© 2007 Microsoft Corporation. All rights reserved.