This blog has moved to http://blogs.msdn.com/b/appfabric please update your links!
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.
Going down the list of behaviors, the second one to be covered is the IContractBehavior. Like the other ones, it can be used to inspect or change the contract description and its runtime for all endpoints which have that contract. For example, the behavior can validate the contract against the binding to ensure that the it’s used the contract is only used with appropriate, or it can modify the runtime for all its operations in a centralized location. Unlike IServiceBehavior mentioned in the previous post, the IContractBehavior is also used to change the client runtime.
As was mentioned in the post about Behaviors, you can use the Validate method to ensure that the contract doesn’t violate the validation logic on the behavior. One example is the DeliveryRequirementsAttribute, which throws if the behavior requires ordered message delivery but it’s not there (i.e., using HTTP, MSMQ), or if the behavior says that it cannot have queued delivery but the binding provides that.
AddBindingParameters is used to pass information to the binding elements themselves. It’s not used as often, but one (not too common) example would be for a contract, after validating that the binding does not contain any message encoding binding elements, to add one in the binding parameters at this method, thus guaranteeing that it would always use that encoding type.
ApplyDispatchBehavior is the method which is most used in the server side. It’s called right after the runtime was initialized, so at that point the code has access to the listeners / dispatchers / runtime objects for the server, and can add / remove / modify those. The example below will use an endpoint behavior to change the serializer used by all of the operations in the contract.
ApplyClientBehavior is the counterpart to ApplyDispatchBehavior for the client side. After the client runtime is initialized, the method is called so that the code can update the client runtime objects.
Via the service / endpoint description (server): On the server side, once you add an endpoint you can get a reference to it, then you can access its Contract property, and from there you can access the behavior collection.
Via the channel factory / generated proxy (client): The ChannelFactory class has an Endpoint property which is of the same type as the one on the server. Likewise, generated proxies (using svcutil / add service reference) are subclasses of ClientBase<T>, which also has an Endpoint property which contains a reference to the contract description, and from there to the behavior collection. Just remember that, once the endpoint has been opened (for channel factories, when it’s been explicitly opened or a channel created; for generated clients when it’s been explicitly opened or an operation called), modifying the description will have no effect on the runtime, since it’s already been created).
Using attributes: if the contract behavior is derived from System.Attribute, it can be applied to either the contract interface or the service class, and it will be added to the contract description by WCF. If it’s applied to the contract interface, it will be added to the contract description in all endpoints which use that contract (both server and client). If the attribute is applied to the service implementation, it will be applied to all contracts implemented by the service (unless the attribute also implements the IContractBehaviorAttribute interface, in which case it can be restricted to a single contract type – see more information in the remarks section of the documentation for IContractBehavior.
Using configuration: Configuration is not an option for adding endpoint behaviors.
This has come up a few times in the forums (1, 2, 3, …). Most of the time it’s about forcing WCF to use XmlSerializer, or replace the default DataContractSerializer with the NetDataContractSerializer (to avoid having to deal with known types in inheritance-heavy scenarios). Other times people really need a new way of serializing / deserializing objects – for performance or size reasons, for example. In this example I have a custom serializer which knows how to serialize very compactly some data types which are tagged with a specific interface. The interface is a simple one with two methods: one for the object to serialize itself into a stream, and another for an object to initialize itself from a stream.
And before I go any further, here goes 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 inputs and it worked, but I cannot guarantee that it will work for all types (please let me know if you find a bug or something missing). Also, for simplicity sake it doesn’t have a lot of error handling which a production-level code would. Finally, this sample (as well as most other samples in this series) uses extensibility points other than the one for this post (e.g., operation behavior, custom serializers, etc.) which are necessary to get a realistic scenario going. I’ll briefly describe what they do, and leave to their specific entries a more detailed description of their behavior.
Below are a few types which implement this interface. They use some helper functions to read/write data in a compact binary format (the code for the helper can be found in the [Code for this post] link at the end). This is a format similar to the one used by the .NET XML Binary Format (the one used by the binary encoding in WCF).
Now to the behavior. In order to easily share the behavior between client and server (after all, both sides need to agree on the serialization format), I’m implementing it as an attribute, so we can share the contract definition between the parties and it will be decorated with the behavior. The behavior will do two things: validate that all types which will use the new serializer can be created (i.e., they’re public types with a public, parameter-less constructor), and replace a behavior in all of the contract operations. The behavior which selects the serializer in WCF is the DataContractSerializerOperationBehavior (DCSOB), whose task is to add a formatter to the runtime which knows how to convert between the incoming / outgoing message and the parameters in the operations. We could create a new formatter to do that, but inheriting from DCSOB (even though we won’t be using the DataContractSerializer for our custom serialization) will save us time, as it exposes two helper virtual methods that makes it easier to replace the serializer).
Finally, the serializer. Implementing a new WCF serializer is a matter of finding a mapping between XML (the internal format used by WCF messages) and the format you want. For this optimized serialization, I’ll use a very simple mapping which writes the serialized object as binary data, wrapped inside a XML element. The mapping is done by subclassing XmlObjectSerializer, by overriding a few methods about reading / writing the object from / to XML reader / writers. Also, since there are types which can be used in the operation but aren’t ready for optimized serialization, the serializer is holding on to a reference to the original serializer from WCF, so that they can be used as well. So the implementation checks whether the type being serialized supports custom serialization; if it does, the new logic is used; otherwise it delegates the call to the original serializer for that type.
The remaining 2 behavior interfaces.
[Code in this post]
[Back to the index]
Carlos Figueira http://blogs.msdn.com/carlosfigueira Twitter: @carlos_figueira http://twitter.com/carlos_figueira
What can be wrong in your code if the CreateSerializer method is not called anyway on the client side? While operation behaviors has been tweaked successfully.
P.S. NET 4.0, VS2010 SP1
Thanks in advance.
Ruslan, if you add breakpoints in the behavior's ApplyClientBehavior method, and in the MyNewSerializerOperationBehavior's CreateSerializer methods, are any of them hit?