InstanceContextSharing
Cet exemple montre comment utiliser l'interface IInstanceContextProvider pour partager des objets InstanceContext sur des appels et des clients multiples. Cet exemple montre comment une application cliente peut créer et envoyer un identificateur unique au service. Le service associe ensuite cet identificateur à un objet InstanceContext spécifique. Le client peut passer ensuite l'identificateur à un autre client. Ce client peut alors placer l'identificateur de contexte dans un en-tête envoyé au même service. Ce service utilise l'identificateur de contexte pour associer le deuxième appel au premier objet de contexte d'instance et par conséquent à l'objet de service.
Remarque : |
---|
La procédure d'installation ainsi que les instructions de génération relatives à cet exemple figurent à la fin de cette rubrique. |
Implémentez l'interface IInstanceContextProvider pour fournir l'objet InstanceContext approprié au système. En général, cette interface est implémentée pour prendre en charge les sessions partagées, activer le regroupement d'instances de service, contrôler les durées de vie d'instances de service ou regrouper des contextes parmi des clients.
Pour insérer le IInstanceContextProvider personnalisé, créez un comportement (tel qu'un IEndpointBehavior ou un IServiceBehavior) et utilisez le comportement pour assigner l'objet IInstanceContextProvider à la propriété System.ServiceModel.Dispatcher.DispatchRuntime.InstanceContextProvider.
Cet exemple utilise un IServiceBehavior sur un attribut ShareableAttribute
personnalisé pour insérer le IInstanceContextProvider.
L'ShareableAttribute
instancie un objet CalculatorExtension
, qui implémente IInstanceContextProvider et parcourt chaque EndpointDispatcher et affecte à chaque propriété InstanceContextProvider l'objet CalculatorExtension
tout juste créé. L'exemple de code suivant le démontre.
//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);
}
}
}
Chaque appel du client traverse l'objet CalculatorExtension
pour déterminer quel InstanceContext est utilisé pour tel message particulier.
L'exemple utilise deux clients, Client1
et Client2
, pour illustrer le partage. La séquence dans laquelle les deux clients interagissent est illustrée dans le code suivant. Notez que le client et le serveur utilisent tous deux un CustomHeader
pour communiquer l'identificateur unique pour le InstanceContext et utilisent un générateur de nombres aléatoires pour générer un identificateur unique de 32 octets.
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
crée un OperationContextScope afin de pouvoir ajouter des en-têtes. Il génère alors un ID unique puis ajoute cet ID comme valeur à sa liste d'en-têtes sortants.//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); ... } }
Puis, il appelle
DoCalculations
qui appelle plusieurs opérations le serveur distant. Pour chaque opération appelée, l'en-tête personnalisé et l'ID généré sont envoyés.Client1
appelle l'opérationAdd
, ce qui représente le premier appel sur ce canal.Le serveur reçoit le message et appelle
CalculatorExtension.GetExistingInstanceContext
avec le canal et le message. L'extension vérifie si le canal est joint à un InstanceContext. Si ce n'est pas le cas, l'extension essaie de rechercher l'en-tête personnalisé et vérifie le cache pour voir s'il comporte un InstanceContext pour cet ID. Dans le cas présent, le cache est vide et donc, null est retourné. Notez que l'extension stocke réellement unAddressableInstanceContextInfo
(défini ultérieurement dans la source) pour conserver des informations supplémentaires à propos deInstanceContext
et coordonner plusieurs threads avant queInstanceContext
soit créé.// 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; }
Le serveur crée ensuite un nouveau InstanceContext et appelle
CalculatorExtension.InitializeInstanceContext
. La méthode InitializeInstanceContext informe l'extension de la création du InstanceContext. Cela permet de récupérer lesAddressableInstanceContextInfo
soit depuis le canal (pour les canaux de session), soit depuis le cache (pour les canaux de datagramme) et les ajoute à la collection d'extensions du InstanceContext. Cela permet au serveur de récupérer rapidement l'ID de tout InstanceContext. Si le canal a une session, l'extension ajoute le canal à la collection IncomingChannels du InstanceContext afin que Windows Communication Foundation (WCF) ne ferme pas le InstanceContext jusqu'à ce que le canal soit fermé. L'exemple se raccorde également à l'événement Closed du InstanceContext afin de pouvoir être supprimé du cache au cas où il serait fermé explicitement. Désormais, ce InstanceContext est utilisé pour traiter le message et y répondre.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
appelle ensuite sa deuxième opérationSubtract
. Encore une fois,CalculatorExtension.GetExistingInstanceContext
est appelé avec le nouveau message. L'en-tête est récupéré et cette fois la recherche réussit. Elle retourne le InstanceContext du cache.WaitForInstance
s'assure que le premier appel a terminé son appel auInitializeInstanceContext
. WCF utilise ce InstanceContext pour traiter le reste du message.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
appelle les opérationsSubtract
etDelete
qui utilisent le même InstanceContext en répétant l'étape 5.Le client crée ensuite un autre canal (
Client2
) et encore une fois crée une OperationContextScope et ajoute le même ID à sa collection OutgoingMessageHeaders. Donc, le même ID utilisé parClient1
est maintenant envoyé avec tous les appels généraux faits surClient2
.Client2
appelle les opérationsAdd()
,Subtract()
,Multiply()
etDivide()
et à l'aide de la même logique qu'à l'étape 7, toutes sont traitées par le InstanceContext créé par le premier client. Le serveur appelleCalculatorExtension.GetExistingInstanceContext
. L'extension recherche l'en-tête et le InstanceContext associé. Ce InstanceContext est utilisé pour distribuer le message.// 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; }
Pour configurer, générer et exécuter l'exemple
Assurez-vous d'avoir effectué la procédure figurant dans la section Procédure d'installation unique pour les exemples Windows Communication Foundation.
Pour générer la solution, suivez les instructions indiquées dans la rubrique Génération des exemples Windows Communication Foundation.
Pour exécuter l'exemple dans une configuration à un ou plusieurs ordinateurs, conformez-vous aux instructions figurant dans la rubrique Exécution des exemples Windows Communication Foundation.
Send comments about this topic to Microsoft.
© 2007 Microsoft Corporation. All rights reserved.