Binding Polymorphism

A few days ago, Avner Kashtan asked why the design-time elements of the channel stack don't have a common object hierarchy for configuration settings (thereby making it difficult to change settings without knowing what transport you're using). The answer turns out to be extremely pragmatic.

First, some corrections.

The article contains a layered binding model, which seems to continue lingering in the documentation despite its lack of correctness. There's really only one rule for bindings. A binding must contain a transport. Everything else is more common sense than an actual requirement. For instance, if you go to the trouble of signing, encrypting, and creating a cryptographic hash for a message header, then it doesn't make sense to later have a binding element whose job it is to change that header. This leads to rules of thumb, such as that the message security binding element is below the transaction binding element, but that doesn't actually stop you from trying to build and use that binding.

Also, message encoders are optional despite our very strong guidance that you should always use a message encoder. You can build and use a binding without a message encoder assuming that the transport is kind enough to give you one by default. The transport might not even have the concept of a message encoder. How would you encode messages for a .NET Remoting transport? It doesn't make sense to talk about encoding in that transport.

Now, the answer.

There are configuration settings that don't have a common object hierarchy because it turns out that despite being intuitively similar, they neither look nor act the same. Take the example of security configuration on a transport. There are entirely incompatible security objects called NetTcpSecurity and NetNamedPipeSecurity. The only overlap in configuring these two transports happens to be when security is entirely disabled. That's not a very interesting abstraction to make. HTTP security is actually nothing at all like either TCP security or named pipe security. These transports simply have different feature sets.

Then, there are some configuration settings that do look and act the same. For the settings that apply to every channel in the binding, those settings are available on the binding itself. For the settings that apply to a specific channel, you don't know what in the channel stack is actually implementing those settings. We have a mechanism for dealing with this problem called GetProperty. For instance, if you want to set an XML reader quota in a generic fashion, you would use

 binding.GetProperty<XmlDictionaryReaderQuotas>(new BindingParameterCollection()).MaxArrayLength = 2;
 new BindingContext(binding, new BindingParameterCollection()).GetInnerProperty<XmlDictionaryReaderQuotas>().MaxArrayLength = 2

Update: I found a small piece on binding element stacking rules of thumb that I wrote earlier. I'll try to clean that up and post it next week as a counterpoint to the layered binding model.

Update: I'll explain in an upcoming post where and why that uglier version of GetProperty comes from.