Changing WCF Service Implementation at Runtime

In this paper, I will show an approach that lets WCF to have two implementations for a WCF service. The proposed approach provides the ability to switch among the service implementations at runtime.

Introduction

Last week, I work on a service oriented project that has two types of clients. Both of the types should be able to access the services on the server and both of the types should see the same interface on the server, but the services on the server should provide basic functionality for the clients in the first group (majority of the clients) and they should provide the complete functionality for the clients in the second group, (minority of the clients). Base on the logic behind the business, some of the clients in the first group have been upgraded to the second group and some time, some of the clients in the second group have been moved to the first group.

The simple solution that came to mind is putting an if-else in each method of the services like this:

If (server should do basic functionality)

{

basic functionality.

}

Else

{

Complete functionality.

}

However this solution is not convincing. The problem is the fact that the codes of the basic and complete implementations have been tight together strongly. The ideal is having two different implementations; a basic and complete implementations. Based on the Admin decision the server will use the basic or complete implementations. In the time that I seek for a solution, I find that the idea of having two implementations per WCF service can be useful in many other situations. Sometimes you want to change the implementation of your services but you want to keep the old services in case the new service gets problem. Sometimes your services interact with internet resources, but you want to have a local implementation of your services in case of losing internet connection. There are other situations that the idea would be useful too.

At first glance, I thought it doesn't possible to have two implementations for each service, but thanks to the great WCF extensibility. WCF is a fantastic platform to build service-oriented applications in the .NET platform. One of the facts about the WCF is its ability to extend its functionality. Every part of the platform can be extended, and most of the default behaviors can be replaced. The problem is how we can have two implementations for each service. Studying the WCF concepts, I realized that the solution is related to a custom IInstanceProviders. In fact, using a custom InstanceProvider, a custom ServiceBehavior, and a new ServiceHostExtention, the whole scenario comes to life. Before delving into the detail, I will show you how to use the solution.

Using the Code

Like any other WCF service, the first step is defining the interface of the WCF service.

[ServiceContract]

public interface ITestService

{

[OperationContract]

string DoWork();

[OperationContract]

void SwichService();

}

The DoWork method returns a string that contains the name of the service and the SwitchService method switches the service from one implementation to another. Here are two different implementations of the above interface.

public class TestService : ITestService

{

public string DoWork()

{

return "This is complete service";

}

void ITestService.SwichService()

{

SwichEnabledExtension extension = OperationContext.Current.Host.Extensions.FirstOrDefault(c => c is SwichEnabledExtension) as SwichEnabledExtension;

extension.UseBasicService = !extension.UseBasicService;

OperationContext.Current.InstanceContext.ReleaseServiceInstance();

}

}

public class BasicTestService : ITestService

{

string ITestService.DoWork()

{

return "This is Basic Service";

}

void ITestService.SwichService()

{

SwichEnabledExtension extension = OperationContext.Current.Host.Extensions.FirstOrDefault(c => c is SwichEnabledExtension) as SwichEnabledExtension;

extension.UseBasicService = !extension.UseBasicService;

OperationContext.Current.InstanceContext.ReleaseServiceInstance();

}

}

In order to create the services, a custom implementation of the IServiceFactory should be implemented as follows:

public class TestServiceFactory : IServiceFactory

{

object IServiceFactory.CreateBasicService()

{

return new BasicTestService();

}

object IServiceFactory.CreateCompleteService()

{

return new TestService();

}

}

The responsibility of the factory is providing an interface for creating the service implementations. The final step is adding a SwichServiceAttribute to the main implementation of the service as follows:

[SwichService(typeof(TestServiceFactory), false)]

public class TestService : ITestService

{

….

}

Now, the service has the capability to switch between its implementations. By calling the SwitchService method, the implementation of the service has been switched. For example, consider the following sample that is run on the client.

ICP.WCF.SwichService.TestServiceClient client = new ICP.WCF.SwichService.TestServiceClient();

Console.WriteLine(client.DoWork());

client.SwichService();

Console.WriteLine(client.DoWork());

The result in the Console environment is as follows:

This is complete service

This is Basic Service

As you can see, the first call of the client.DoWork has fired the method of the TestService class, but the second call of it fires the method of the BasicTestService. It is the result of calling the SwitchService between the two calls. I will demonstrate the code of the SwichtService later.

The Structure of the Solution

The following class diagram displays the classes of the solution.

In the above diagram, there are a custom IInstanceProvider, a ServiceBehavior and a ServiceHostExtention. The custom IInstanceProvider has been used to create service instances from the appropriate implementation. The ServiceHostExtention is used by the developers to announce the IInstanceProvider to switch from one implementation to another and the ServiceBehavior has been used to tight all of the things together. In the following sections I demonstrate the detail of each part.

Custom IInstanceProvider

WCF infrastructure uses IInstanceProvider to create and release instances of WCF services whenever it is necessary. Here is the code of the IInstanceProvider.

// Summary:

// Declares methods that provide a service object or recycle a service object

// for a service.

public interface IInstanceProvider

