WCF Extensibility – Behavior configuration extensions

This post is part of a series about WCF extensibility points. For a list of all previous posts and planned future ones, go to the index page .

Despite my personal displeasure with configuration, I understand that there are situations where one would want to split the coding of a WCF feature with its deployment. Sometimes there are indeed two different sets of people who will deal with development and deployment and it makes sense to let the latter choose which behaviors to apply to the services / endpoints in an (arguably) simple way. Also, many people do prefer to have a declarative way to define the behaviors in services, and the XML configuration file is the common way (some people really like this option, even asking for support for configuration-based extensibility in Silverlight, where it’s not supported). For those scenarios, WCF allows the developer of a service or endpoint behavior to create a BehaviorExtensionElement to be able to plug such behavior in the application configuration file.

The same class (BehaviorExtensionElement) is used for both service and endpoint behaviors, and the location in the <system.serviceModel/behaviors> element in configuration will determine the type of behavior which an extension can provide. The behavior extension itself needs to be registered for WCF to know where to look for its code.

Public implementations in WCF

The items marked with (*) are added to the list of predefined extensions in the machine configuration file for the .NET Framework. The others are baked in the service model code.

Class declaration

  1. public abstract class BehaviorExtensionElement : ServiceModelExtensionElement
  2. {
  3.     protected internal abstract object CreateBehavior();
  4.     public abstract Type BehaviorType { get; }
  5. }

The two members which are required to be overridden in subclasses of BehaviorExtensionElement are BehaviorType and CreateBehavior. The former is a property which returns the type of the behavior (service or endpoint) which this extension can create – that’s how the WCF config loader will find out whether an element associated with the extension is valid for endpoint or service behaviors. The latter is invoked to actually create an instance of the behavior to be added to the endpoint or service.

Notice that the parent class of BehaviorExtensionElement, ServiceModelExtensionElement, is itself a subclass of ConfigurationElement, the common base class for .NET configuration extensions. So you can use all the functionality of the configuration support in .NET, including defining properties and tagging them with [ConfigurationProperty] to have them automatically parsed from the configuration, defining validation rules for the property values or even defining default values for the properties – all inherited from the configuration support in .NET.

How to add behavior extensions

Custom behavior extensions are added to the <system.serviceModel / extensions / behaviorExtensions> element, and given an alias so that they can be used in actual service / endpoint behaviors. They can be added both in a global location (i.e., machine.config in the \Windows\Microsoft.NET\Framework\<version>\Config directory) which will make their registered alias available for any process in the computer, or in more localized places, such as the global web.config (for IIS-hosted services) or even in the application’s configuration (app.config for stand-alone projects; web.config for IIS-hosted ones). The registration consists of adding a child to the extensions element with the behavior alias, and it’s assembly-qualified name of the behavior extension type. Notice that in order for the extension to be used, the .NET runtime must be able to resolve the type name, so unless the assembly with the behavior extension will be placed in the GAC, registering it on the machine.config is a bad idea (registering anything in machine.config is almost always a bad idea anyway, but that’s another story).

  1. <system.serviceModel>
  2.   <extensions>
  3.     <behaviorExtensions>
  4.       <add name="myLogger"
  5.            type="InspectNonXmlMessages.IncomingMessageLoggerBehaviorExtension, InspectNonXmlMessages"/>
  6.     </behaviorExtensions>
  7.   </extensions>
  8.   <behaviors>
  9.     <endpointBehaviors>
  10.       <behavior name="NonSoapInspector">
  11.         <webHttp/>
  12.         <myLogger logFolder="d:\temp" />
  13.       </behavior>
  14.     </endpointBehaviors>
  15.   </behaviors>
  16. </system.serviceModel>

The config above registers the behavior extension element with type “InspectNonXmlMessages.IncomingMessageLoggerBehaviorExtension”, from the assembly InspectNonXmlMessages, and gives it the alias “myLogger”. That element then can be used in the set of behaviors which create the endpoint behavior used by the service.

