Udostępnij za pośrednictwem


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]

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