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
- CallbackDebugElement (<callbackDebug>): Defines whether the client should respond with unknown exceptions as faults. It’s the client callback equivalent to the ServiceDebugElement.
- CallbackTimeoutsElement (<callbackTimeouts>): The extension element which adds an internal behavior responsible for defining the transaction timeout in callback channels.
- ClearBehaviorElement (<clear>): Special extension which removes all inherited behaviors.
- ClientCredentialsElement: (<clientCredentials>): Defines the client credentials as well as service authentication settings. The client equivalent to the ServiceCredentials, where the user can configure the client credentials as well as service authentication settings. Extension used to specify the credentials used to authenticate the client to a service.
- ClientViaElement (<clientVia>): Adds a ClientViaBehavior to the client endpoint to change the address used by the transport when sending messages.
- DataContractSerializerElement (<dataContractSerializer> or <dataContractSerializer>): The extension which defines properties for the serializers used in the WCF services or endpoints. This is the only out-of-the-box behavior extension which can be used both in endpoint and in service behaviors.
- DispatcherSynchronizationElement (<dispatcherSynchronization>, new in 4.0): Adds a DispatcherSynchronizationBehavior to service endpoints.
- RemoveBehaviorElement (<remove>): Special extension which removes a particular inherited service or endpoint behavior.
- ServiceAuthenticationElement (<serviceAuthenticationManager>): Defines the authentication behavior of the service, by providing an authentication manager to be called for incoming messages.
- ServiceAuthorizationElement (<serviceAuthorization>): Defines properties for handling authorization for incoming messages to the service.
- ServiceCredentialsElement (<serviceCredentials>): Defines the credentials used by the service, such as X.509 certificates.
- ServiceDebugElement (<serviceDebug>): Defines debugging features for the service, such as the service help page or a flag indicating whether to send unknown exception details to clients.
- ServiceMetadataPublishingElement (<serviceMetadata>): Exposes metadata about the service for tools such as svcutil.exe or Add Service Reference.
- ServiceSecurityAuditElement (<serviceSecurityAudit>): Defines the audit behavior of security events, such as authentication or authorization events.
- ServiceThrottlingElement (<serviceThrottling>): Defines throughput quotas to be applied to the service.
- ServiceTimeoutsElement (<serviceTimeouts>): The extension element which adds an internal behavior responsible for defining the transaction timeout in services.
- SynchronousReceiveElement (<synchronousReceive>): Defines that the channel listener in the endpoint must use a synchronous receive call, instead of the default asynchronous mode.
- TransactedBatchingElement (<transactedBatching>): Instructs the endpoint to optimize transfer operations for transports which supports transacted receives. Essentially, it enables the batching of messages for MSMQ services. This port from Nick Allen has a good description of the scenario.
- UseRequestHeadersForMetadataAddressElement (<useRequestHeadersForMetadataAddress>, new in 4.0): Used to determine that the machine names in metadata requests should use the address to which the request was made. Useful in scenarios where the server which is receiving the message is behind a load balancer – we want the endpoint addresses in the metadata to reflect the NLB’s address, not the individual node’s.
- WebHttpElement (<webHttp>, new in 3.5 *): Enables the WCF web programming model for this endpoint.
- WebScriptEnablingElement (<enableWebScript>, new in 3.5 *): Enables the endpoint to receive requests from an ASP.NET AJAX client. It provides a JavaScript proxy for the client to call the service in a strongly-typed way.
- EndpointDiscoveryElement (<endpointDiscovery>, new in 4.0 *): Controls the content of the discovery metadata returned by a discovery endpoint.
- ServiceDiscoveryElement (<serviceDiscovery>, new in 4.0 *): Defines that the all the endpoints of this service will be announced using the WS-Discovery protocol.
- RoutingExtensionElement (<routing>, new in 4.0 *): Defines the behavior for a service acting as a router.
- SoapProcessingExtensionElement (<soapProcessing>, new in 4.0 *): Defines rules for translating messages between different SOAP versions in a routing scenario.
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
- public abstract class BehaviorExtensionElement : ServiceModelExtensionElement
- {
- protected internal abstract object CreateBehavior();
- public abstract Type BehaviorType { get; }
- }
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).
- <system.serviceModel>
- <extensions>
- <behaviorExtensions>
- <add name="myLogger"
- type="InspectNonXmlMessages.IncomingMessageLoggerBehaviorExtension, InspectNonXmlMessages"/>
- </behaviorExtensions>
- </extensions>
- <behaviors>
- <endpointBehaviors>
- <behavior name="NonSoapInspector">
- <webHttp/>
- <myLogger logFolder="d:\temp" />
- </behavior>
- </endpointBehaviors>
- </behaviors>
- </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.
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.
- public class IncomingMessageLogger : IDispatchMessageInspector, IEndpointBehavior
- {
- private const string DefaultMessageLogFolder = @"c:\temp\";
- private static int messageLogFileIndex = 0;
- private string messageLogFolder;
- public IncomingMessageLogger() : this(DefaultMessageLogFolder) { }
- public IncomingMessageLogger(string messageLogFolder)
- {
- this.messageLogFolder = messageLogFolder;
- }
- public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
- {
- string messageFileName = Path.Combine(this.messageLogFolder, string.Format("Log{0:000}_Incoming.txt", Interlocked.Increment(ref messageLogFileIndex)));
- // rest of the method ommitted...
- }
- public void BeforeSendReply(ref Message reply, object correlationState)
- {
- string messageFileName = Path.Combine(this.messageLogFolder, string.Format("Log{0:000}_Outgoing.txt", Interlocked.Increment(ref messageLogFileIndex)));
- // rest of the method ommitted...
- }
- }
Next we can add the behavior extension. The implementation is quite trivial, nothing interesting here.
- public class IncomingMessageLoggerBehaviorExtension : BehaviorExtensionElement
- {
- const string MyPropertyName = "logFolder";
- public override Type BehaviorType
- {
- get { return typeof(IncomingMessageLogger); }
- }
- [ConfigurationProperty(MyPropertyName)]
- public string LogFolder
- {
- get
- {
- return (string)base[MyPropertyName];
- }
- set
- {
- base[MyPropertyName] = value;
- }
- }
- protected override object CreateBehavior()
- {
- return new IncomingMessageLogger(this.LogFolder);
- }
- }
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).
- <system.serviceModel>
- <extensions>
- <behaviorExtensions>
- <add name="myLogger"
- type="InspectNonXmlMessages.IncomingMessageLoggerBehaviorExtension, InspectNonXmlMessages"/>
- </behaviorExtensions>
- </extensions>
- <behaviors>
- <endpointBehaviors>
- <behavior name="NonSoapInspector">
- <webHttp/>
- <myLogger logFolder="d:\temp" />
- </behavior>
- </endpointBehaviors>
- </behaviors>
- <services>
- <service name="InspectNonXmlMessages.ContactManagerService">
- <endpoint address=""
- behaviorConfiguration="NonSoapInspector"
- binding="webHttpBinding"
- contract="InspectNonXmlMessages.IContactManager" />
- </service>
- </services>
- </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.