Microsoft .NET Framework 3.5 Windows Communication Foundation: Sessions and Instancing

  • 9/24/2008

Lesson 1: Instancing Modes

Instancing should, for the most part, be a service-side implementation detail that has no effect on the client, and this is generally the case. However, the demands of the client frequently do influence the instancing that should be used. Instancing can affect scalability, throughput, transactions, and queued calls, so although the client might be oblivious to the instancing mode, the service can’t reciprocate. This lesson considers the different types of possible instancing, along with how they are set up and the ramifications of the choices.

Instancing

WCF is responsible for binding an incoming message to a particular service instance. When a request comes in, WCF determines whether an existing instance of the service class (the service instance) can process the request. The decision matrix for this choice is basically the instancing management that WCF provides.

When it comes to the question of which instancing mode to use, there is no correct answer. A variety of factors must be balanced to determine the most appropriate mode for the given situation. For this reason, this lesson covers all the modes in great detail and provides scenarios in which they might be the most appropriate choice. However, even with the given scenarios, the choice is seldom clear, and a small change in the importance of one factor can tip the scale to another choice. Your benefit from this discussion should be a general sense of when a particular mode is more or less likely to be chosen.

The determination of the instancing mode is done on the service side. This is to be expected because it is an implementation detail that should be hidden from the caller. The mode is defined within the service behavior. This means that the instancing mode is used across all the endpoints of a service. It can also be applied directly in the service’s implementation class.

Three choices are available for the InstanceContextMode. They are per call mode, per session mode, and singleton mode. The meanings of these modes are described in the next few sections, but those are not the only available choices. In the original version of WCF, there was also an option in the InstanceContextMode enumeration called Shareable. Although the functionality still exists, the enumerated value does not. Instead, to share the same service instance across multiple requests, the service must intercept the request, determine which instance the requestor wants, and then provide that instance to the run time. The upcoming sections describe how this is done.

Per Call Mode

In per call mode, every single request gets its own copy of a service implementation object. Figure 10-1 illustrates the basic flow for the request

Figure 10-1

Figure 10-1 Per call instantiation

The client makes a request to the service through a proxy. When the request arrives at the service host, the host creates an instance of the service’s implementation class. This class is then called to process the request. After the request is complete and the response returned to the client, the implementation object is disposed of.

Per call instancing is the default mode for WCF. There are a number of reasons for making this particular choice. For the developer, per call mode requires the least amount of consideration given to concurrency. If each request has its own copy of the object, there is no need to worry about a shared value being updated in a non-atomic manner.

Historically, the instancing mode many client/server applications used was one implementation object per client. This is a simple approach, but a number of problems affect performance. For example, consider the issue associated with a scarce resource. If the service object opens a connection to a database and keeps that connection open for its lifetime, the resource is unavailable for use by other instances, yet the period of time the resource might actually be required is quite small.

It is well understood by designers of distributed applications that this model has scalability weaknesses. One of the solutions is to reduce the time the implementation object exists. This is the genesis for the per call mode. In per call, the implementation object is instantiated as soon as it is needed, and it is disposed of as soon as the request is completed. If the object holds on to a scarce resource, the lifetime of the object has been reduced to minimize the impact holding that resource has on overall performance.

However, “simple to use” is not the same as “best.” And that per call instancing hides many of the challenges associated with distributed applications doesn’t mean that it should be the mode you always use. Consider some of the drawbacks associated with this approach.

A scarce resource is one that is expensive to allocate or is limited in the number available for use. A canonical example is a file that resides on the service system’s hard drive. If the file is opened for update, only one service implementation instance can have it open at a time, so in a per call instancing mode, only the first request in can be processed through to completion. The second (and subsequent) requests will block, waiting for the physical file to become available. Although a physical file is an extreme scenario, there are many other scenarios. Database connections, network connections (used to make Web service calls), or communications ports all qualify as scarce resources.

One of the keys to making this model work is the existence of a proxy object for the service. The typical programming model that has already been discussed has the client instantiating an object and maintaining a reference to it for the life of the application. However, in per call mode, the object that is referred to should be disposed of. This would typically invalidate the reference, a generally undesirable outcome. However, in the world of WCF, the client is actually holding a reference to the proxy. The proxy is not disposed of with every call. Instead, it becomes part of the proxy’s job to re-create the service implementation object as necessary.

An ancillary benefit to this model is how it works with transactional applications. The need to re-create the object and reconnect to scarce resources works well in an environment in which the instance state must be deterministic.

