Share via


InstanceContextSharing

Dieses Beispiel zeigt, wie die IInstanceContextProvider-Schnittstelle verwendet wird, um InstanceContext-Objekte für mehrere Aufrufe und Clients freizugeben. Das Beispiel demonstriert, wie eine Clientanwendung einen eindeutigen Bezeichner erstellen und an den Dienst senden kann. Der Dienst ordnet diesen Bezeichner dann einem bestimmten InstanceContext-Objekt zu. Anschließend kann der Client den Bezeichner an einen anderen Client übergeben. Dieser Client kann den Kontextbezeichner dann in einem Header platzieren, der an denselben Dienst gesendet wird. Dieser Dienst verwendet den Kontextbezeichner, um den zweiten Aufruf dem ersten Instanzkontextobjekt und damit dem Dienstobjekt zuzuordnen.

Tipp

Die Setupprozedur und Buildanweisungen für dieses Beispiel befinden sich am Ende dieses Themas.

Implementieren Sie die IInstanceContextProvider-Schnittstelle, um dem System das entsprechende InstanceContext-Objekt bereitzustellen. In der Regel wird diese Schnittstelle implementiert, um freigegebene Sitzungen zu unterstützen, Dienstinstanzpooling zu aktivieren, die Lebensdauer von Dienstinstanzen zu steuern oder Clients Kontexte in Gruppen zuzuordnen.

Erstellen Sie zum Einfügen des benutzerdefinierten IInstanceContextProvider ein Verhalten (z. B. ein IEndpointBehavior oder IServiceBehavior), und ordnen Sie mithilfe dieses Verhaltens das IInstanceContextProvider-Objekt der System.ServiceModel.Dispatcher.DispatchRuntime.InstanceContextProvider-Eigenschaft zu.

In diesem Beispiel wird ein IServiceBehavior in einem benutzerdefinierten ShareableAttribute-Attribut verwendet, um den IInstanceContextProvider einzufügen.

Das ShareableAttribute instanziiert ein CalculatorExtension-Objekt, das IInstanceContextProviderimplementiert, und durchläuft jeden EndpointDispatcher und legt jede InstanceContextProvider-Eigenschaft auf das eben erstellte CalculatorExtension-Objekt fest. Dies wird im folgenden Beispielcode gezeigt.

//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);
        }
    }
}

Jeder Aufruf aus dem Client läuft durch das CalculatorExtension-Objekt, um zu bestimmen, mit welchem InstanceContext die jeweilige Nachricht bedient wird.

Das Beispiel demonstriert dieses Freigeben mithilfe zweier Clients, Client1 und Client2. Die Reihenfolge, in der die zwei Clients interagieren, wird im folgenden Code gezeigt. Beachten Sie, dass sowohl der Client als auch der Server den eindeutigen Bezeichner mithilfe eines CustomHeader für den InstanceContext übermitteln und einen eindeutigen 32-Byte-Bezeichner mit einem Zufallsnummerngenerator generieren.

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 erstellt einen OperationContextScope, um Header hinzufügen zu können. Anschließend generiert er eine eindeutige ID und fügt diese ID dann seiner Liste ausgehender Header als Wert hinzu.

    //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);
            ...
        }
    }
    

    Dann ruft er DoCalculations auf, das mehrere Vorgänge auf dem Remoteserver aufruft. Für jeden aufgerufenen Vorgang werden der benutzerdefinierte Header und die generierte ID gesendet.

  2. Client1 ruft den Add-Vorgang auf, was auf diesem Kanal der erste Aufruf ist.

  3. Der Server empfängt die Nachricht und ruft CalculatorExtension.GetExistingInstanceContext mit dem Kanal und der Nachricht auf. Die Erweiterung überprüft, ob der Kanal an einen InstanceContext angefügt ist. Wenn dies nicht der Fall ist, versucht die Erweiterung den benutzerdefinierten Header nachzuschlagen und überprüft, ob sich im Cache ein InstanceContext für diese ID befindet. In diesem Fall ist der Cache leer, daher wird NULL zurückgegeben. Beachten Sie, dass die Erweiterung tatsächlich (später in der Quelle definierte) AddressableInstanceContextInfo speichert, um zusätzliche Informationen über den InstanceContext zu speichern und um zwischen mehreren Threads zu koordinieren, bevor der InstanceContext erstellt wird.

    // 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. Dann erstellt der Server einen neuen InstanceContext und ruft CalculatorExtension.InitializeInstanceContext auf. Die InitializeInstanceContext-Methode benachrichtigt die Erweiterung über den neu erstellten InstanceContext. Diese ruft die AddressableInstanceContextInfo entweder aus dem Kanal (bei Sitzungskanälen) oder aus dem Cache (bei Datagrammkanälen) ab und fügt sie der Erweiterungsauflistung des InstanceContext hinzu. Dies wird durchgeführt, damit der Server für jeden InstanceContext schnell die ID abrufen kann. Wenn der Kanal über eine Sitzung verfügt, fügt die Erweiterung den Kanal zur InstanceContext's IncomingChannels-Auflistung hinzu, damit Windows Communication Foundation (WCF) den InstanceContext erst dann schließt, wenn der Kanal geschlossen ist. Das Beispiel hakt sich auch in das Closed-Ereignis von InstanceContext ein, so dass es aus dem Cache entfernt werden kann, wenn es explizit geschlossen wird. Nun wird dieser InstanceContext verwendet, um die Nachricht zu verarbeiten und zu beantworten.

    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 ruft dann seinen zweiten Vorgang (Subtract) auf. CalculatorExtension.GetExistingInstanceContext wird mit der neuen Nachricht erneut aufgerufen. Der Header wird abgerufen und dieses Mal erfolgreich nachgeschlagen. InstanceContext wird aus dem Cache zurückgegeben. WaitForInstance stellt sicher, dass der erste Aufruf seinen InitializeInstanceContext-Aufruf abgeschlossen hat. WCF verwendet diesen InstanceContext, um den Rest der Nachricht zu verarbeiten.

    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 ruft Subtract- und Delete-Vorgänge auf, und sie verwenden denselben InstanceContext, indem sie Schritt 5 wiederholen.

  7. Anschließend erstellt der Client eine anderen Kanal (Client2) und einen weiteren OperationContextScope und fügt seiner OutgoingMessageHeaders-Auflistung dieselbe ID hinzu. Dadurch wird nun dieselbe ID, die von Client1 verwendet wurde, mit allen bei Client2 vorgenommenen Aufrufen gesendet.

  8. Client2 ruft Add()-, Subtract()-, Multiply()- und Divide()-Vorgänge auf, die alle mit derselben Logik in Schritt 7 von dem vom ersten Client erstellten InstanceContext bedient werden. Der Server ruft CalculatorExtension.GetExistingInstanceContext auf. Die Erweiterung sucht den Header und schlägt den zugehörigen InstanceContext nach. Dieser InstanceContext wird verwendet, um die Nachricht zu verteilen.

    // 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;
    }
    

So richten Sie das Beispiel ein, erstellen es und führen es aus

  1. Stellen Sie sicher, dass Sie die Beispiele zum einmaligen Setupverfahren für Windows Communication Foundation ausgeführt haben.

  2. Folgen Sie zum Erstellen der Projektmappe den Anweisungen unter Erstellen der Windows Communication Foundation-Beispiele.

  3. Wenn Sie das Beispiel in einer Konfiguration mit einem Computer oder computerübergreifend ausführen möchten, folgen Sie den Anweisungen unter Durchführen der Windows Communication Foundation-Beispiele.

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