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.
Comments
- Anonymous
February 01, 2012
Hi Carlos,I have a similar implementation where I'm adding a endpoint behaviour to a web service (webHttpBinding).What's happening is that all works fine when self hosting the service, but when I host the service on IIS, the methods AfterReceiveRequest and BeforeSendReply of my implementation of IEndpointBehavior are never called. Do you know any similar issue?Many thanks. - Anonymous
February 01, 2012
Hi christiano. Behaviors should work fine on IIS as well; if you add breakpoints to the endpoint behavior (ApplyDispatchBehavior), are they hit? Also, make sure that you have the fully-qualified name of the service class in the <service> element in web.config, otherwise any configuration you have for that class will not be picked up. - Anonymous
February 01, 2012
Hi Carlos, thanks for your quick reply.I'm sending the requests through a web browser and have logs inside the class that implements the IDispatchMessageInspector interface. When hosting the service in a console app, the constructor, AfterReceiveRequest and BeforeSendReply methods are called.When hosting in IIS (5.1) only the constructor is called. However the service itself works fine and returns a valid response. The configuration file is exactly the same.I suppose if there is something wrong with the implementation it shouldn't work when self hosting the service either.Is there a way I can send you a simplified version of my solution so you could have a quick look on it?Thanks. - Anonymous
February 01, 2012
Sure, you can save the solution in a place such as dropbox or skydrive. - Anonymous
February 01, 2012
I've posted the zip into a public folder at the following link.skydrive.live.comPlease let me know if you find what's wrong or you cannot access the source code.I really appreciate your help.Thanks - Anonymous
February 01, 2012
The problem is that you need the fully-qualified name of the service class in the name attribute of the <service> element. In your code, you have the assembly-qualified name. Another issue is that the <host> element is ignored in IIS-hosted applications (the base address is the path to the .svc file), so you shouldn't specify the name EchoService.svc again in the endpoint address. With the address below, you can browse to http://localhost:4694/EchoService.svc/Echo/hello and you'll see the logs for the inspector being traced. <service name="TestEndpointBehavior.EchoService"> <endpoint behaviorConfiguration="restBehavior" binding="webHttpBinding" address="" contract="TestEndpointBehavior.IEchoService" /> </service> - Anonymous
February 02, 2012
I have done the changes you recommended:<services> <service name="TestEndpointBehavior.EchoService, TestEndpointBehavior"> <endpoint behaviorConfiguration="restBehavior" binding="webHttpBinding" address="" contract="TestEndpointBehavior.IEchoService" /> </service> </services>Which version of IIS are you using? I have publiched the code with only this documentation in either IIS5.1 and IIS7.5 and none of them are logging on those methods. Self hosting and asp.net development server is still generating working properly.Thanks. - Anonymous
February 02, 2012
You need to remove the ", TestEndpointBehavior" from the <service name="...">, otherwise the service configuration won't be read by WCF.<services> <service name="TestEndpointBehavior.EchoService"> <endpoint behaviorConfiguration="restBehavior" binding="webHttpBinding" address="" contract="TestEndpointBehavior.IEchoService" /> </service> </services> - Anonymous
February 02, 2012
That was the original configuration I had, so I put it back and it works now.So probably by removing the host element and changing the address fixed it.I just don't know how the service itself worked even with the wrong configuration.By the way, I'm glad it is working now.Thanks very much for all your patience, time and quick replies. - Anonymous
February 29, 2012
Great that it worked! - Anonymous
November 04, 2013
Hi, I'm struck up since few days in WCF configuration after migrating the application from VS 2008 to vs 2010. will you help me asap? Thanks,Siva. - Anonymous
April 13, 2014
Hi..I am stuck with the Wcf REST service not serializing the Complex object type automatically inside the body.Please have a look at the issue.stackoverflow.com/.../wcf-rest-put-methods-throws-400-bad-request-error-on-passing-object-type-inside - Anonymous
August 09, 2014
Is modifying the schema really the only way to get rid of the squiggly? That's not portable at all, which means I'll have to do the same thing on my developer colleagues machines as well... - Anonymous
October 27, 2015
The comment has been removed