As has already been mentioned, the instancing mode is set at the service level. The following code demonstrates (in bold) how to set the mode to per call.

' VB
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerCall)> _
Public Class UpdateService
   Implements IUpdateService
   ...
End Class

// C#
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class UpdateService : IUpdateService {...}

Although, theoretically, the client doesn’t need to be aware of whether the service is running in per call mode, the reality is that per call means that no state can exist between calls. It becomes a design issue, but the client cannot expect that the results from one call will be preserved or used in the second call to the service. If this is a requirement, regardless of the reason, it becomes part of the service’s task to ensure that state is saved across calls. This would typically be done by persisting the state into a service-local store (such as a database). Then, when subsequent requests come in, the previously saved state can be restored and used.

If the service’s design calls for this pattern, there is an impact on the design of the Service contract. Specifically, each operation must include a parameter that identifies the client making the request. This allows the service method to retrieve the state associated with the client. The actual parameter that is used could be a business-level value (customer number, order number, account number) or a meaningless value (such as guid).

From a general design perspective, per call mode is best used when individual operations are short and the operation does not spawn any background threads that continue processing after the request is complete. The reason for this second stipulation has to do with the disposal of the implementation object. If an operation were to spin up something that isn’t completed prior to the response being returned to the client, the object will not be around to receive the result. It will have been destroyed as soon as the request is finished.

Per Session Mode

Given the idea that a parameter would be passed into a service’s method to retrieve state, it seems a short jump to this next mode. WCF can maintain a private session between a client and a particular instance of the service’s implementation object.

The key to understanding the intricacies of per session mode is understanding what is happening internally. Each client, upon the first request to the service, gets an instance of the service’s implementation object. This instance is dedicated to processing the requests that come from that client. Any subsequent calls are considered to be part of the same session (with some exceptions that will be described shortly), and the calls are processed by the same instance of the implementation object. Figure 10-2 illustrates this relationship.

Figure 10-2

Figure 10-2 Per session mode interactions

There are two components to per session mode. The contractual piece involves letting the client know that a session is required. This is necessary because to maintain the session, the client must include an identifier to locate the appropriate implementation object in the service. To indicate to the contract that a session is to be maintained, the ServiceContract attribute includes a SessionMode property. For per session mode to be used, this Boolean value must be set to SessionMode.Required, as demonstrated in bold in the following code.

' VB
<ServiceContract(SessionMode:=SessionMode.Required)> _
Public Interface IUpdateService
   ' Interface definition code goes here
End Interface

// C#
[ServiceContract(SessionMode = SessionMode.Required)]
public interface IUpdateService
{
   // Interface definition code goes here
}

The second component of the configuration is behavioral in nature. WCF needs to be told that you would like to use per session mode and that the service instance should be kept alive throughout the session. You do this by setting the InstanceContextMode in the service behavior as illustrated in bold in the following code.

' VB
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerSession)>_
Public Class UpdateService
   Implements IUpdateService

   ' Implementation code goes here

End Class
// C#
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class UpdateService : IUpdateService
{
   // Implementation code goes here
}

Now it’s time to talk about some of the details. The relationship isn’t quite between the client and the service. It is actually between a specific instance of the proxy class used by the client and the service. When you create a proxy for a WCF service, an identifier for that proxy is generated. This identifier is used by the service host to direct any requests to the appropriate instance. However, the identifier is associated with the instance of the proxy class, so if a single client creates more than one instance of the proxy class, those instances will not combine sessions. Each proxy will get its own instance of the service implementation class.

After an instance is created for a proxy, the instance remains in memory for the length of the session. For this reason, it is possible to maintain state in memory. This makes the programming model quite similar to the traditional client–server approach, but this also means that per session mode suffers from the same issues that the client server model has. It has issues with scalability, needs to be aware of state, and can have problems with transactions. The practical limit for a service is no more than a few hundred clients.

As mentioned earlier, the service instance lasts until the client no longer requires it. Again, there are caveats to that generalization. The most efficient path for session termination involves the client closing the proxy. This causes a notification to be sent to the service that the session has ended, but what happens if the client doesn’t close the proxy? What happens if the client doesn’t terminate gracefully or a communications issue between the client and service prevents the notification from being received? In these cases, the session will automatically terminate after ten minutes of inactivity. After the session has been terminated in such a manner, the client will receive a CommunicationObjectFaultedException if it attempts to use the proxy.