{

// Summary:

// Returns a service object given the specified System.ServiceModel.InstanceContext

// object.

//

// Parameters:

// instanceContext:

// The current System.ServiceModel.InstanceContext object.

//

// Returns:

// A user-defined service object.

object GetInstance(InstanceContext instanceContext);

//

// Summary:

// Returns a service object given the specified System.ServiceModel.InstanceContext

// object.

//

// Parameters:

// instanceContext:

// The current System.ServiceModel.InstanceContext object.

//

// message:

// The message that triggered the creation of a service object.

//

// Returns:

// The service object.

object GetInstance(InstanceContext instanceContext, Message message);

//

// Summary:

// Called when an System.ServiceModel.InstanceContext object recycles a service

// object.

//

// Parameters:

// instanceContext:

// The service's instance context.

//

// instance:

// The service object to be recycled.

void ReleaseInstance(InstanceContext instanceContext, object instance);

}

WCF has a default IInstanceProvider that is responsible for creating and releasing the services. Every EndpointDispatcher has an instance of IInstanceProvider. Whenever a Message has been received from a client to an Endpoint, one of the GetInstance methods of the IInstanceProvider will be called to resolve the service instance. By substituting the default IInstanceProvider of the Endpoints with a custom IInstanceProvider, one can control the creation and releasing of WCF services. It is especially useful in situations where there are lots of clients and there is a fear that the resources on the server will be finished. In such scenarios, a custom IInstanceProvider that applies some policies on creation and releasing the services would be quite useful. In our case, we use a custom IInstanceProvider that can create two different types per service. Here is its code:

public class SwitchEnabledInstanceProvider : IInstanceProvider

{

private IServiceFactory ServiceFactory;

public SwitchEnabledInstanceProvider(IServiceFactory serviceFactory)

{

this.ServiceFactory = serviceFactory;

}

#region IInstanceProvider Members

public object GetInstance(InstanceContext instanceContext, System.ServiceModel.Channels.Message message)

{

var extension = instanceContext.Host.Extensions.Find<SwichEnabledExtension>();

if (extension.UseBasicService)

{

return ServiceFactory.CreateBasicService();

}

else

{

return ServiceFactory.CreateCompleteService();

}

}

public object GetInstance(InstanceContext instanceContext)

{

var extension = instanceContext.Host.Extensions.Find<SwichEnabledExtension>();

if (extension.UseBasicService)

{

return ServiceFactory.CreateBasicService();

}

else

{

return ServiceFactory.CreateCompleteService();

}

}

public void ReleaseInstance(InstanceContext instanceContext, object instance)

{

instance = null;

}

#endregion

}

As can be seen in the code, the IInstanceProvider gets an IServiceFactory in its constructor. The ServiceFactory has two methods for creating the services; the first one creates an instance from the basic implementation and the second one creates an instance from the complete implementation. Here is the code of the IServiceFactory.

public interface IServiceFactory

{

object CreateBasicService();

object CreateCompleteService();

}

The other thing that needs attention is communication between the user code and the IInstanceProvider. As can be seen in the code of the SwichEnabledInstanceProvider, there is a ServiceHost extention called SwichEnabledExtension. As you can see, the IInstanceProvider uses its UseBasicService to decide using which implementation of the service. The SwichEnabledExtension class can be accessed in the code of the services to announce the InstanceProvider to change the service implementation. Here is the code of SwichEnabledExtension class.

public class SwichEnabledExtension : IExtension<ServiceHostBase>

{

public bool UseBasicService = false;

public void Attach(ServiceHostBase owner)

{

}

public void Detach(ServiceHostBase owner)

{

}

}

As can be seen, the extension code is almost empty. It has just one Boolean property. In the code of services, the Extension can be used as follows:

SwichEnabledExtension extension = OperationContext.Current.Host.Extensions.FirstOrDefault(c => c is SwichEnabledExtension) as SwichEnabledExtension;

extension.UseBasicService = !extension.UseBasicService;

OperationContext.Current.InstanceContext.ReleaseServiceInstance();

The above code gets the extension from the list of the Host extensions, then switches its flag and finally releases the current service. By releasing the current service, the SwichEnabledInstanceProvider uses the other implementation of the service in the next call of the service.

SwichServiceAttribute

Finally we reach the SwitchServiceAttribute class. The role of this Behavior is attaching the SwichEnabledExtension to the ServiceHost and substituting the default InstanceProvider by our custom InstanceProvider. Here is its code:

public class SwitchServiceAttribute : Attribute, IServiceBehavior

{

public IServiceFactory Factory { get; set; }

public bool UseBasicService { get; set; }

public SwitchServiceAttribute(Type factoryType, bool useBasicService)

{

Factory = (IServiceFactory)Activator.CreateInstance(factoryType);

UseBasicService = useBasicService;

}

#region IServiceBehavior Members

void IServiceBehavior.AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)

{

}

void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)

{

SwitchEnabledInstanceProvider instanceProvider = new SwitchEnabledInstanceProvider(Factory);

SwichEnabledExtension extension = new SwichEnabledExtension();

serviceHostBase.Extensions.Add(extension);

foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)

{

ChannelDispatcher cd = cdb as ChannelDispatcher;

if (cd != null)

{

foreach (EndpointDispatcher ed in cd.Endpoints.Where(c => description.Endpoints.Count(d => d.Address == c.EndpointAddress) > 0))

{

// Assign it to DispatchBehavior in each endpoint.

ed.DispatchRuntime.InstanceProvider = instanceProvider;

}

}

}

}

void IServiceBehavior.Validate(ServiceDescription description, ServiceHostBase serviceHostBase)

{

}

#endregion

}

The code of the above classes, plus a demo can be downloaded from here. You need VS 2010 to run the sample.

By Siyamand Ayubi   Popularity  (4116 Views)