Udostępnij za pośrednictwem


WCF Extensibility – Binding (and binding element) 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 .

After a heavy subject in the channels, this week’s post is about something simpler – configuration extensions for bindings and binding elements. Just like you can use extensions to define endpoint and service behaviors in configuration, both binding elements (to be used in custom bindings) and full binding settings can be defined using configuration as well, as long as we have the appropriate binding element and standard binding extensions in place. And despite my personal bias against configuration, the scenario I’ll show in this post is actually a perfect example where defining a binding in configuration can be a good choice.

Binding elements can be made “configurable” by using the BindingElementExtensionElement class (similar to the BehaviorExtensionElement class covered in a previous post). Bindings take a little more work, since we need two extension classes, one for the binding “section” (StandardBindingCollectionElement<TStandardBinding, TBindingConfiguration>) for the new binding type, and one for each specific binding “instance” (StandardBindingElement). The next sections will have more details about these classes.

Public implementations in WCF

Those are the public binding element extensions – those which can be used inside custom binding declarations in configuration. I’ll divide them in sections for the channel types which those binding elements create.

Encoders:

Transports:

Protocol Channels:

Those are public standard binding extensions.

The collection classes for those bindings above are located in the same namespace (and assembly) as their respective binding classes.

Class declaration

  1. public abstract class BindingElementExtensionElement : ServiceModelExtensionElement
  2. {
  3.     public abstract Type BindingElementType
  4.     {
  5.         get;
  6.     }
  7.  
  8.     protected internal abstract BindingElement CreateBindingElement();
  9. }
  10.  
  11. public abstract partial class StandardBindingElement : ConfigurationElement
  12. {
  13.     protected abstract Type BindingElementType
  14.     {
  15.         get;
  16.     }
  17.  
  18.     protected abstract void OnApplyConfiguration(Binding binding);
  19. }
  20.  
  21. public partial class StandardBindingCollectionElement<TStandardBinding, TBindingConfiguration> : BindingCollectionElement
  22.     where TStandardBinding : Binding
  23.     where TBindingConfiguration : StandardBindingElement, new()
  24. {
  25. }

To implement a binding element configuration extension, one needs to derive from BindingElementExtensionElement. The derived class needs to override the BindingElementType property to return the type of the binding element which that extension can create, and also the CreateBindingElement method, to actually return an instance of the binding element it configures. Notice that BindingElementExtensionElement is derived from System.Configuration.ConfigurationElement, so the class can (and should) use the ConfigurationPropertyAttribute to define attributes and sub-elements which need to be passed to the binding element at creation time.

To implement a new “standard binding” type, one needs first to derive from StandardBindingElement, and again override two members: the BindingElementType property (which should be called “BindingType”, since this doesn’t deal with binding elements, but I digress) to return the type of binding which will be created, and OnApplyConfiguration, which will be called with an uninitialized instance of the binding type to be set. This class is also derived from System.Configuration.ConfigurationElement, but unlike on the binding element extensions, I always have to override the Properties property as well to include the custom properties for that binding (it’s not automatically picked up by the runtime). Also notice that since a “blank” instance of the binding element is passed to the extension (instead of the extension creating one), the standard binding class needs to have a parameter-less constructor, otherwise it cannot be defined in configuration. Finally, we need to define a “collection element” for the new standard binding, deriving a class from StandardBindingCollectionElement<TStandardBinding, TBindingConfiguration>. This step is actually quite simple, as the base class (with generic parameters) does most of the legwork, and all one needs to do is to define the parameters as the new standard binding type and the standard binding configuration element type which will be used. The code example will show this in practice.

How to add binding element and binding extensions

Binding element: custom binding element extensions are added to the <system.serviceModel / extensions / bindingElementExtensions> element, and given an alias which can be used inside custom binding declarations:

  1. <system.serviceModel>
  2.   <extensions>
  3.     <bindingElementExtensions>
  4.       <add name="myNewBindingElement"
  5.            type="MyNamespace.MyBindingElementExtension, MyAssemblyName"/>
  6.     </bindingElementExtensions>
  7.   </extensions>
  8. </system.serviceModel>

Standard bindings: standard binding extensions are added to the <system.serviceModel / extensions / bindingExtensions> element, and given an alias which can be used as a child element of the <bindings> node. The type must be the assembly-qualified name of the class derived from StandardBindingCollectionElement, not the one derived from StandardBindingElement.

  1. <system.serviceModel>
  2.   <extensions>
  3.     <bindingExtensions>
  4.       <add name="myNewBinding"
  5.            type="MyNamespace.MyBindingElementCollection, MyAssemblyName"/>
  6.     </bindingExtensions>
  7.   </extensions>
  8. </system.serviceModel>

