WCF: Navigating the Binding maze
A few days ago I ranted a bit about the Binding object model in WCF and how restrictive it feels when I want to imperatively describe my service bindings without being tied down to a specific transport. If I want to disable security or set the maximum allowed size per field in a message, I have to write identical-looking code for each different binding - something that just begs for refactoring which can't be done.
Luckily, the WCF team bloggers are ever alert, and a few days later I got a response from Nicholas Allen describing the reasons behind this behavior. His explanantion consisted of two parts, and I'd like to address them seperately:
1) Avoiding meaningless abstractions.
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.
Granted, one of my common failings is the urge for over-abstraction. I've read Spolsky and I've read Gunnerson, and I try to avoid excessive generalizations, but somtimes it gets the better of me. Having the security settings share a common base - even if only to allow setting No Security in a shared way - seems intuitive. I'll bow down to the WCF team's decision here - if the binding security objects really don't share a common base, there's no reason to abstract them together.
2) Aggregation, not inheritance.
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;
Instead of relying on inheritance for shared properties, the object model views each Binding object as an aggregate of several binding elements and properties. Since the properties of a binding can differ between two instances of the same transport, the GetProperty<T> mechanism allows us to query the binding for a specific capability - the XmlDictionaryReaderQuotas capability in this case - and access it generically. I would assume the above line would have to be wrapped in some sort of null check in case the binding doesn't support XmlDictionaryReaderQuotas.
The first thing that came to my mind about this is the similarity to the way C++ code worked with COM objects. You used the shared IUnknown interface to call QueryInterface and see what interfaces were supported by the object, and then got a handle to those interfaces. This is similar - the GetProperty<T> is the shared interface which allows us to query for further functionality.
This, however, led to this question: why wasn't this functionality also implemented with interfaces? Why couldn't we have an ISupportsReaderQuotas interface which defines the XmlDictionaryReaderQuotas property, and instead of calling GetProperty() I can cast my binding to the interface and work with that? Each binding can implement the interfaces that make sense to it, and I can use the existing .NET mechanisms (the is and as operators, as well as reflection support) to query the object for the required operations.
This will also bypass other limitations of this model. As it stands, I have no idea how to access the MaxReceivedMessageSize property on the binding - it's an Int64 seperately defined in each binding class. GetProperty<T>'s generic parameter is constrained to be a reference type, but even if I could query for Int64 I have no way of specifying which Int64 parameter I want. If this property was part of an IBindingMessageProvider interface I could access it through there.
The advantages to the GetProperty<T> approach is that it is more flexible. I don't have to know at compile-time what interfaces my binding supports. Glancing at the GetProperty code in Reflector, I could see it go deep into the BindingElements that make up the Binding and calling GetProperty<T> on all of them. This means that a binding element added at runtime or through configuration (say, a message encoder) get be retrieved without it being defined as part of the NetTcpBinding class.
Thinking it over (this is a stream-of-consciousness blog, can't you tell?), I can certainly see the logic behind the GetProperty<T> mechanism, but it requires more extensive support for it in the binding object. This means the binding object should have as little "unattached" properties as possible, especially if they can be a part of several different bindings. The MaxMessageSize/MaxPoolSize and related parameters can be extracted to a MessageSizeInformation class, while the TransactionFlow/TransactionProtocol can be extracted to a TransactionInformation class - that way I can always query for the required "interface" at runtime without having to familiarize myself with specific properties of specific bindings.
(This actually gives me an interesting idea about combining interfaces and aggregation, but more on that later)
So, assuming you made it to the end - how do you feel about the current implementation? Am I completely off-base here? Do I make a valid point? Were these issues discussed internally before this implementation were chosen? What were the pros and cons?
I'd love to hear more opinions, both from anyone on the WCF that might be listening and from anyone with an opinion.
2 Comments
Comments have been disabled for this content.
AvnerK said
Thanks for the link. I try to follow as many WCF bloggers but there's too much blog backlog (would that be... oh, nevermind). Good to see a good guideline set forth. Don't you feel that these distinctions (basic property, Binding-level capability and BindingElement-level capability), while naturally being implemented differently, should be exposed in a more uniform manner?
Nicholas Allen said
That's a tradeoff between consistency and usability. GetProperty is the most powerful mechanism for exposing settings but it's fundamentally very ugly. It's the only mechanism for "late-bound" properties like we need to support custom bindings. GetProperty is also nice that we can add features to bindings without having to recreate all of the classes and interfaces for each release. However, GetProperty is not easily discoverable and requires complex incantations to get right.