One note about extensions and Visual Studio: when we use behavior extensions, VS will usually issue a warning about a schema violation, and tag the extension with a squiggly line (see below). The warning states that it is not a valid child for the <behavior> element: “The element 'behavior' has invalid child element 'myLogger'. List of possible elements expected: 'clientVia, callbackDebug, callbackTimeouts, clear, clientCredentials, transactedBatching, dataContractSerializer, dispatcherSynchronization, remove, synchronousReceive, enableWebScript, webHttp, endpointDiscovery, soapProcessing'.” This is just a nuisance, as this error can be safely ignored and won’t cause any problems during runtime. But if you’re someone who gets bugged by warnings (or has a setting in the project to treat all warnings as errors, you can update the configuration schema in Visual Studio at \Program Files\Microsoft Visual Studio 10.0\Xml\Schemas\DotNetSchema.xsd (replace Program Files with Program Files (x86) for 64-bit OS, and replace 10.0 with the appropriate VS version) and update the schema to allow for this new element as well.

ExtensionSquigglyLine

Real world example: updating the REST message inspector to be configurable via config

There isn’t much that can be done with behavior extensions – they simply add a behavior to the service / endpoint description (which in turn add runtime elements to the runtime), so I’ll simply post an update version of the REST message inspector (the example with the most number of downloads in the code gallery so far). The behavior in that sample had a hardcoded path for the log files, so I’ll make it configurable to show how to add parameters to config extensions.

  1. public class IncomingMessageLogger : IDispatchMessageInspector, IEndpointBehavior
  2. {
  3.     private const string DefaultMessageLogFolder = @"c:\temp\";
  4.     private static int messageLogFileIndex = 0;
  5.     private string messageLogFolder;
  6.  
  7.     public IncomingMessageLogger() : this(DefaultMessageLogFolder) { }
  8.  
  9.     public IncomingMessageLogger(string messageLogFolder)
  10.     {
  11.         this.messageLogFolder = messageLogFolder;
  12.     }
  13.  
  14.     public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
  15.     {
  16.         string messageFileName = Path.Combine(this.messageLogFolder, string.Format("Log{0:000}_Incoming.txt", Interlocked.Increment(ref messageLogFileIndex)));
  17.         // rest of the method ommitted...
  18.     }
  19.  
  20.     public void BeforeSendReply(ref Message reply, object correlationState)
  21.     {
  22.         string messageFileName = Path.Combine(this.messageLogFolder, string.Format("Log{0:000}_Outgoing.txt", Interlocked.Increment(ref messageLogFileIndex)));
  23.         // rest of the method ommitted...
  24.     }
  25. }

Next we can add the behavior extension. The implementation is quite trivial, nothing interesting here.

  1. public class IncomingMessageLoggerBehaviorExtension : BehaviorExtensionElement
  2. {
  3.     const string MyPropertyName = "logFolder";
  4.  
  5.     public override Type BehaviorType
  6.     {
  7.         get { return typeof(IncomingMessageLogger); }
  8.     }
  9.  
  10.     [ConfigurationProperty(MyPropertyName)]
  11.     public string LogFolder
  12.     {
  13.         get
  14.         {
  15.             return (string)base[MyPropertyName];
  16.         }
  17.         set
  18.         {
  19.             base[MyPropertyName] = value;
  20.         }
  21.     }
  22.  
  23.     protected override object CreateBehavior()
  24.     {
  25.         return new IncomingMessageLogger(this.LogFolder);
  26.     }
  27. }

And finally we can hook it up to the configuration (and remove the imperative code in the Program.cs to add the endpoint and configure the behaviors).

  1. <system.serviceModel>
  2.   <extensions>
  3.     <behaviorExtensions>
  4.       <add name="myLogger"
  5.            type="InspectNonXmlMessages.IncomingMessageLoggerBehaviorExtension, InspectNonXmlMessages"/>
  6.     </behaviorExtensions>
  7.   </extensions>
  8.   <behaviors>
  9.     <endpointBehaviors>
  10.       <behavior name="NonSoapInspector">
  11.         <webHttp/>
  12.         <myLogger logFolder="d:\temp" />
  13.       </behavior>
  14.     </endpointBehaviors>
  15.   </behaviors>
  16.   <services>
  17.     <service name="InspectNonXmlMessages.ContactManagerService">
  18.       <endpoint address=""
  19.                 behaviorConfiguration="NonSoapInspector"
  20.                 binding="webHttpBinding"
  21.                 contract="InspectNonXmlMessages.IContactManager" />
  22.     </service>
  23.   </services>
  24. </system.serviceModel>

Unless anyone requests it, I won’t create a page in the MSDN Code Gallery for this one, since it’s pretty much the same as the one it’s being modified.

[Back to the index]