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.
The first behavior covered in this series is the IServiceBehavior. It can be used to inspect or change the entire service description and runtime. For example, to collect information about all endpoints in the service, to apply some common behavior to all endpoints in the service, anything which affects the global service state, the service behavior is a good candidate. As you can see in the public implementations of the interface (and in the example later), they all apply to the service as a whole.
Public implementations in WCF
- AspNetCompatibilityRequirementsAttribute: Defines whether the service can, cannot or must use the ASP.NET Compatibility mode.
- ServiceAuthenticationBehavior: Defines the authentication behavior of the service, by providing an authentication manager to be called for incoming messages.
- ServiceAuthorizationBehavior: Defines properties for handling authorization for incoming messages to the service.
- ServiceCredentials: Defines the credentials used by the service, such as X.509 certificates.
- ServiceDebugBehavior: Defines debugging features for the service, such as the service help page or a flag indicating whether to send unknown exception details to clients.
- ServiceMetadataBehavior: Exposes metadata about the service for tools such as svcutil.exe or Add Service Reference.
- ServiceSecurityAuditBehavior: Defines the audit behavior of security events, such as authentication or authorization events.
- ServiceThrottlingBehavior: Defines throughput quotas to be applied to the service.
- UseRequestHeadersForMetadataAddressBehavior (new in 4.0): Used to determine that the machine names in metadata requests should use the address to which the request was made. Useful in scenarios where the server which is receiving the message is behind a load balancer – we want the endpoint addresses in the metadata to reflect the NLB’s address, not the individual node’s.
- ServiceDiscoveryBehavior (new in 4.0): Defines that the all the endpoints of this service will be announced using the WS-Discovery protocol.
- RoutingBehavior (new in 4.0): Defines the behavior for a service acting as a router.
- ServiceBehaviorAttribute: Allows the definition of some behavior for the service implementation.
As was mentioned in the post about Behaviors, you can use the Validate method to ensure that the service (or the host) doesn’t violate the validation logic on the behavior. One example is the AspNetCompatibilityRequirementsAttribute, which throws if the service requires ASP.NET compatibility, but it’s not there (i.e., in a self-hosted scenario), or if the behavior says that the compatibility mode is not allowed, but the hosting provides it.
AddBindingParameters is used to pass information to the binding elements themselves. For example, the security-related behaviors (ServiceAuthenticationBehavior, ServiceCredentials), add some security managers, which are used by the security binding elements when they’re creating their channels. This method in different than all others in all behaviors, in a sense that it can be called multiple times (all others are called only once) – it will be invoked once per endpoint defined in the service.
ApplyDispatchBehavior is the method which is most used. 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. Some examples are the help page added by the ServiceDebugBehavior (an extra channel listener is added to understand “GET” requests to the service base address), the wsdl page added by the ServiceMetadataBehavior (again, an extra listener), or the properties set by the ServiceThrottlingBehavior, which get propagated to all dispatchers in the service.
How to add a service behavior
Via the host Description property: if you have a reference to the host object, you can get a reference to the service behavior collection via its description property
Using attributes: If the service behavior is a class derived from System.Attribute, it can be applied directly to the service class, and it will be added to the description by WCF:
Using configuration: if the service behavior has a configuration extension, then it can be applied directly in the configuration
Real-world scenario: a POCO Service Host
Last month I talked to a friend who said he was starting to use WCF and he actually liked it, but he didn’t like to have to add attributes all over to define the service. Last week I talked to a colleague who said that we should have thought at first of some convention-based definition for services (in addition to the current attribute-based, not as a replacement), just like we added during .NET Framework 3.5 (or 3.5 SP1, I don’t remember) the serialization support for POCO (plain old CLR objects) – types not decorated with [DataContract]/[DataMember] attributes. As I was looking for a good scenario for a behavior which applies to a whole service, this seemed like a good one – if not for something that we’d encourage people to use often (when I mentioned it to another colleague he said that anytime someone would use such a thing, a SOAP kitten would lose part of its soul), but it demonstrates some concepts about the service description and the service runtime which can be accomplished via this extensibility point.
The main challenge in using a non-[ServiceContract] type as a service interface is that WCF has some restrictions about the contracts for its endpoints – they have to be declared as such. So if you simply try to add an endpoint passing a non-[SC] interface (or class type), it will throw while opening the host, during the runtime initialization. We need instead to delay the addition of the (default) endpoint to the service only to after the service runtime is initialized – and that’s exactly where IServiceContract.ApplyDispatchBehavior is called.
To enable such service to respond to client requests, we only needed to create a new dispatcher and route incoming messages to the appropriate service operation. In this implementation, however, I also update the service description, so that the service metadata behavior can produce metadata about this “new” endpoint and clients can use svcutil / Add Service Reference to create proxies to this POCO service, just like for any “normal” WCF service (and this is the only place I needed to access some internal property of WCF in this example).
This scenario is interesting because it also shows an example of the Validate method being used – we want to notify the user right away if the service class which is being used isn’t supported by our behavior. In the example, if the service is not a public class, does not have a default (public, parameter-less) constructor, or if it contains methods with ref or out parameters (implementation detail, it could be supported but I decided to omit them for simplicity sake), then it will throw on validation.
Before I go into the code a small 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 services (please let me know if you find a bug or something missing). Also, for simplicity sake (it’s already quite large) 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 invoker, instance provider, 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.
To the code. The validation logic is straightforward, as I mentioned before (it can be made more complex, such as verifying whether the operation parameters are serializable, for example). On ApplyDispatchBehavior, the code first creates a description object (of type ServiceEndpoint) and adds it to the service description. As I mentioned before, if the endpoint wasn’t added to the description the service would still continue working, but its metadata would not be exposed.to runtime (on serviceHostBase) has already been initialized.
CreateContractDescription walks through the public methods of the service, and add an operation description to the contract. CreateOperationDescription will create the description of the incoming and outgoing messages which are necessary for the metadata extension to create the WSDL describing this service.
CreateChannelDispatcher is the method which sets up the listener which will accept client requests. The dispatcher is usually created by WCF itself during the runtime initialization, but since we’re adding a non-orthodox endpoint we’re creating it ourselves. The code below walks through the service description (which we created before) and sets up the runtime necessary to listen to incoming messages. AssociateEndpointToDispatcher will access an internal property of the dispatcher and the endpoint to ensure that they are “linked” to each other (otherwise the code generated by svcutil would contain the binding and contract information, but not the address). Finally, the three nested classes implement the logic for instancing (one new service instance per call), instance context (a new one for each new message) and operation invoking (dispatching via a MethodInfo).
[update from 2012/01/30] It turns out that we don’t need to set up a custom instance provider or instance context provider for this case, as long as the ServiceBehaviorAttribute is applied after our service behavior (it will set it up itself). Thanks to Youssef Moussaoui for pointing this up. I’ve updated the code gallery sample, and if you want to check the history, you can look at the GitHub project for this sample.
To tie it all up, we have a new service host class, PocoServiceHost, which is simply a subclass of ServiceHost which adds the PocoServiceBehavior to the services. And as mentioned in the update, our service behavior needs to be inserted before the ServiceBehaviorAttribute, which is added by default to the service description.
And an example of this host being used:
Other behavior interfaces.