This ten-minute timeout is just the default value. Whether the default can be changed depends on the binding. If the binding supports a reliable session, you can set the InactivityTimeout property associated with the reliable session. The following code demonstrates how to do this with a netTcpBinding binding.

' VB
Dim binding As New NetTcpBinding()
binding.ReliableSession.Enabled = True
binding.ReliableSession.InactivityTimeout = TimeSpan.FromMinutes(60)

// C#
NetTcpBinding binding = new NetTcpBinding();
binding.ReliableSession.Enabled = true;
binding.ReliableSession.InactivityTimeout = TimeSpan.FromMinutes(60);

You can make the same setting through configuration files, as illustrated in the following segment:

<netTcpBinding>
    <binding name="timeoutSession">
        <reliableSession enabled="true" inactivityTimeout="01:00:00"/>
    </binding>
</netTcpBinding>

Speaking of reliable sessions, support for reliable sessions is required for a binding to support sessions. All the endpoints that expose the Service contract must use bindings that support reliable transport sessions, and the session must be enabled as shown in the earlier code example. This constraint is validated when the service is loaded; an InvalidOperationException is thrown if there is a mismatch.

One binding that is unable to support sessions is basicHttpBinding. Within the protocol that underlies this binding, there is no way to pass the information necessary to maintain the session. You can overcome the problem with the transport protocol within the format of the messages. The wsHttpBinding binding is capable of providing the necessary data to support sessions, for example.

There is one exception to the reliable session rule. The named pipe binding supports reliability by definition, so there is no need for the reliable messaging protocol to be implemented—and the netNamedPipeBinding binding does support sessions.

Singleton Mode

In this mode, only one instance of the service’s implementation class is created. This instance is enlisted to handle every request that arrives at the service. The instance lives forever (or close to forever) and is disposed of only when the host process shuts down.

Singleton mode does not require any session information to be transmitted with the message. As a result, there is no restriction on the ability of the binding to support transport-level sessions. Nor is there a need for the protocol or binding to provide a mechanism that appears to emulate session behavior. If the contract exposed by the service has a session, the client must provide the session, but there is no requirement for sessions for singleton mode to work. Further, if a session is associated with the request, that session will never expire. The session identifier is maintained within the client proxy until the proxy is destroyed.

Alternatively, if no session information is exposed by the contract, the communications don’t fall back to per call mode (unlike other modes). Instead, the request continues to be handled by the single instance of the singleton service.

Configure a singleton service in a manner similar to the other modes. The InstanceContextMode property of the ServiceBehavior attribute is set to Single. The following code demonstrates this, as shown in bold:

' VB
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single)> _
Public Class UpdateService
   Implements IUpdateService
' Implementation code goes here
End Class

// C#
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class UpdateService : IUpdateService
{
   // Implementation code goes here
}

One of the features the singleton behavior offers is the ability to initialize the implementation instance through the constructor. For the other behaviors, the instance object is created behind the scenes at a time determined by the host process, but for singletons, you have the option to create the singleton instance and pass it into the host process.

Naturally, this begs the question of why you would want to do this. Typically, the rationale involves performing initialization processing outside the scope of the first request. If the service instance needs to allocate some resources (such as connecting to a database), the default behavior is to have the first request pay that performance price. It might be that logic should be injected into the service instance that is not available to the client (and, therefore, couldn’t be included in the request). In both of these cases (and there are other reasons as well), having the service host create the singleton instance is not the best alternative.

However, in singleton mode, you can create the service instance before the host is even started. This instance can then be passed into the host as the host is getting started. One of the constructors for the ServiceHost class takes a singleton instance as a parameter. When constructed in this manner, the host will direct all incoming requests to the provided instance. The following code demonstrates how this is accomplished.

' VB
Dim singletonInstance As New SingletonUpdateService()
Dim host As New ServiceHost(singletonInstance)
host.Open()

// C#
SingletonUpdateService singletonInstance = new SingletonUpdateService();
ServiceHost host = new ServiceHost(singletonInstance);
host.Open();

For the preceding code to work, the service (SingletonUpdateService in this example) must be defined with the InstanceContextMode property in the ServiceBehavior attribute set to Single.

When the service host is using a singleton instance, it is also possible for other objects to reach into the instance to call methods or set parameters. The ServiceHost class exposes a SingletonInstance property that references the instance processing the incoming requests. The following code demonstrates how to update a member of the instance:

' VB
Dim instance As SingletonUpdateService = _
   TryCast(host.SingletonInstance, SingletonUpdateService)