One note about extensions and Visual Studio: just like it happened with behavior extensions, VS will usually issue a warning about a schema violation when we use binding or binding element configuration extensions, and tag the extension with a squiggly line (see below). The warning states that it is not a valid child for the <binding> or <customBinding> elements. 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.

WarningInBindingExtensions

Real world example: configuration extensions for the “chaos monkey channel”

The “chaos monkey channel”, introduced in the post about channels, is great during testing of the system for resiliency over failures on dependent services. But as a system matures (or as it goes from the test to the production stage), one may want to tweak the amount of failures which the channel injects into the system. With a code-based binding element (as it was in the first sample), one would need to change the source code, then recompile it for every change in the “chaos controller”. Or one would simply add some external variable (i.e., registry, file, database, etc.) which would be read by the runtime when creating the channel. WCF configuration is nothing more than another external source, it just happens to be fairly well used (the .NET configuration system has been in use since the beginning of .NET), so it’s a good place for it to be. This is one of the (few, IMO) places which configuration actually makes sense.

And before I go further, the usual disclaimer: this is a sample for illustrating the topic of this post, this is not production-ready code. I tested it for a few scenarios and it worked, but I cannot guarantee that it will work for all scenarios. It also does not have a lot of error handling / good exception messages which a production-level code should have, as I’ve kept it simple for illustration purposes.

The project starts with the “Chaos Monkey Channel”. I won’t go into the details of that project, only on the configuration extensions this time. Just one small modification: the sample chaos controller was “promoted” from a nested class to a top-level one, and it got two parameters which can be passed to its constructor (to illustrate parameters in the configuration extensions).

  1. public class MyMonkeyController : IChaosController
  2. {
  3.     Random rndGen;
  4.     int percentFailure;
  5.  
  6.     public MyMonkeyController() : this(Environment.TickCount)
  7.     {
  8.     }
  9.  
  10.     public MyMonkeyController(int randomSeed) : this(randomSeed, 20)
  11.     {
  12.     }
  13.  
  14.     public MyMonkeyController(int randomSeed, int percentFailure)
  15.     {
  16.         this.rndGen = new Random(randomSeed);
  17.         this.percentFailure = percentFailure;
  18.     }
  19.  
  20.     public bool ShouldFault(ref Message request, out Message fault)
  21.     {
  22.         int rnd = this.rndGen.Next(0, 100);
  23.         if (rnd < this.percentFailure)
  24.         {
  25.             var exception = new FaultException("Chaos!!!", new FaultCode("chaos!"));
  26.             var messageFault = exception.CreateMessageFault();
  27.             fault = Message.CreateMessage(request.Version, messageFault, "Chaos");
  28.             return true;
  29.         }
  30.         else
  31.         {
  32.             fault = null;
  33.             return false;
  34.         }
  35.     }
  36. }

Now we can start with the extension for the binding element. The class is actually quite simple, with a pair of properties to control both the random seed and the level of failure introduced by the channel. Those properties are picked up automatically by the configuration subsystem, so they can simply be used in the CreateBindingElement method.

  1. public class ChaosMonkeyElementExtension : BindingElementExtensionElement
  2. {
  3.     private const string RandomSeedPropertyName = "randomSeed";
  4.     private const string PercentFailurePropertyName = "percentFailure";
  5.  
  6.     public override Type BindingElementType
  7.     {
  8.         get { return typeof(ChaosMonkeyBindingElement); }
  9.     }
  10.  
  11.     [ConfigurationProperty(RandomSeedPropertyName, IsRequired = false, DefaultValue = -1)]
  12.     public int RandomSeed
  13.     {
  14.         get { return (int)base[RandomSeedPropertyName]; }
  15.         set { base[RandomSeedPropertyName] = value; }
  16.     }
  17.  
  18.     [ConfigurationProperty(PercentFailurePropertyName, IsRequired = false, DefaultValue = 20)]
  19.     public int PercentFailure
  20.     {
  21.         get { return (int)base[PercentFailurePropertyName]; }
  22.         set { base[PercentFailurePropertyName] = value; }
  23.     }
  24.  
  25.     protected override BindingElement CreateBindingElement()
  26.     {
  27.         int randomSeed = this.RandomSeed;
  28.         if (randomSeed < 0)
  29.         {
  30.             randomSeed = Environment.TickCount;
  31.         }
  32.  
  33.         IChaosController controller = new MyMonkeyController(this.RandomSeed, this.PercentFailure);
  34.         return new ChaosMonkeyBindingElement(controller);
  35.     }
  36. }

