I have a customer who contacted me regarding a WCF service that is currently configured to have a 'Single' InstanceContextMode and during testing they have noticed that there is only ever one concurrent thread. Now this is by design as the default setting for the Concurrency is 'Single'. Ideally it would need to be set as 'Multiple' for more than one operation to be handled at a time.
Now, the customer is too close to their delivery date to recompile their code at this stage and wants to see the different options that WCF offers them within configuration. However, the InstanceContextMode and ConcurrencyMode attributes are normally set declaratively within the ServiceBehaviour attribute in the code behind. That would mean a recompilation of the service. This post is about changing this behaviour programmatically as an extension in a separate assembly.
In order to test this I wanted to create a behaviour that prevents the service from starting if it is set to 'Single' Concurrency.
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
public class MultipleConcurrencyServiceValidator : IServiceBehavior
{
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
var behaviour = serviceDescription.Behaviors.Find<ServiceBehaviorAttribute>();
if (behaviour.ConcurrencyMode == ConcurrencyMode.Single)
{
throw new InvalidOperationException("ConcurrencyMode set at Single - should be Multiple.");
}
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
Ok, so the main points to note in this class are that we are able to interrogate the service description and retrieve the ServiceBehaviorAttribute and check what value it has been set to. But, we are not able to change any of its values, if we were to place some code in the ApplyDispatchBehavior and set the ConcurrencyMode it would not take effect as the service has already been started. Let's link this class to our service behaviour within our config file
<behaviors>
<serviceBehaviors>
<behavior name="WCFConcurrency35.Service1Behavior">
<multipleConcurrencyValidator />
</behavior>
</serviceBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="multipleConcurrencyValidator" type="WcfExtensions.Extensions.MultipleConcurrencyServiceValidator, Wcf.Extensions" />
</behaviorExtensions>
</extensions>
We add the type reference in and give it a name and then just add this element into the serviceBehaviour. In order to test it, we simply add the following line to our service and browse to it.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
public class Service1 : IService1
Now with our new class hooked up if we were to browse to it, we would get our exception thrown. So next let's create some code that will change the concurrency mode and get this to work again. So normally, we should create our own ServiceHost but this is just a quick example to change a behaviour that is not exposed by the framework. Let's create a service host factory class.
using System;
using System.ServiceModel;
using System.ServiceModel.Activation;
public class ConcurrentServiceHostFactory : ServiceHostFactory
{
public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
var serviceHost = base.CreateServiceHost(constructorString, baseAddresses);
serviceHost.Description.Behaviors.Find<ServiceBehaviorAttribute>().ConcurrencyMode = ConcurrencyMode.Multiple;
return serviceHost;
}
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var serviceHost = base.CreateServiceHost(serviceType, baseAddresses);
serviceHost.Description.Behaviors.Find<ServiceBehaviorAttribute>().ConcurrencyMode = ConcurrencyMode.Multiple;
return serviceHost;
}
}
This is just simple code to create our service host, change the service behaviour and return it as normal. Next, we need to get our service to be created with this factory and that is simple too; edit the svc file like so
<%@ ServiceHost Factory="Wcf.Extensions.ConcurrentServiceHostFactory" />
So now, we have that all tested, all I have to do to change my concurrency on an already compiled application is to add a simple Factory line into the markup of the svc file.