instance.Counter += 50

// C#
SingletonUpdateService instance = host.SingletonInstance as
    SingletonUpdateService;
instance.Counter += 50;

Even if the local host object variable is not available, you can still gain access to the instance. The OperationContext class exposes a read-only Host property, so from within an operation, the singleton instance can be accessed.

' VB
Dim host As ServiceHost = TryCast(OperationContext.Current.Host, _
   ServiceHost)
If host IsNot Nothing Then
   Dim instance as SingletonUpdateService = _
      TryCast(host.SingletonInstance, SingletonUpdateService)
   If instance IsNot Nothing Then
      Instance.Counter += 1
   End If
End If

// C#
ServiceHost host = OperationContext.Current.Host as ServiceHost;
if (host != null)
{
   SingletonUpdateService instance = host.SingletonInstance
      as SingletonUpdateService;
   if (instance != null)
      instance.Counter += 1;
}

That every request is handled by a single instance of the implementation class has potential implications for contention issues. If multiple requests arrive at the service, they will be processed, in many cases, by the same instance but in a different worker thread. This means that any variable scoped outside of the current method (that is a class-level variable) can be corrupted if the value is updated by two worker threads at once. You must ensure that updates are performed using concurrency techniques such as locking.

The side effect of dealing with concurrency is that, at least in areas that are synchronized, only one request can be processed at a time. If the singleton service has a number of areas that require synchronization, or even if there is only one but it is in a frequently used method, performance can be negatively affected.

From a design perspective, singleton services are best used when they are modeling a singleton resource—a log file, perhaps, that allows a single writer only or, as has recently happened in the real-world job mentioned earlier, communicating with a single robot. If there is a possibility that, in the future, the service might no longer be a singleton, think hard before using this model. Subtle dependencies can be introduced while using the singleton model. The client might come to expect that state will be shared across multiple requests. Although the change to reconfigure the service to be something other than a singleton is simple, the challenge of tracking down dependency bugs can be much worse.

Sharing Instances

As has been mentioned, the mechanism for sharing a service instance between multiple clients has changed from the original approach. By creating a class that implements the IInstanceContextProvider interface and then injecting the class into the dispatch pipeline, you can have a great deal of control over which instance of the service class will be used to service each request.

The starting point must come from the client. For the service to distinguish between the different clients, it examines each incoming request. Based on information that exists within the request, an existing instance is provided (or a new one is created). This generally means that the client needs to place something in the request, such as a message header. The easiest way to accomplish this is to use the MessageHeader class factory to create an instance of a MessageHeader object. That object can then be added to the message headers that are sent with the request. The following code demonstrates how to do this.

' VB
Dim header As MessageHeader = _
   MessageHeader.CreateHeader("headerName", "headerNamespace", _
   "instanceId")

Using SessionClient proxy As NewSessionClient()
   Using (New OperationContextScope(proxy.InnerChannel))
      OperationContext.Current.OutgoingMessageHeaders.Add(header)
      ' use the proxy object
   End Using
End Using

// C#
MessageHeader header = MessageHeader.CreateHeader("headerName",
   "headerNamespace", "instanceId");

using (SessionClient proxy = new SessionClient())
{
   using (new OperationContextScope(proxy.InnerChannel))
   {
       OperationContext.Current.OutgoingMessageHeaders.Add(header);
        // Use the proxy object
   }
}

The idea is that any client making a request to the service will use this pattern of code. If two clients must share an instance, the instance ID from one client will be sent to the second client, which would then include that in the message header it sends to the service.

Sending the header information is just the starting point. On the service side, the presence of the instance ID must be recognized and extracted from the request. This ID is then used as the key to a collection of previously created instances. If the corresponding instance already exists in the collection, it must be used to process the request. If the instance ID does not exist, a new instance must be created and then added to the collection to handle future requests.

The mechanism to implement the preceding scenario might not be obvious. Fortunately, Microsoft uses a provider model for the creation of instances to process requests. The interface for this is named IInstanceContextProvider. This interface exposes four methods: GetExistingInstanceContext, InitializeInstanceContext, IsIdle, and NotifyIdle. These four methods actually work in two groups.

GetExistingInstanceContext and InitializeInstanceContext work in concert to determine which instance of the service’s implementation object will be used to create the response. The GetExistingInstanceContext method is invoked as part of the process of handling an incoming request. The result from this method is either an existing instance context or a value of null/ Nothing. In the latter case, WCF recognizes that no instance has been previously created, so it creates a new instance and then invokes the InitializeInstanceContext method. The idea is that any setup that must be performed on the new instance will be done in the InitializeInstanceContext method. In the case of the instance-sharing mode, this would normally include saving the new instance so that it can be retrieved in a future call to GetExistingInstanceContext.

