Modifying Behavior of WCF-Free Service Implementations

Modifying Behavior of WCF-Free Service Implementations

In my previous post, I explained how to implement a WCF service without referencing WCF. In simple cases, it works as described, but you may soon find yourself in a situation where you need to modify the behavior of the service when it's hosted by WCF.

However, you can't set these attributes on the service implementation itself, since it mustn't have a reference to System.ServiceModel.

In the rest of this post, I'll provide an example of how to achieve such results while still keeping WCF out of the service implementation.

Consider this service implementation:

publicclassStuffService : IStuffService

{

privatereadonlyFunc<IStuffCallbackService> createCallbackChannel_;

public StuffService(Func<IStuffCallbackService> callbackCreator)

{

if (callbackCreator == null)

{

thrownewArgumentNullException("callbackCreator");

}

this.createCallbackChannel_ = callbackCreator;

}

// IStuffService members here...

}

There are many cases (Dependency Injection is a common reason) where a a non-default constructor is desirable, so if you are wondering about this weird-looking Func<IStuffCallbackService> parameter, just consider that it might as well have been some other type of dependency. In a later post (edit 2008.06.29: available here), it will become apparent just why the constructor takes this specific input - for now, the important point is that the service implementation only has one, non-default constructor, and that it will throw if passed a null value.

Since WCF by default manages a service instance's lifetime, and the default behavior is to create a new instance per request, WCF requires the service to have a default constructor; that is, unless an IInstanceProvider is provided.

IInstanceProvider is defined in System.ServiceModel, so I can't implement this interface directly in the service library; instead, I create a new library that defines the WCF hosting behavior for the service. This library references both System.ServiceModel and the service library (the library that contains StuffService), so essentially, it's the library that sits on top of the dependency hierarchy and wires everything together.

In this library, I can implement IInstanceProvider via an IContractBehavior to provide new instances of StuffService:

publicclassStuffInstancingBehavior :

IContractBehavior, IInstanceProvider

{

#region IInstanceProvider Members

publicobject GetInstance(InstanceContext instanceContext,

Message message)

{

returnthis.GetInstance(instanceContext);

}

publicobject GetInstance(InstanceContext instanceContext)

{

returnnewStuffService(() =>

OperationContext.Current.

GetCallbackChannel<IStuffCallbackService>());

}

publicvoid ReleaseInstance(InstanceContext instanceContext,

object instance) { }

#endregion

#region IContractBehavior Members

publicvoid AddBindingParameters(ContractDescription description,

ServiceEndpoint endpoint,

BindingParameterCollection bindingParameters) { }

publicvoid ApplyClientBehavior(ContractDescription description,

ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }

publicvoid ApplyDispatchBehavior(ContractDescription description,

ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)

{

dispatchRuntime.InstanceProvider = this;

}

publicvoid Validate(ContractDescription description,

ServiceEndpoint endpoint) { }

#endregion

}

There are really only two lines of code of interest in this code listing: ApplyDispatchBehavior associates this IInstanceProvider implementation with the DispatchRuntime, and the GetInstance method creates the StuffService instance itself.

Notice that while we will not accept the use of OperationContext.Current in the service library, it's perfectly legal to use it here.

Applying an IEndpointBehavior can be done either via configuration or by programmatically adding it to the ServiceHost before opening it.

Since WCF will not be able to create an instance of StuffService without StuffInstancingBehavior, it's not really optional, so making it configurable doesn't make a whole lot of sense. This means that I should add it programmatically.

To support hosting in as wide a range of scenarios as possible, the best way to ensure that the ServiceHost is always created correctly (even when hosted in IIS) is by implementing a dedicated ServiceHostFactory (let's call it StuffServiceHostFactory). The CreateServiceHost method can be implemented as simply as this:

returnnewStuffServiceHost(baseAddresses);

Obviously, the real behavior configuration takes place in the StuffServiceHost class, which derives from ServiceHost. The entire implementation takes place in the constructor:

public StuffServiceHost(paramsUri[] baseAddresses)

: base(typeof(StuffService), baseAddresses)

{

foreach (ContractDescription cd in

this.ImplementedContracts.Values)

{

cd.Behaviors.Add(newStuffInstancingBehavior());

}

this.Description.Behaviors.Find<ServiceBehaviorAttribute>().

ConcurrencyMode = ConcurrencyMode.Reentrant;

}

It adds StuffInstancingBehavior to all contracts, which will cause WCF to use its implementation of IInstanceProvider each time it wants to create a new instance of the StuffService. It also modifies the service's ConcurrencyMode; again, why it does that will become apparent in a future post, but I just wanted to show how you can programmatically achieve the same result as decorating your service with the ServiceBehaviorAttribute, since you can't do that directly on StuffService because it's being implemented in a library that doesn't reference WCF.

If you want to host StuffService in IIS, you just need to specify StuffServiceHostFactory in the .svc file's Factory attribute; if you want to host StuffService in your own process, you can just create a new instance of StuffServiceHost directly (or use an instance of StuffServiceHostFactory to create it).

All this behavior modification is taking place without StuffService being aware of it at all, and you can concentrate on implementing the service in whatever way you prefer, while keeping it totally testable and WCF runtime services far away from it. That's a very clean separation of concerns in my book.