And with it we can register this new extension class in the <system.serviceModel / extensions / bindingElementExtensions> list, as shown below.

  1. <system.serviceModel>
  2.   <extensions>
  3.     <bindingElementExtensions>
  4.       <add name="chaosMonkey"
  5.            type="ChaosMonkeyChannel.ChaosMonkeyElementExtension, ChaosMonkeyChannel"/>
  6.     </bindingElementExtensions>
  7.   </extensions>
  8.   <bindings>
  9.     <customBinding>
  10.       <binding name="BindingWithChaos">
  11.         <chaosMonkey percentFailure="50" />
  12.         <binaryMessageEncoding />
  13.         <httpTransport />
  14.       </binding>
  15.     </customBinding>
  16.   </bindings>
  17. </system.serviceModel>

And we can now create a new custom binding in code which uses the new binding element (<chaosMonkey>), and use it in a program.

  1. var binding = new CustomBinding("BindingWithChaos");
  2. var factory = new ChannelFactory<ITest>(binding, new EndpointAddress(baseAddress + "/custom"));
  3. var proxy = factory.CreateChannel();
  4.  
  5. for (int i = 0; i < 30; i++)
  6. {
  7.     try
  8.     {
  9.         Console.WriteLine("10 + {0} = {1}", i, proxy.Add(10, i));
  10.     }
  11.     catch (FaultException ex)
  12.     {
  13.         Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);
  14.     }
  15. }

And that’s it – the ChaosMonkeyBindingElement can now be defined in configuration. For the standard binding, if we’re using a certain binding element combination in a custom binding ofrten enough, sometimes we create a new Binding class so that we don’t have to always define the same custom binding over and over. In this example, I’ll create a new binding which has a chaos monkey element, plus a binary encoding, plus the HTTP transport.

  1. public class BinaryOverHttpWithChaosBinding : Binding
  2. {
  3.     IChaosController controller;
  4.  
  5.     public BinaryOverHttpWithChaosBinding()
  6.         : this(null)
  7.     {
  8.     }
  9.  
  10.     public BinaryOverHttpWithChaosBinding(IChaosController controller)
  11.     {
  12.         this.controller = controller;
  13.     }
  14.  
  15.     public IChaosController Controller
  16.     {
  17.         get { return this.controller; }
  18.         set { this.controller = value; }
  19.     }
  20.  
  21.     public override BindingElementCollection CreateBindingElements()
  22.     {
  23.         if (this.controller == null)
  24.         {
  25.             throw new InvalidOperationException("The controller cannot be null.");
  26.         }
  27.  
  28.         return new BindingElementCollection(new BindingElement[] {
  29.             new ChaosMonkeyBindingElement(this.controller),
  30.             new BinaryMessageEncodingBindingElement(),
  31.             new HttpTransportBindingElement(),
  32.         });
  33.     }
  34.  
  35.     public override string Scheme
  36.     {
  37.         get { return new HttpTransportBindingElement().Scheme; }
  38.     }
  39. }

