Compartir a través de


InstanceContextSharing

Download sample

Este ejemplo muestra cómo utilizar la interfaz IInstanceContextProvider para compartir los objetos InstanceContext en varias llamadas y clientes. Este ejemplo muestra cómo una aplicación cliente puede crear y enviar un identificador único al servicio. El servicio asocia a continuación ese identificador con un objeto InstanceContext concreto. El cliente puede pasar a continuación el identificador a otro cliente. Después, este cliente puede colocar el identificador de contexto en un encabezado que se envía al mismo servicio. Ese servicio utiliza el identificador de contexto para asociar la segunda llamada con el primer objeto de contexto de la instancia y, por consiguiente, el objeto de servicio.

Nota

El procedimiento de instalación y las instrucciones de compilación de este ejemplo se encuentran al final de este tema.

Implemente la interfaz IInstanceContextProvider para proporcionar al sistema el objeto InstanceContext adecuado. Normalmente, esta interfaz se implementa para admitir sesiones compartidas, habilitar la agrupación de instancias de servicio, controlar las duraciones de las instancias de servicio o agrupar contextos entre los clientes.

Para insertar el IInstanceContextProvider personalizado, cree un comportamiento (como un IEndpointBehavior o IServiceBehavior) y utilice el comportamiento para asignar el objeto IInstanceContextProvider a la propiedad System.ServiceModel.Dispatcher.DispatchRuntime.InstanceContextProvider.

Este ejemplo utiliza IServiceBehavior en un atributo ShareableAttribute personalizado para insertar IInstanceContextProvider.

ShareableAttribute crea instancias de un objeto CalculatorExtension, que implementa IInstanceContextProvider, recorre en iteración cada EndpointDispatcher y establece cada propiedad InstanceContextProvider en el objeto CalculatorExtension que acaba de crear. Esto se muestra en el código de ejemplo siguiente.

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

Cada llamada del cliente pasa por el objeto CalculatorExtension para determinar qué InstanceContext se utiliza para prestar servicio a ese mensaje en concreto.

El ejemplo utiliza dos clientes, Client1 y Client2 para mostrar el uso compartido. La secuencia en la que los dos clientes interactúan se muestra en el código siguiente. Tenga en cuenta que el cliente y el servidor utilizan CustomHeader para comunicar el identificador único para InstanceContext y utilizar una utilidad de generador de números aleatorios con objeto de generar un identificador de 32 bytes único.

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 un OperationContextScope de manera que puede agregar los encabezados. Después genera un id. único y, a continuación, lo agrega como un valor a su lista de encabezados salientes.

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

    A continuación, invoca DoCalculations que llama a varias operaciones en el servidor remoto. Para cada operación invocada, se envía el encabezado personalizado y el id. generado.

  2. Client1 invoca la operación Add, que es la primera llamada en este canal.

  3. El servidor recibe el mensaje e invoca CalculatorExtension.GetExistingInstanceContext con el canal y el mensaje. La extensión comprueba si el canal está asociado a InstanceContext. Si no, la extensión intenta buscar el encabezado personalizado y comprueba el caché para ver si tiene un InstanceContext para ese id. En este caso la caché está vacía, por lo que se devuelve null. Tenga en cuenta que la extensión almacena realmente un AddressableInstanceContextInfo (definido posteriormente en el origen) para mantener la información adicional sobre InstanceContext y para coordinar entre varios subprocesos antes de que se cree 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. El servidor crea a continuación un nuevo InstanceContext y llama a CalculatorExtension.InitializeInstanceContext. El método InitializeInstanceContext notifica la extensión del InstanceContext recién creado. Esto recupera AddressableInstanceContextInfo desde el canal (para los canales de la sesión) o la caché (para los canales del datagrama) y lo agrega a la colección de extensiones de InstanceContext. Se hace esto para que el servidor pueda recuperar rápidamente el id. para cualquier InstanceContext. Si el canal tiene una sesión, la extensión agrega el canal a la colección de IncomingChannels de InstanceContext de manera que Windows Communication Foundation (WCF) no cierre InstanceContext hasta que se cierre el canal. El ejemplo también enlaza con el evento Closed de InstanceContext de manera que se pueda quitar de la caché en caso de que se cierre explícitamente. Ahora se usa InstanceContext para procesar y responder al mensaje.

    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 llama a la segunda operación Subtract. Una vez más, se llama a CalculatorExtension.GetExistingInstanceContext con el nuevo mensaje. Se recupera el encabezado y esta vez la búsqueda tiene éxito. Devuelve InstanceContext desde la caché. WaitForInstance garantiza que la primera llamada ha finalizado su llamada de InitializeInstanceContext. WCF utiliza este InstanceContext para procesar el resto del mensaje.

    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 llama a las operaciones Subtract y Delete, y éstas utilizan el mismo InstanceContext repitiendo el paso 5.

  7. El cliente crea a continuación otro canal (Client2) y crea una vez más un OperationContextScope y agrega el mismo id. a su colección de OutgoingMessageHeaders. Así que el mismo id. usado por Client1 se envía ahora con todas las llamadas realizadas en Client2.

  8. Client2 llama a las operaciones Add(), Subtract(), Multiply() y Divide() y utiliza la misma lógica en el paso 7, todas ellas están atendidas por el InstanceContext creado por el primer cliente. El servidor llama a CalculatorExtension.GetExistingInstanceContext. La extensión encuentra el encabezado y busca el InstanceContext asociado a él. Se utiliza InstanceContext para enviar el mensaje.

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

Para configurar, generar y ejecutar el ejemplo

  1. Asegúrese de que ha realizado el Procedimiento de instalación único para ejemplos de Windows Communication Foundation.

  2. Para generar la solución, siga las instrucciones de Generación de ejemplos de Windows Communication Foundation.

  3. Para ejecutar el ejemplo en una configuración de equipos única o cruzada, siga las instrucciones de Ejecución de ejemplos de Windows Communication Foundation.

Footer image

Copyright © 2007 Microsoft Corporation. Reservados todos los derechos.