Dela via


Custom Lifetime

This sample demonstrates how to write a Windows Communication Foundation (WCF) extension to provide custom lifetime services for shared WCF service instances.

Note

The setup procedure and build instructions for this sample are located at the end of this topic.

Shared Instancing

WCF offers several instancing modes for your service instances. The Shared Instancing mode covered in this topic provides a way to share a service instance between multiple channels. Clients can either resolve the instance’s endpoint address locally or contact a factory method in the service to obtain the endpoint address of a running instance. Once it has the endpoint address, it can create a new channel and start communication. The following code snippet shows how a client application creates a new channel to an existing service instance.

// Create the first channel.
IEchoService proxy = channelFactory.CreateChannel();

// Resolve the instance.
EndpointAddress epa = ((IClientChannel)proxy).ResolveInstance();

// Create new channel factory with the endpoint address resolved by 
// previous statement.
ChannelFactory<IEchoService> channelFactory2 =
                new ChannelFactory<IEchoService>("echoservice",
                epa);

// Create the second channel to the same instance.
IEchoService proxy2 = channelFactory2.CreateChannel(); 

Unlike other instancing modes, the shared instancing mode has a unique way of releasing the service instances. When all the channels are closed for an instance, the service WCF runtime starts a timer. If nobody makes a connection before the timeout expires, WCF releases the instance and claims the resources. As a part of the teardown procedure WCF invokes the IsIdle method of all IShareableInstanceContextLifetime implementations before releasing the instance. If all of them return true the instance is released. Otherwise the IShareableInstanceContextLifetime implementation is responsible for notifying the Dispatcher of the idle state by using a callback method.

By default, the idle timeout value of InstanceContext is one minute. However this sample demonstrates how you can extend this using a custom extension.

Extending the InstanceContext

In WCF, InstanceContext is the link between the service instance and the Dispatcher. WCF allows you to extend this runtime component by adding new state or behavior by using its extensible object pattern. The extensible object pattern is used in WCF to either extend existing runtime classes with new functionality or to add new state features to an object. There are three interfaces in the extensible object pattern: IExtensibleObject<T>, IExtension<T>, and IExtensionCollection<T>.

The IExtensibleObject<T> interface is implemented by objects to allow extensions which customize their functionality.

The IExtension<T> interface is implemented by objects that can be extensions of classes of type T.

And finally, the IExtensionCollection<T> interface is a collection of IExtensions that allows for retrieving IExtensions by their type.

Therefore in order to extend the InstanceContext you must implement the IExtension interface. In this sample project the CustomLeaseExtension class contains this implementation.

class CustomLeaseExtension : IExtension<InstanceContext>
{
}

The IExtension interface has two methods Attach and Detach. As their names imply, these two methods are called when the runtime attaches and detaches the extension to an instance of the InstanceContext class. In this sample, the Attach method is used to keep track of the InstanceContext object that belongs to the current instance of the extension.

InstanceContext owner;

public void Attach(InstanceContext owner)
{
  this.owner = owner; 
}

In addition, you must add the necessary implementation to the extension to provide the extended lifetime support. Therefore the ICustomLease interface is declared with the desired methods and is implemented in the CustomLeaseExtension class.

interface ICustomLease
{
    bool IsIdle { get; }        
    InstanceContextIdleCallback Callback { get; set; }
}

class CustomLeaseExtension : IExtension<InstanceContext>, ICustomLease
{
}

When WCF invokes the IsIdle method in the IShareableInstanceContextLifetime implementation this call is routed to the IsIdle method of the CustomLeaseExtension. Then the CustomLeaseExtension checks its private state to see whether the InstanceContext is idle. If it is idle it returns true. Otherwise, it starts a timer for a specified amount of extended lifetime.

public bool IsIdle
{
  get
  {
    lock (thisLock)
    {
      if (isIdle)
      {
        return true;
      }
      else
      {
        StartTimer();
        return false;
      }
    }
  }
}

In the timer’s Elapsed event the callback function in the Dispatcher is called in order to start another clean up cycle.

void idleTimer_Elapsed(object sender, ElapsedEventArgs args)
{
    idleTimer.Stop();
    isIdle = true;  
    callback(owner);
}

There is no way to renew the running timer when a new message arrives for the instance being moved to the idle state.

The sample implements IShareableInstanceContextLifetime to intercept the calls to the IsIdle method and route them to the CustomLeaseExtension. The IShareableInstanceContextLifetime implementation is contained in CustomLifetimeLease class. The IsIdle method is invoked when WCF is about to release the service instance. However, there is only one instance of a particular ISharedSessionInstance implementation in the ServiceBehavior’s InstanceContextLifetimes collection. This means there is no way of knowing the InstanceContext being closed at the time WCF checks the IsIdle method. Therefore this sample uses thread locking to serialize requests to the IsIdle method.