This new binding can be used in code, but since this is a configuration post, let’s make it configurable as well. First off, the StandardBindingElement subclass for it. As I mentioned before, if I don’t override the “Properties” property, this does not work (unlike in the binding element extension, where it just worked).

  1. public class BinaryOverHttpWithChaosBindingElement : StandardBindingElement
  2. {
  3.     private const string RandomSeedPropertyName = "randomSeed";
  4.     private const string PercentFailurePropertyName = "percentFailure";
  5.  
  6.     [ConfigurationProperty(RandomSeedPropertyName, IsRequired = false, DefaultValue = -1)]
  7.     public int RandomSeed
  8.     {
  9.         get { return (int)base[RandomSeedPropertyName]; }
  10.         set { base[RandomSeedPropertyName] = value; }
  11.     }
  12.  
  13.     [ConfigurationProperty(PercentFailurePropertyName, IsRequired = false, DefaultValue = 20)]
  14.     public int PercentFailure
  15.     {
  16.         get { return (int)base[PercentFailurePropertyName]; }
  17.         set { base[PercentFailurePropertyName] = value; }
  18.     }
  19.  
  20.     protected override Type BindingElementType
  21.     {
  22.         get { return typeof(BinaryOverHttpWithChaosBinding); }
  23.     }
  24.  
  25.     protected override void OnApplyConfiguration(Binding binding)
  26.     {
  27.         BinaryOverHttpWithChaosBinding chaosBinding = (BinaryOverHttpWithChaosBinding)binding;
  28.         int randomSeed = this.RandomSeed;
  29.         if (randomSeed < 0)
  30.         {
  31.             randomSeed = Environment.TickCount;
  32.         }
  33.  
  34.         IChaosController controller = new MyMonkeyController(this.RandomSeed, this.PercentFailure);
  35.         chaosBinding.Controller = controller;
  36.     }
  37.  
  38.     protected override ConfigurationPropertyCollection Properties
  39.     {
  40.         get
  41.         {
  42.             var result = base.Properties;
  43.             result.Add(new ConfigurationProperty("percentFailure", typeof(int), 20));
  44.             result.Add(new ConfigurationProperty("randomSeed", typeof(int), -1));
  45.             return result;
  46.         }
  47.     }
  48. }

Finally the standard binding collection element. This is essentially a one-liner, as the base class does all the work:

  1. public class BinaryOverHttpWithChaosBindingElementCollection
  2.     : StandardBindingCollectionElement<BinaryOverHttpWithChaosBinding,
  3.         BinaryOverHttpWithChaosBindingElement>
  4. {
  5. }

Now we can register that extension in the <system.serviceModel / extensions / bindingExtensions> list:

  1. <system.serviceModel>
  2.   <extensions>
  3.     <bindingExtensions>
  4.       <add name="binaryHttpChaos"
  5.            type="ChaosMonkeyChannel.BinaryOverHttpWithChaosBindingElementCollection, ChaosMonkeyChannel"/>
  6.     </bindingExtensions>
  7.   </extensions>
  8.   <bindings>
  9.     <binaryHttpChaos>
  10.       <binding name="MyBinding" percentFailure="30" randomSeed="1" />
  11.     </binaryHttpChaos>
  12.   </bindings>
  13.   <client>
  14.     <endpoint name="allConfig"
  15.               address="https://localhost:8000/Service/custom"
  16.               binding="binaryHttpChaos"
  17.               bindingConfiguration="MyBinding"
  18.               contract="ChaosMonkeyChannel.ITest" />
  19.   </client>
  20. </system.serviceModel>

And finally, we can use it in our test program:

  1. var binding = new CustomBinding("BindingWithChaos");
  2. var factory = new ChannelFactory<ITest>("allConfig");
  3. var proxy = factory.CreateChannel();
  4.  
  5. for (int i = 0; i < 30; i++)
  6. {
  7.     try
  8.     {
  9.         Console.WriteLine("10 + {0} = {1}", i, proxy.Add(10, i));
  10.     }
  11.     catch (FaultException ex)
  12.     {
  13.         Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);
  14.     }
  15. }

And that’s it for configuration extensions.

[Code in this post]

[Back to the index]

