Dynamically set InstanceContextMode for a WCF Service from app.config or web.config

Datetime:2016-08-23 04:33:10          Topic: WCF           Share

WCF offers a lot of very powerful configuration and extensibility options – sometimes it becomes a bit dizzying.

I recently had a requirement to design a WCF service that could potentially consume many system resources (particularly, RAM) in some client scenarios. Any single call will be manageable, and concurrent calls would be manageable in some environments (but not others – the service is required to be able to process large files in a single call, but can only handle so much before running out of memory). Obviously, it would be good to find ways to split up calls to the service or for the client to sequence them, but WCF offers a very simple configuration attribute to specify that the service should run as a Singleton, processing only one call at a time:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class LargeFileProcessingService : ILargeFileProcessingService
{
    ...
}

Problem solved, right? Now what if a particular environment will only be handling smaller files, and needs to process them quickly? We lose the multi-threaded processing power that WCF would be able to offer there. Unfortunately, InstanceContextMode is not exposed out of the box as a property that can be changed in app.config or web.config. However, a couple classes can allow us to do just that. This class will allow you to deploy your services and configure them to either be running in a “single threaded” mode (queuing subsequent requests until the current request is processed or they time out) or “multi-threaded” (allowing multiple instances to run concurrently). This works much like BizTalk’s “ordered delivery” option on Send Ports, and like that option it can be configured at any time and initialized in the instance by restarting it.

First, an implementer of IServiceBehavior :

namespace Tallan.WCF
{
    public class InstanceContextServiceBehavior : IServiceBehavior
    {
        InstanceContextMode _contextMode = default(InstanceContextMode);

        public InstanceContextServiceBehavior(string contextMode)
        {
            if (!string.IsNullOrWhiteSpace(contextMode))
            {
                InstanceContextMode mode;

                if (Enum.TryParse(contextMode, true, out mode))
                {
                    _contextMode = mode;
                }
                else
                {
                    throw new ArgumentException($"'{contextMode}' Could not be parsed as a valid InstanceContextMode; allowed values are 'PerSession', 'PerCall', 'Single'", "contextMode");
                }
            }
        }

        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {
            var behavior = serviceDescription.Behaviors.Find<ServiceBehaviorAttribute>();
            behavior.InstanceContextMode = _contextMode;
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            return;
        }

        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            return;
        }
    }
}

And then the BehaviorExtensionElement implementation (so we can set this from web or app.config):

namespace Tallan.WCF
{
    public class InstanceContextExtensionElement : BehaviorExtensionElement
    {
        public override Type BehaviorType
        {
            get
            {
                return typeof(InstanceContextServiceBehavior);
            }
        }

        protected override object CreateBehavior()
        {
            return new InstanceContextServiceBehavior(ContextMode);
        }

        const object contextMode = null;

        [ConfigurationProperty(nameof(contextMode))]
        public string ContextMode
        {
            get
            {
                return (string)base[nameof(contextMode)];
            }
            set
            {
                base[nameof(contextMode)] = value;
            }
        }
    }
}

And viola! You can now add this to your app.config:

<system.serviceModel>
    <services>
      <service name="Tallan.WCF.LargeFileProcessingService" behaviorConfiguration="Default">
        <endpoint address="" behaviorConfiguration="webBehavior" binding="webHttpBinding" bindingConfiguration="largeWebHttpBinding" contract="TctepConnector.WCF.IX12CoreProcessingService" />
      </service>
    </services>
    <bindings>
      <webHttpBinding>
        <!-- allow large requests, set generous timeouts -->
        <binding name="largeWebHttpBinding"
                 closeTimeout="01:00:00"
                 openTimeout="01:00:00"
                 receiveTimeout="01:00:00"
                 sendTimeout="01:00:00"
                 allowCookies="false"
                 bypassProxyOnLocal="false"
                 hostNameComparisonMode="StrongWildcard"
                 maxBufferSize="2147483647"
                 maxBufferPoolSize="2147483647"
                 maxReceivedMessageSize="2147483647"
                 transferMode="Streamed"
                 useDefaultWebProxy="true">
          <readerQuotas maxDepth="32" maxStringContentLength="524288" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <!--<security mode="Transport" />-->
        </binding>
      </webHttpBinding>
    </bindings>
    <extensions>
      <behaviorExtensions>
        <add name="instanceContext" type="Tallan.WCF.InstanceContextExtensionElement, Tallan.WCF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=KEY TOKEN HERE"/>
      </behaviorExtensions>
    </extensions>
    ...
    <behaviors>
      <serviceBehaviors>
        <behavior name="Default">
          <!-- valid values for contextMode are: "Single", "PerCall", "PerSession" -->
          <!-- This will override any declared attributes on the service -->
          <instanceContext contextMode="Single"/>
          <serviceMetadata httpGetEnabled="True" httpsGetEnabled="True"/>
          <!-- To receive exception details in faults for debugging purposes,
          set the value below to true.  Set to false before deployment
          to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    ...
</system.serviceModel>

Obviously, this kind of service might not work very well over the internet, but could offer a lot of interoperability over an intranet while still allowing for multiple configuration options with regard to singleton or not.





About List