WCF uses the IsIdle and NotifyIdle methods when it believes that all the activities associated with an instance have been completed. At this point, the IsIdle method is invoked. It is up to this method to determine whether the client (or clients) no longer needs the instance. The method returns a Boolean value, and if it returns true, then WCF will close the context.

Alternatively, if IsIdle returns False, that is a signal to WCF that the client might still need the particular instance. At this point, WCF invokes the NotifyIdle method. This method includes as one of the parameters a callback method. The idea is that, after the instance is no longer required (as determined by the provider), the method reference by the callback parameter will be invoked. This notifies WCF that the instance is no longer required. It will then start the instance deactivation process (including a call to the IsIdle method) once again.

Lab: Instance Modes

In this lab, you will focus on experimenting with the different instancing modes available in WCF. The first exercise looks at the InstanceContextMode enumeration, illustrating the different possible behaviors. The second exercise walks you through the creation of an instance context provider and illustrates how it can be used to share instances between clients.

httpatomoreillycomsourcemspimages713776.jpg Exercise 1 Per Session, Per Call, and Singleton Modes

In this first exercise, you will use the InstanceContextMode value to determine the instancing WCF uses as well as to demonstrate the behavior of each mode by using a variable that is private to the implementation class.

  1. Navigate to the <InstallHome>/Chapter10/Lesson1/Exercise1/<language>/Before directory and double-click the Exercise1.sln file to open the solution in Visual Studio.

    The solution consists of two projects. They are as follows:

    • The DemoService project, a simple WCF service library that implements the ISession interface. This interface consists of a single method (GetSessionStatus) that returns a string indicating the number of times the method has been called within the current service instance.

    • The TestClient project, a Console application that generates a request for the service and displays the result in the Console window.

  2. In Solution Explorer, double-click the Program.cs or Mobile1.vb file in the TestClient project.

    In this file, you can see the lines of code that send requests to the service. Initially, there are two calls, back to back. First, set up the service to use the PerCall instance method. This is actually redundant because that is the default mode, but it does set up for the other modes.

  3. To start, in Solution Explorer, double-click the SessionService file.

    The declaration for the SessionService class includes the ServiceBehavior attribute. One of the properties for that class is named InstanceContextMode. You can assign this value through the attribute by using a named parameter format.

    Change the class declaration to be the following:

    ' VB
    <ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerCall)>_
    Public Class SessionService
       Implements ISession
    
    // C#
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]
    public class SessionService : ISession
  4. Ensure that TestClient is set as the startup project and launch the application by pressing F5.

    After a few moments, you will see that two messages appear. Each message indicates that the instance has been called only one time, even though the same proxy object is being used. This is to be expected when the instance is created once per call.

  5. Press Enter to stop running the application.

  6. In the SessionService file, change the instance context mode from PerCall to PerSession. When you are finished, the class declaration will look like the following (changes shown in bold):

    ' VB
    <ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerSession)> _
    Public Class SessionService
       Implements ISession
    
    // C#
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
    public class SessionService : ISession

    For session mode to work, the service interface must be marked as requiring an interface.

  7. In Solution Explorer, double-click the ISession file.

    The declaration for the ISession interface includes a ServiceContract attribute. The attribute includes a SessionMode property, which you must set to Required.

  8. Modify the interface’s declaration as shown in bold to look like the following:

    ' VB
    <ServiceContract(SessionMode:=SessionMode.Required)> _
    Public Interface ISession
    
    // C#
    [ServiceContract(SessionMode=SessionMode.Required)]
    public interface ISession
  9. Launch the application by pressing F5.

    After a few moments, you will see that two messages appear. The messages indicate that a single instance of the service class has been called twice. Again, this is the expectation when the instance is created once per session.

  10. Press Enter to terminate the application.

    To simulate two clients, the client application can create two separate using blocks.

  11. In the Program.cs or Module1.vb file, add a second using block that creates a new proxy object and invokes the service. Change the Main method so that the body looks like the following:

    ' VB
    Using proxy As New DemoService.GetSessionStatusClient()
       Console.WriteLine("First call: " + proxy.GetSessionStatus())
    End Using
    
    Using proxy As New DemoService.GetSessionStatusClient()
       Console.WriteLine("Second call: " + proxy.GetSessionStatus())
    End Using
    Console.ReadLine()
    
    // C#
    using (DemoService.GetSessionStatusClient proxy = new
       DemoService.GetSessionStatusClient())
    {
       Console.WriteLine("First call: " + proxy.GetSessionStatus());
    }
    
    using (DemoService.GetSessionStatusClient proxy = new
       DemoService.GetSessionStatusClient())
    {
       Console.WriteLine("Second call: " + proxy.GetSessionStatus());
    }
    Console.ReadLine();
  12. Launch the application by pressing F5.

    In a few moments, the messages will appear on the console. The messages indicate that even though the instance context mode is set to PerSession, the different using blocks result in two different sessions.

  13. Press Enter to terminate the application.

  14. In the SessionService file, change the instance mode to Single.

    The declaration for the SessionService class should read as follows (changes shown in bold):

    ' VB
    <ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single)> _
    Public Class SessionService
       Implements ISession
    
    // C#
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
    public class SessionService : ISession
  15. Launch the application one last time by pressing F5.

    In a few moments, the console messages appear. In this case, they indicate that even though two different sessions have been created (there are still two using blocks), they both use the same session instance.

  16. Press Enter to terminate the application

