InstanceContextSharing

In questo esempio viene illustrato come utilizzare l'interfaccia IInstanceContextProvider per condividere oggetti InstanceContext con più chiamate e client. Viene inoltre illustrato come un'applicazione client può creare e inviare un identificatore univoco al servizio. Il servizio associa quindi tale identificatore a un oggetto InstanceContext specifico. Il client può quindi passare l'identificatore a un altro client. Tale client può quindi posizionare l'identificatore di contesto in un'intestazione inviata allo stesso servizio. Tale servizio utilizza l'identificatore di contesto per associare la seconda chiamata al primo oggetto del contesto dell'istanza e pertanto all'oggetto del servizio.

Nota

La procedura di installazione e le istruzioni di compilazione per questo esempio si trovano alla fine dell'argomento.

Implementare l'interfaccia IInstanceContextProvider per fornire l'oggetto InstanceContext appropriato al sistema. In genere, questa interfaccia viene implementata per supportare sessioni condivise, abilitare il pooling delle istanze di servizio, controllare la durata delle istanze del servizio o raggruppare i contesti fra client.

Per inserire l'interfaccia IInstanceContextProvider personalizzata, creare un comportamento (ad esempio IEndpointBehavior o IServiceBehavior) e utilizzarlo per assegnare l'oggetto IInstanceContextProvider alla proprietà System.ServiceModel.Dispatcher.DispatchRuntime.InstanceContextProvider.

In questo esempio viene utilizzata un'interfaccia IServiceBehavior su un attributo ShareableAttribute personalizzato per inserire IInstanceContextProvider.

ShareableAttribute crea un'istanza di un oggetto CalculatorExtension che implementa IInstanceContextProvider, scorre ogni EndpointDispatcher e imposta ogni proprietà InstanceContextProvider sull'oggetto CalculatorExtension appena creato, come illustrato nel codice di esempio seguente.

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

Ogni chiamata dal client passa attraverso l'oggetto CalculatorExtension per determinare quale InstanceContext viene utilizzato per gestire il messaggio specifico.

Nell'esempio vengono utilizzati due client, Client1 e Client2, per dimostrare la condivisione. La sequenza in cui i due client interagiscono è mostrata nel codice seguente. Si noti che sia il client che il server utilizzano un CustomHeader per comunicare l'identificatore univoco per InstanceContext e utilizzano un generatore di numeri casuali per generare un identificatore univoco 32 byte.

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 crea OperationContextScope in modo da poter aggiungere le intestazioni. Genera quindi un ID univoco e lo aggiunge come valore all'elenco corrispondente di intestazioni in uscita.

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

    Richiama quindi DoCalculations, che chiama varie operazioni sul server remoto. Per ogni operazione richiamata, vengono inviati l'intestazione personalizzata e l'ID generato.

  2. Client1 richiama l'operazione Add, che è la prima chiamata su questo canale.

  3. Il server riceve il messaggio e richiama CalculatorExtension.GetExistingInstanceContext con il canale e il messaggio. L'estensione verifica se il canale è collegato a un InstanceContext. In caso contrario, l'estensione cerca l'intestazione personalizzata e controlla la cache per verificare se è presente un InstanceContext per tale ID. In questo caso la cache è vuota, quindi viene restituito null. Si noti che l'estensione archivia un oggetto AddressableInstanceContextInfo (definito successivamente nell'origine) per conservare informazioni aggiuntive su InstanceContext e per coordinare più thread prima della creazione di 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. Il server crea quindi un nuovo InstanceContext e chiama CalculatorExtension.InitializeInstanceContext. Il metodo InitializeInstanceContext notifica l'estensione del InstanceContext appena creato. Viene quindi recuperato AddressableInstanceContextInfo dal canale (per i canali della sessione) o dalla cache (per i canali del datagramma) e viene aggiunto all'insieme di estensioni di InstanceContext. Questa procedura viene eseguita in modo che il server possa recuperare rapidamente l'ID per qualsiasi InstanceContext. Se il canale dispone di una sessione, l'estensione aggiunge il canale all'insieme InstanceContext's IncomingChannels in modo che Windows Communication Foundation (WCF) non chiuda il InstanceContext fino alla chiusura del canale. L'esempio è associato anche all'evento Closed di InstanceContext per poter essere rimosso dalla cache qualora venga chiuso in modo esplicito. Questo InstanceContext viene quindi utilizzato per elaborare e rispondere al messaggio.

    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 chiama quindi la seconda operazione Subtract. Ancora una volta, CalculatorExtension.GetExistingInstanceContext viene chiamato con il nuovo messaggio. L'intestazione viene recuperata e, questa volta, la ricerca ha esito positivo, in quanto restituisce InstanceContext dalla cache. WaitForInstance assicura che la prima chiamata abbia concluso la chiamata a InitializeInstanceContext. WCF utilizza questo InstanceContext per elaborare le parti restanti del messaggio.

    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 chiama le operazioni Subtract e Delete che utilizzano stesso InstanceContext ripetendo il passaggio 5.

  7. Il client crea quindi un altro canale (Client2) e ancora una volta crea un OperationContextScope e aggiunge lo stesso ID al relativo insieme OutgoingMessageHeaders. Quindi lo stesso ID utilizzato da Client1 viene ora inviato con tutte le chiamate eseguite su Client2.

  8. Client2 chiama le operazioni Add(), Subtract(), Multiply() e Divide() e, utilizzando la stessa logica del passaggio 7, tutte le operazioni vengono gestite dall'oggetto InstanceContext creato dal primo client. Il server chiama CalculatorExtension.GetExistingInstanceContext. L'estensione individua l'intestazione e cerca l'oggetto InstanceContext associato. Tale oggetto InstanceContext viene utilizzato per inviare il messaggio.

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

Per impostare, compilare ed eseguire l'esempio

  1. Verificare di aver eseguito Procedura di installazione singola per gli esempi di Windows Communication Foundation.

  2. Per compilare la soluzione, seguire le istruzioni in Generazione degli esempi Windows Communication Foundation.

  3. Per eseguire l'esempio su una configurazione con un solo computer o tra computer diversi, seguire le istruzioni in Esecuzione degli esempi di Windows Communication Foundation.

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