次の方法で共有


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.

Perhaps you need to control the service's ConcurrencyMode, or perhaps you need to set UseSynchronizationContext. These options are typically controlled by the ServiceBehaviorAttribute.

You may also want to provide an IInstanceProvider via a custom attribute that implements IContractBehavior.

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:

 public class StuffService : IStuffService
 {
     private readonly Func<IStuffCallbackService> createCallbackChannel_;
  
     public StuffService(Func<IStuffCallbackService> callbackCreator)
     {
         if (callbackCreator == null)
         {
             throw new ArgumentNullException("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:

 public class StuffInstancingBehavior :
     IContractBehavior, IInstanceProvider
 {
     #region IInstanceProvider Members
  
     public object GetInstance(InstanceContext instanceContext,
         Message message)
     {
         return this.GetInstance(instanceContext);
     }
  
     public object GetInstance(InstanceContext instanceContext)
     {
         return new StuffService(() =>
             OperationContext.Current.
             GetCallbackChannel<IStuffCallbackService>());
     }
  
     public void ReleaseInstance(InstanceContext instanceContext,
         object instance) { }
  
     #endregion
  
     #region IContractBehavior Members
  
     public void AddBindingParameters(ContractDescription description,
         ServiceEndpoint endpoint,
         BindingParameterCollection bindingParameters) { }
  
     public void ApplyClientBehavior(ContractDescription description,
         ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
  
     public void ApplyDispatchBehavior(ContractDescription description,
         ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
     {
         dispatchRuntime.InstanceProvider = this;
     }
  
     public void 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:

 return new StuffServiceHost(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(params Uri[] baseAddresses)
     : base(typeof(StuffService), baseAddresses)
 {
     foreach (ContractDescription cd in
         this.ImplementedContracts.Values)
     {
         cd.Behaviors.Add(new StuffInstancingBehavior());
     }
  
     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.

Update (2008-07-12): Sample code is available here.

Comments

  • Anonymous
    June 28, 2008
    One of my readers recently asked me about unit testing WCF services when they have callbacks. Given that

  • Anonymous
    July 08, 2008
    In my previous post , I explained how to unit test a WCF service with callbacks. Since the scenario involves