httpatomoreillycomsourcemspimages713776.jpg Exercise 2 Share Service Instances

The fourth instancing mode for WCF services used to be known as Shareable. WCF uses a provider model to determine which instance of a service implementation class should be used. In this exercise, you will create a custom provider for instances and inject it into the WCF pipeline. The instance ID will be a number typed into the client to emulate the sharing process.

  1. Navigate to the <InstallHome>/Chapter10/Lesson1/Exercise2/<language>/Before directory and double-click the Exercise2.sln file to open the solution in Visual Studio.

    The solution consists of two projects. They are as follows:

    • The DemoService project, a simple WCF service library that implements the ISession interface. This interface consists of a single method (GetSessionStatus) that returns a string indicating the number of times the method has been called within the current service instance.

    • The TestClient project, a Console application that generates a request for the service and displays the result in the Console window.

  2. In Solution Explorer, double-click the DemoContextInfo file.

    This file will store information about an individual instance context. The provider will maintain a dictionary of DemoContextInfo files. This class implements the IExtension interface. The interface facilitates the aggregation of classes into the WCF pipeline, although in this particular case, the methods associated with this interface (Attach and Detach) are not needed for the implementation.

  3. In Solution Explorer, double-click the DemoContextProvider file.

    This file will provide the implementation for the provider. This class must implement the IInstanceContextProvider interface.

  4. Change the class declaration to be as follows:

    ' VB
    Public Class DemoContextProvider
       Implements IInstanceContextProvider
    
    // C#
    public class DemoContextProvider : IInstanceContextProvider

    The interface requires four methods to be added.

  5. Add the following method blocks to fulfill this requirement:

    ' VB
    Public Function GetExistingInstanceContext(message As Message, _
        channel As IContextChannel) As InstanceContext _
        Implements IInstanceContextProvider.GetExistingInstanceContext
    End Function
    
    Public Sub InitializeInstanceContext(instanceContext As InstanceContext, _
        message As Message, channel As IContextChannel) _
        Implements IInstanceContextProvider.InitializeInstanceContext
    End Sub
    
    Public Function IsIdle(instanceContext As InstanceContext) As Boolean _
       Implements IInstanceContextProvider.IsIdle
    End Function
    
    Public Sub NotifyIdle(callback As InstanceContextIdleCallback, _
       instanceContext As InstanceContext) _
      Implements IInstanceContextProvider.NotifyIdle
    End Sub
    
    // C#
    public InstanceContext GetExistingInstanceContext(Message message,
       IContextChannel channel) { }
    
    public void InitializeInstanceContext(InstanceContext instanceContext,
       Message message, IContextChannel channel) { }
    
    public bool IsIdle(InstanceContext instanceContext)
    {
        return false;
    }
    
    public void NotifyIdle(InstanceContextIdleCallback callback,
       InstanceContext instanceContext) { }

    In the GetExistingInstanceContext method, the first step is to retrieve the instance ID from the request.

  6. Add the following code to the GetExistingInstanceContext method.

    ' VB
    Dim headerIndex As Integer = message.Headers.FindHeader(headerName, _
        headerNamespace)
    
    Dim _instanceId As String = String.Empty
    If headerIndex <> -1 Then
       _instanceId = message.Headers.GetHeader(Of String)(headerIndex)
    End If
    
    // C#
    int headerIndex = message.Headers.FindHeader(headerName, headerNamespace);
    
    string instanceId = String.Empty;
    if (headerIndex != -1)
       instanceId = message.Headers.GetHeader<string>(headerIndex);
  7. If the request is associated with a session, the information about the instance will have been added as one of the extensions in the channel. If so, retrieve it. Add the following code below the newly added lines.

    ' VB
    Dim info As DemoContextInfo = Nothing
    Dim hasSession As Boolean = (channel.SessionId IsNot Nothing)
    If hasSession Then
       info = channel.Extensions.Find(Of DemoContextInfo)()
    End If
    
    // C#
    DemoContextInfo info = null;
    bool hasSession = (channel.SessionId != null);
    if (hasSession)
        info = channel.Extensions.Find<DemoContextInfo>();
  8. If the request has an instance ID associated with it, there might already be a context to use. If so, retrieve it from the dictionary. Otherwise, instantiate a new DemoContextInfo object and add it to the dictionary. Add the following code to the GetExistingInstanceContext method below the lines added in the previous step.

    ' VB
    Dim isNew As Boolean = False
    If String.IsNullOrEmpty(_instanceId) OrElse Not _
       contextMap.TryGetValue(_instanceId, info) Then
       info = New DemoContextInfo(_instanceId)
       isNew = True
       contextMap.Add(_instanceId, info)
       If hasSession Then
          channel.Extensions.Add(info)
       End If
    End If
    
    // C#
    bool isNew = false;
    if (String.IsNullOrEmpty(instanceId) ||
       ! contextMap.TryGetValue(instanceId, out info))
    {
       info = new DemoContextInfo(instanceId);
       isNew = true;
       contextMap.Add(instanceId, info);
       if (hasSession)
          channel.Extensions.Add(info);
    }

    At the end of the GetExistingInstanceContext method, the choice is to return a null/Nothing value (if there was no existing instance context) or return the instance context the provider found. In the latter case, information about the channel is added to the channels associated with the instance. This enables the instance to track the different channels with which it is operating.

  9. Add the following code at the bottom of the GetExistingInstanceContext method:

    ' VB
    If isNew Then
       Return Nothing
    Else
       Dim _instance As InstanceContext = info.Instance
       If hasSession Then
          _instance.IncomingChannels.Add(channel)
       End If
       Return _instance
    End If
    
    // C#
    if (isNew)
    {
       return null;
    }
    else
    {
       InstanceContext instanceContext = info.Instance;
       if (hasSession)
          instanceContext.IncomingChannels.Add(channel);
    
       return instanceContext;
    }

    In this interface, the other method of importance is InitializeInstanceContext. This method is called when the GetExistingInstanceContext returns null/Nothing and a new instance context has to be created. For this exercise, the code in this method will add the new instance to the dictionary of instances.

  10. To start, check whether there is an existing session because, if so, the instance is already associated with the channel through the Extensions collection. Add the following code to the beginning of the InitializeInstanceContext method:

    ' VB
    Dim info As DemoContextInfo = Nothing
    Dim hasSession As Boolean = (channel.SessionId IsNot Nothing)
    
    If hasSession Then
       instanceContext.IncomingChannels.Add(channel)
       info = channel.Extensions.Find(Of DemoContextInfo)()
    End If
    
    // C#
    DemoContextInfo info = null;
    bool hasSession = (channel.SessionId != null);
    
    if (hasSession)
    {
       instanceContext.IncomingChannels.Add(channel);
       info = channel.Extensions.Find<DemoContextInfo>();
    }
  11. If there is no existing session, get the instance ID from the headers in the request and see whether the ID can be found in the dictionary of previously used instances. Add the following else clause to the just-added if statement.

    ' VB
    Else
       Dim headerIndex As Integer = message.Headers.FindHeader(headerName, _
          headerNamespace)
       If headerIndex <> -1 Then
          Dim instanceId As String = _
              message.Headers.GetHeader(Of string)(headerIndex)
          If instanceId IsNot Nothing Then
             contextMap.TryGetValue(instanceId, info)
          End If
       End If
    
    // C#
    else
    {
       int headerIndex = message.Headers.FindHeader(headerName,
          headerNamespace);
       if (headerIndex != -1)
       {
          string instanceId = message.Headers.GetHeader<string>(headerIndex);
          if (instanceId != null)
             this.contextMap.TryGetValue(instanceId, out info);
       }
    }

    If, for any reason, the instance context was found, it must be added to the DemoContextInfo object that will be used to process the request.

  12. Add the following lines to the bottom of the InitializeInstanceContext method:

    ' VB
    If info IsNot Nothing Then
       Info.Instance = instanceContext
    End If
    
    // C#
    if (info != null)
       info.Instance = instanceContext;

    There are a number of ways to inject this functionality into the WCF pipeline. They are described in Chapter 9, “When Simple Is Not Sufficient,” in the discussion of the details surrounding the DispatchRuntime object. For this exercise, you create an attribute to decorate the implementation class. The file for the attribute already exists.

  13. In Solution Explorer, double-click the ShareableAttribute file.

    The class is already decorated with the IServiceBehavior interface. This requires the three methods in the class to be defined. To add the InstanceContextProvider, the only method that must have code is ApplyDispatchBehavior. In this method, every endpoint dispatcher on every channel will set the InstanceContextProvider property to a new instance of the DemoContextProvider class.

  14. Add the following code to the ApplyDispatchBehavior method:

    ' VB
    Dim extension As New DemoContextProvider()
    Dim dispatcherBase As ChannelDispatcherBase
    For Each dispatcherBase In serviceHostBase.ChannelDispatchers
       Dim dispatcher As ChannelDispatcher = TryCast(dispatcherBase, _
          ChannelDispatcher)
       Dim _endpointDispatcher As EndpointDispatcher
       For Each _endpointDispatcher in dispatcher.Endpoints
          _endpointDispatcher.DispatchRuntime.InstanceContextProvider = _
             extension
       Next
    Next
    
    // C#
    DemoContextProvider extension = new DemoContextProvider();
    foreach (ChannelDispatcherBase dispatcherBase in
       serviceHostBase.ChannelDispatchers)
    {
       ChannelDispatcher dispatcher = dispatcherBase as ChannelDispatcher;
       foreach (EndpointDispatcher endpointDispatcher in dispatcher.Endpoints)
       {
          endpointDispatcher.DispatchRuntime.InstanceContextProvider =
             extension;
       }
    }

    Now that the attribute has been created, the service’s implementation class must be decorated with it.

  15. First, in Solution Explorer, double-click SessionService.

  16. In the class declaration, add the Shareable attribute. When you’re finished, the class declaration should look like the following:

    ' VB
    <ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single)>_
    <Shareable> _
    Public Class SessionService
       Implements ISession
    
    // C#
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
    [Shareable]
    public class SessionService : ISession
  17. Before starting the demo, in Solution Explorer, double-click the Program.cs or Module1.vb file in TestClient.

    Notice that there is a loop that prompts for an instance ID. Within that loop, there is a using block for the proxy to the service. This means that the same session will not be used for each call and that the only way for the instances to be maintained is through the provider that you have just written.

  18. Ensure that TestClient is set to be the startup project, and then launch the application by pressing F5.

    You will prompted for an instance ID.

  19. Enter the instance ID of your choice (say, 123, to keep it simple).

    The returned message indicates that this method has been called once.

  20. Enter the same instance ID, and the instance has been called twice. Enter a different instance ID, and the counter restarts; if you later duplicate an earlier instance ID, you will see the previous counter incremented in the output on the console. When you have finished exercising the application, press Enter to terminate.

Lesson Summary

  • The instance mode determines the relationship between the client and the instance of the service’s implementation class.

  • Along with the standard modes, WCF also provides a provider model to determine the instance context that should be used to process a request.

  • PerCall is the default mode, and it maintains a one-to-one association between method calls and instances.

  • PerSession creates an instance for each client proxy whereas an instance mode of Single results in one instance handling every request.

Lesson Review

You can use the following questions to test your knowledge of the information in Lesson 1, “Instancing Modes”. The questions are also available on the companion CD if you prefer to review them in electronic form.

  1. You have created a WCF application by which the client communicates with the service, using the netTcpBinding. You would like to minimize any possible threading and synchronization issues in the service. Which instance mode should you use?

    1. Per call

    2. Per session

    3. Singleton

    4. Instance context provider

  2. You have created a WCF application by which the client communicates with the service, using the wsTcpBinding. A number of methods in the service retrieve a large quantity of relatively static data. You would like to minimize the processing time spent retrieving the data (and keep the data in a cache within the service object). Which instance mode should you use?

    1. Per call

    2. Per session

    3. Singleton

    4. Instance context provider