Comments

  • Anonymous
    December 22, 2011
    Hi Carlos,Great series of posts on WCF extensibility, thank you! I was wondering what we need to do if the binding element needs to return a json message back to the client. In an error case, I'm trying to return a message by using Message.CreateMessage and passing a custom BodyWriter, that is supposed to format my custom error object using DataContractJsonSerializer. I also set the HttpResponseMessageProperty on the message, set status code, and set content type of response header to "application/json". When I see my traffic in Fiddler, the response content type header is json, and the status code is whatever I set it to. however, the actual message body is still in XML and not Json. I'm using a webHttpEndPoint and can see that the WebMessageEncodingBindingElement is present.would appreciate any pointers.thanks,Priya
  • Anonymous
    December 22, 2011
    I figured this out, and have posted the solution in the WCF forum here :social.msdn.microsoft.com/.../08e7edab-3edf-4fb3-af85-34edd1f62bf5. Basically, I need to set a WebBodyFormatMessageProperty  property.
  • Anonymous
    December 22, 2011
    Hi Priya, thanks for the comments. Yes, the WebMessageEncodingBindingElement holds a "composite" encoder which needs some additional information to know how it will encode the outgoing message, and that information is given by the WebBodyFormatMessageProperty as you mentioned. Glad you figured it out!
  • Anonymous
    February 07, 2014
    Hi Carlos, How I can set  maxReceivedMessageSize attribute for the below binding extension?<binaryHttpChaos>     <binding name="MyBinding" percentFailure="30" randomSeed="1" />  </binaryHttpChaos>
  • Anonymous
    February 07, 2014
    Biju, you can define a new [ConfigurationProperty] for that value you want passed in. Then, when creating the binding element collection, use that value to set the MaxReceivedMessageSize on the HttpTransportBindingElement.
  • Anonymous
    February 10, 2014
    Carlos Thanks for your quick response. Please find below the binding extension I created   internal class InternalBindingCollectionElement : StandardBindingCollectionElement<UnSecuredWSHTTPBinding, InternalBindingElement>   {   }      class InternalBindingElement : StandardBindingElement   {       protected override void OnApplyConfiguration(Binding binding) { }       protected override Type BindingElementType { get { return typeof(UnSecuredWSHTTPBinding); } }   }     public class UnSecuredWSHTTPBinding : CustomBinding   {       private static WSHttpBinding CreateSecuredWSHTTPBinding()       {           WSHttpBinding wsBinding = new WSHttpBinding();           wsBinding.Security.Mode = SecurityMode.Transport;           wsBinding.Security.Message.AlgorithmSuite = System.ServiceModel.Security.SecurityAlgorithmSuite.Basic256;           wsBinding.Security.Message.NegotiateServiceCredential = false;           return wsBinding;       }       private static CustomBinding CreateUnSecuredWSHTTPBinding()       {           WSHttpBinding wsBinding = CreateSecuredWSHTTPBinding();               CustomBinding unsecuredWSHTTPBinding = new CustomBinding(wsBinding.CreateBindingElements());                 var httpTransportBindingElement = new UnSecuredTransportBindingElement();                httpTransportBindingElement.MaxReceivedMessageSize = MaxContentLengthLimit;                HttpsTransportBindingElement httpsTransportBindingElement = unsecuredWSHTTPBinding.Elements.Find<HttpsTransportBindingElement>();           int httpsTransportElementIndex = unsecuredWSHTTPBinding.Elements.IndexOf(httpsTransportBindingElement);           unsecuredWSHTTPBinding.Elements[httpsTransportElementIndex] = httpTransportBindingElement;           // Return modified binding.           return unsecuredWSHTTPBinding;       }       public override BindingElementCollection CreateBindingElements()       {           return CreateUnSecuredWSHTTPBinding().Elements;       }       public override string Scheme { get { return "http"; } }       private static int MaxContentLengthLimit { get { return Int32.MaxValue; } }   }
  • Anonymous
    February 10, 2014
       class UnSecuredTransportBindingElement : HttpTransportBindingElement, ITransportTokenAssertionProvider   {           public class Caps : ISecurityCapabilities       {           public ProtectionLevel SupportedRequestProtectionLevel { get { return ProtectionLevel.EncryptAndSign; } }           public ProtectionLevel SupportedResponseProtectionLevel { get { return ProtectionLevel.EncryptAndSign; } }           public bool SupportsClientAuthentication { get { return false; } }           public bool SupportsClientWindowsIdentity { get { return false; } }           public bool SupportsServerAuthentication { get { return true; } }       }       public override T GetProperty<T>(BindingContext context)       {                   if (typeof(T) == typeof(ISecurityCapabilities))               return new Caps() as T;                 return base.GetProperty<T>(context);       }       public System.Xml.XmlElement GetTransportTokenAssertion()       {           return null;       }   }I configured my WCF service to use this binding extension and while accessing the WCF from Clients, I got the following errorSystem.ServiceModel.CommunicationException: The maximum message size quota for incoming messages (65536) has been exceeded. To increase the quota, use the MaxReceivedMessageSize property on the appropriate binding element. ---> System.ServiceModel.QuotaExceededException: The maximum message size quota for incoming messages (65536) has been exceeded. To increase the quota, use the MaxReceivedMessageSize property on the appropriate binding element.In the above bindingextension class, I set this value as Int32.MaxValue (ie, 2,147,483,647). But somehow in Services is taking the default value (65536).  Any idea why this value got overridden?
  • Anonymous
    February 11, 2014
    Hi Carlos,It was my mistake. I forgot to change it in the client config file.Thanks
  • Anonymous
    February 11, 2014
    Great, glad to see your issue has been solved.