Ee960156.Important(en-us,VS.100).gif Note:
Using thread locking is not a recommended approach because serialization can severely affect the performance of your application.

A private member variable is used in the CustomLeaseExtension class to track the IsIdle value. Each time the value of IShareableInstanceContextLifetime is retrieved the IsIdle private member is returned and reset to false. It is essential to set this value to false in order to make sure the Dispatcher calls the NotifyIdle method.

public bool IsIdle
{
    get 
    {
       lock (thisLock)
       {
           bool idleCopy = isIdle;
           isIdle = false;
           return idleCopy;
       }
    }
}

If the ISharedSessionLifetime.IsIdle property returns false the Dispatcher registers a callback function by using the NotifyIdle method. This method receives a reference to the InstanceContext being released. Therefore the sample code can query the ICustomLease type extension and check the ICustomLease.IsIdle property in the extended state.

public void NotifyIdle(InstanceContextIdleCallback callback, 
            InstanceContext instanceContext)
{
    lock (thisLock)
    {
       ICustomLease customLease =
           instanceContext.Extensions.Find<ICustomLease>();
       customLease.Callback = callback; 
       isIdle = customLease.IsIdle;
       if (isIdle)
       {
             callback(instanceContext);
       }
    } 
}

Before the ICustomLease.IsIdle property is checked the Callback property needs to be set as this is essential for CustomLeaseExtension to notify the Dispatcher when it becomes idle. If ICustomLease.IsIdle returns true, the isIdle private member is simply set in CustomLifetimeLease to true and calls the callback method. Because the code holds a lock, other threads cannot change the value of this private member. And the next time Dispatcher checks the ISharedSessionLifetime.IsIdle, it returns true and lets Dispatcher release the instance.

Now that the groundwork for the custom extension is completed, it has to be hooked up to the service model. To hook up the CustomLeaseExtension implementation to the InstanceContext, WCF provides the IInstanceContextInitializer interface to perform the bootstrapping of InstanceContext. In the sample, the CustomLeaseInitializer class implements this interface and adds an instance of CustomLeaseExtension to the Extensions collection from the only method initialization. This method is called by Dispatcher while initializing the InstanceContext.

public void Initialize(InstanceContext instanceContext, Message message)
{
  IExtension<InstanceContext> customLeaseExtension =
    new CustomLeaseExtension(timeout);
  instanceContext.Extensions.Add(customLeaseExtension);
}

Finally the IShareableInstanceContextLifetime and IInstanceContextInitializer implementations are hooked up to the service model by using the IServiceBehavior implementation. This implementation is placed in the CustomLeaseTimeAttribute class and it also derives from the Attribute base class to expose this behavior as an attribute. In the IServiceBehavior.ApplyBehavior method, instances of IInstanceContextInitializer and IShareableInstanceContextLifetime implementations are added to the InstanceContextLifetimes and InstanceContextInitializers collections of the System.ServiceModel.Dispatcher respectively.

public void ApplyBehavior(ServiceDescription description, 
           ServiceHostBase serviceHostBase, 
           Collection<DispatchBehavior> behaviors,
           Collection<BindingParameterCollection> parameters)
{
    CustomLifetimeLease customLease = new CustomLifetimeLease();
    CustomLeaseInitializer initializer = 
                new CustomLeaseInitializer(timeout);

    foreach (DispatchBehavior dispatchBehavior in behaviors)
    {
        dispatchBehavior.InstanceContextLifetimes.Add(customLease);
        dispatchBehavior.InstanceContextInitializers.Add(initializer);
    }
}

This behavior can be added to a sample service class by annotating it with the CustomLeaseTime attribute.

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Shareable)]
[CustomLeaseTime(Timeout = 20000)]
public class EchoService : IEchoService
{
  //…
}

When you run the sample, the operation requests and responses are displayed in both the service and client console windows. Press ENTER in each console window to shut down the service and client.

To set up, build, and run the sample

  1. Ensure that you have performed the One-Time Setup Procedure for the Windows Communication Foundation Samples.

  2. To build the C# or Visual Basic .NET edition of the solution, follow the instructions in Building the Windows Communication Foundation Samples.

  3. To run the sample in a single- or cross-machine configuration, follow the instructions in Running the Windows Communication Foundation Samples.

Ee960156.Important(en-us,VS.100).gif Note:
The samples may already be installed on your machine. Check for the following (default) directory before continuing.

<InstallDrive>:\WF_WCF_Samples

If this directory does not exist, go to Windows Communication Foundation (WCF) and Windows Workflow Foundation (WF) Samples for .NET Framework 4 to download all Windows Communication Foundation (WCF) and WF samples. This sample is located in the following directory.

<InstallDrive>:\WF_WCF_Samples\WCF\Extensibility\Instancing\Lifetime