This chapter is the first of three on how to customize the Windows Communication Foundation. It begins by describing how the Windows Communication Foundation can be extended, before going on to describe one category of customizations in detail: extensions to the Windows Communication Foundation Service Model that are accomplished using custom behaviors.
The Windows Communication Foundation is meant to be, or at least to become, the single best way of getting pieces of software to communicate under any circumstances. For it to achieve that ambitious objective, it must be possible to customize the technology to cover cases that the designers of the Windows Communication Foundation may not have anticipated. Therefore, the designers worked hard to ensure that the Windows Communication Foundation was extensible.
In doing so, they followed the layered approach to framework design that Krzysztof Cwalina and Brad Abrams recommend based on their experience working on the design of the .NET Framework itself (Cwalina and Abrams 2006, 29). The objective of a layered approach to framework design is to make the most common scenarios very easy, while still making it possible to cover all the less common scenarios that are meant to be accommodated. The general guidelines for achieving that objective “is to factor [the] API set into low-level types that expose all of the richness and power and high-level types that wrap the lower layer with convenience APIs” (Cwaline and Abrams 2006, 29–30). One approach to accomplishing that factoring is to “put the high-level and low-level types in different but related namespaces. This has the advantage of hiding the low-level types from the mainstream scenarios without putting them too far out of reach when developers need to implement more complex scenarios” (Cwalina and Abrams 2006, 30).
That is precisely how the Windows Communication Foundation types are organized. The high-level types needed for the commonplace scenarios are in the primary System.ServiceModel
namespace, whereas the low-level types needed for extending the Windows Communication Foundation to accommodate less common cases are primarily in the System.ServiceModel.Dispatcher
and System.ServiceModel.Channels
namespace.
The low-level types in the System.ServiceModel.Dispatcher
namespaces are for defining custom behaviors that extend the Windows Communication Foundation’s Service Model. The low-level types in the System.ServiceModel.Channels
namespace are for defining custom binding elements that extend the Windows Communication Foundation’s Channel Layer.
Behaviors control internal communication functions, whereas binding elements create channels that control external communication functions. For example, a behavior controls the internal function of serializing outbound data into a message, whereas a channel created from a binding element controls the external function of sending the message to its destination.
Within Windows Communication Foundation clients, behaviors modify the operation of components that are primarily responsible for serializing outbound data into messages and de-serializing the responses. The Windows Communication Foundation provides a client runtime component for each endpoint. Each of those components has a number of client operation runtime components associated with it—one for each of the operations included in the endpoint’s contract. When an operation is invoked, the client operation runtime component for that operation takes the outbound data and serializes it into a message. That message is then passed to the client runtime component for the endpoint, which passes it on to the Windows Communication Foundation’s Channel Layer.
Within Windows Communication Foundation services, behaviors modify the functioning of components called dispatchers. Dispatchers are responsible for taking incoming messages and passing the data therein to the appropriate method of a service.
Three kinds of dispatchers participate in that process. The channel dispatcher reads inbound messages from the channel layer, determines the endpoint to which each message is directed toward based on address of the message, and passes each message to the appropriate endpoint dispatcher. The endpoint dispatcher determines the operation to which each message corresponds based on the Action header of the message, and passes the messages to the appropriate operation dispatcher. The operation dispatcher deserializes each incoming message into parameters, and passes those parameters to the method of the service that corresponds to the operation. It also serializes outbound responses into messages.
So, within both clients and services, there are components that correspond to endpoints and others that correspond to operations. The latter perform, as one might expect, functions that are operation specific. For example, the task of serializing outbound data into a message is an operation-specific one, for the way in which the data for one operation has to be serialized might be different from how it has to be done for another operation. The task of passing the messages into which outbound data has been serialized to the channel layer for transmission, however, is not an operation-specific function: It is performed in the same way for all of an endpoint’s operations.
The implementation of custom behaviors to modify the workings of the client runtime components and the dispatchers usually involves three steps. Here is an explanation of each step.
The first step in the implementation of a custom behavior is to declare what sort of behavior one is providing—one that works inside clients to serialize outgoing data into messages, for instance, or one that works inside services to manage instances of service types, perhaps. Declaring what sort of behavior one is providing is easily accomplished by implementing the appropriate interface. Those interfaces are almost all defined in the System.ServiceModel.Dispatcher
namespace. So, for example, declaring that one is providing a behavior to work inside clients to customize the serialization of outgoing data is done by implementing the interface System.ServiceModel.Dispatcher.IClientMessageFormatter
interface. Declaring that one is providing a behavior to work inside services to manage instances of service types is done by implementing the interface System.ServiceModel.Dispatcher.IInstanceProvider
.
One might ask, “How is one to know what kinds of behaviors one can provide, and how is one to know which interface to implement to provide a particular kind of behavior?” The answer is that to ascertain what kinds of behaviors one can provide, one can examine the properties of the client runtime and dispatcher classes, for it is by assigning values to their properties that one attaches custom behaviors to them. The data types of those properties also indicate the interfaces that each type of custom behavior must implement.
Starting that process of examination with the client runtime, the client runtime components are defined by two classes. The class System.ServiceModel.Dispatcher.ClientOperation
represents the client runtime components at the operation level, whereas the class System.ServiceModel.Dispatcher.ClientRuntime
represents the client runtime at the endpoint level.
The System.ServiceModel.Dispatcher.ClientOperation
class has two properties for attaching operation-specific behaviors:
The first property is ParameterInspectors
, which is a collection of System.ServiceModel.Dispatcher.IParameterInspector
objects. So, one can create a custom parameter inspector behavior by implementing the System.ServiceModel.Dispatcher.IParameterInspector
interface. A parameter inspector gets to examine and optionally modify outgoing data as well as incoming response data.
The second property of the System.ServiceModel.Dispatcher.ClientOperation
class by which one can attach a custom behavior to an operation is the Formatter
property. Objects that implement the System.ServiceModel.Dispatcher.IClientMessageFormatter
interface can be assigned to that property. Such objects, which are referred to as client message formatters, are for serializing outgoing data into messages, or, more precisely, into the bodies of System.ServiceModel.Channels.Message
objects.
The System.ServiceModel.Dispatcher.ClientRuntime
class also has two properties for attaching endpoint behaviors:
The first property of the System.ServiceModel.Dispatcher.ClientRuntime
class by which one can attach a custom behavior to an endpoint is the OperationSelector
property. Objects that implement the System.ServiceModel.Dispatcher.IClientOperationSelector
interface can be assigned to that property. Those client operation selector objects determine to which operation of a service a request is to be directed based on the particular method of the client that was invoked.
The second property is MessageInspectors
, which is a collection of System.ServiceModel.Dispatcher.IClientMessageInspector
objects. The client message inspector objects one can create by implementing that interface allow one to examine, and, optionally, modify outgoing request messages and incoming response messages. Being able to do so is useful for, among other things, making copies of outbound messages for later auditing.
Within a service, the dispatchers are defined by three classes. The class System.ServiceModel.Dispatcher.DispatchOperation
represents the operation-specific dispatcher components. The class System.ServiceModel.Dispatcher.DispatchRuntime
represents the dispatcher components at the endpoint level. The class System.ServiceModel.Dispatcher.ChannelDispatcher
defines the service-level channel dispatchers.
Among the several properties of the System.ServiceModel.Dispatcher.DispatchRuntime
class for attaching endpoint behaviors are these:
The InstanceContextProvider
property can be assigned an instance of a class that implements System.ServiceModel.Dispatcher.IInstanceContextProvider
, which can be used for managing state information.
An object that implements the System.ServiceModel.Dispatcher.IDispatchOperationSelector
interface can be assigned to the OperatonSelector
property to determine, based on the addressing of a request message, the operation to which that message is to be dispatched.
MessageInspectors
property can be used to attach message inspectors to examine incoming request messages. Those message inspectors would implement the System.ServiceModel.Dispatcher.IDispatchMessageInspector
interface.
The InstanceProvider
property can be assigned an instance of a class that implements System.ServiceModel.Dispatcher.IInstanceProvider
, which can be used to manage instances of service types.
The System.ServiceModel.Dispatcher.DispatchOperation
class has three properties for attaching operation-specific behaviors:
Objects that implement the System.ServiceModel.Dispatcher.IDispatchMessageFormatter
interface can be assigned to the Formatter
property to deserialize incoming messages into data items and to serialize outbound responses into messages. Such objects are referred to as dispatch message formatters.
The ParameterInspectors
property is a collection of System.ServiceModel.Dispatcher.IParameterInspector
objects. Those parameter inspector objects can be used to examine and optionally modify the data deserialized from incoming messages by the dispatch message formatter, as well as to examine and possibly modify outbound response data.
A custom operation invoker can be assigned to the OperationInvoker
property. An operation invoker implements the System.ServiceModel.Dispatcher.IOperationInvoker
interface. It is used to invoke the method of the service that implements the operation, passing it the data deserialized from the incoming message as parameters.
This analysis of the kinds of behaviors one can provide also yields an understanding of how the Windows Communication Foundation’s service model works. Starting from the point at which a Windows Communication Foundation developer’s code invokes a method of a Windows Communication Foundation client, the following sequence of events occurs:
The client operation selector determines to which operation of the service to direct a request, based on which method of the client was invoked.
Any parameter inspectors attached to the client runtime components specific to that operation get to see the data that the developer’s code is passing as arguments to the operation, and can modify that data, too. Parameter inspectors might be used to confirm that the values of outbound data items fall within a specific range, and to adjust them if they do not. They might also perform transformations on the values of certain data items.
The client message formatter serializes the data items into XML, and writes the XML into the body of a Windows Communication Foundation message.
The System.ServiceModel.Channels.Message
object representing the message is passed to the client runtime component at the endpoint level.
The client message inspector is permitted to examine and optionally modify the System.ServiceModel.Channels.Message
object representing the message.
The message is passed to the Windows Communication Foundation’s Channel Layer for delivery. More specifically, it is passed to the top channel in the stack of channels that the Windows Communication Foundation assembled in accordance with the binding selected for the endpoint.
The message is received by the service and passed from the Channel Layer to the channel dispatcher, which passes it on to the dispatcher component of the appropriate endpoint.
The instance context provider retrieves any state information.
Based on the addressing of the message, the dispatch operation selector determines to which operation, among those defined by the service contract, the message pertains.
The dispatch message inspector is permitted to examine and optionally modify the System.ServiceModel.Channels.Message
object representing the incoming message.
The instance provider creates or retrieves the instance of the service type that will be passed the data extracted from the message.
The message is passed to the dispatcher component for the operation identified by the dispatch operation selector.
The dispatch message formatter for the operation deserializes the body of the message into an array of data items.
Parameter inspectors attached to the dispatcher components for the operation are permitted to examine and optionally modify the data items.
The operation invoker for the operation invokes the method of the service by which the operation is implemented, passing the data items deserialized from the body of the message as arguments to the method.
If the method returns data, the parameter inspectors attached to the dispatcher components for the operation are allowed to look at and modify that data.
The dispatch message formatter for the operation serializes the data returned by the method into a System.ServiceModel.Channels.Message
object that represents the response to the message received from the client.
That response message is passed to the dispatcher component at the endpoint level.
The dispatch message inspector is permitted to examine and modify the response message.
The instance context provider is allowed to persist or discard the state information.
The instance provider is given the opportunity to dispose of the instance of the service type that it created or retrieved to process the message.
The response message is passed to the channel dispatcher, which passes it on for transmission to the uppermost channel in the hierarchy of channels that the Windows Communication Foundation builds in accordance with the binding selected for the endpoint.
The response message is received by the client and passed from its Channel Layer to the client runtime component for the receiving endpoint.
The client message inspector of that client runtime component examines and optionally modifies the response message.
The client operation selector identifies the operation to which the response message pertains based on the addressing of the message.
The response message is passed to the client runtime component for that operation.
The client message formatter deserializes the body of the response message into an array of data items.
That array of data items is passed to any parameter inspectors attached to the client runtime component of the operation for examination and optional modification.
The data items are provided to the client developer’s code as values returned by the operation that code invoked.
The second step in implementing a custom behavior is to attach the custom behavior to an operation, if it is operation specific, or to an endpoint if it is not operation specific. To attach a custom behavior to an operation, implement the System.ServiceModel.Description.IOperationBehavior
interface. To attach a custom behavior to an endpoint, implement the System.ServiceModel.Description.IEndpointBehavior
interface.
One might ask, “If the System.ServiceModelDispatcher.IClientMessageFormatter
interface, for example, signifies that a behavior serializes outgoing data, and if that is, by definition, an operation-specific function, why doesn’t the System.ServiceModelDispatcher.IClientMessageFormatter
interface derive from the System.ServiceModel.Description.IEndpointBehavior
interface?” The answer is that by keeping the two interfaces separate, one class that implements the System.ServiceModelDispatcher.IClientMessageFormatter
interface can be attached to an operation by another class that implements the System.ServiceModel.Description.IEndpointBehavior
interface. Of course, one class could also implement both interfaces, and attach itself to the operation.
For the behavior to attach itself to a client runtime component or to a dispatcher at either the operation or endpoint level, the behavior must be given access to the client runtime components and the dispatchers. Informing the Windows Communication Foundation Service Model of a custom behavior will cause it to pass the appropriate client runtime component or dispatcher to the custom behavior so that it can attach itself to one of them.
The preceding steps for implementing a custom behavior are actually very simple to execute. As an example, consider writing a class that is to serve as a client message inspector.
The first step would be to have the class declare what sort of behavior it will be providing. It is meant to be a message inspector, and classes declare themselves to be client message inspectors by implementing the System.ServiceModel.Dispatcher.IClientInspector
interface:
public class MyMessageInspector: IClientMessageInspector { public void AfterReceiveReply( ref System.ServiceModel.Channels.Message reply, object correlationState) { } public object BeforeSendRequest( ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel) { return null; } }
The second step in implementing a client message inspector is to attach it to the client runtime. Message inspectors attach to the client runtime components at the endpoint level. That is accomplished by implementing the System.ServiceModel.Description.IEndpointBehavior
interface, as illustrated in Listing 13.1. That interface has an ApplyClientBehavior()
method for attaching to the client endpoint runtime, as well as an ApplyDispatchBehavior()
method for attaching to the endpoint dispatcher.
Example 13.1. Attaching a Behavior to the Client Endpoint Runtime
public class MyMessageInspector: IClientMessageInspector, IEndpointBehavior { public void AddBindingParameters( ServiceEndpoint serviceEndpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior( ServiceEndpoint serviceEndpoint, ClientRuntime behavior) { behavior.MessageInspectors.Add(this); } public void ApplyDispatchBehavior( ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint serviceEndpoint) { } [...] }
The remaining step in implementing a client behavior is to inform the Windows Communication Foundation Service Model of the existence of the behavior. That step is required in order for the Windows Communication Foundation to give the custom behavior access to the client runtime so that the custom behavior can attach itself to the client runtime. The step can be accomplished in code or through configuration.
The constructor of this Windows Communication Foundation client informs the Windows Communication Foundation service model of the existence of a custom behavior that is to be attached to the client endpoint runtime:
public class Client: ClientBase<ISimple>, ISimple { public Client(string endpointConfigurationName) : base(endpointConfigurationName) { IEndpointBehavior endpointBehavior = new MyMessageInspector(); base.Endpoint.Behaviors.Add(endpointBehavior); } [...] }
Having been thus informed, the Windows Communication Foundation Service Model will pass the custom behavior the client endpoint runtime via that custom behavior’s implementation of the ApplyClientBehavior()
method of the System.ServiceModel.Description.IEndpointBehavior
interface.
In the statement that informs the Windows Communication Foundation Service Model of the behavior
base.Endpoint.Behaviors.Add(endpointBehavior);
the expression, Endpoint
, is an object of the type System.ServiceModel.Description.ServiceEndpoint
. The object represents the Windows Communication Foundation’s description of the endpoint. Remember that when a Windows Communication Foundation application starts, the Windows Communication Foundation examines the code and the configuration to determine how the application is to communicate. The information gleaned from that examination is stored in a System.ServiceModel.Description.ServiceDescription
object, which represents a description of how a Windows Communication Foundation client or service is to communicate. A System.ServiceModel.Description.ServiceEndpoint
is an element of that description. As its name suggests, it is an element that describes an endpoint. From this snippet,
public class Client: ClientBase<ISimple>, ISimple { public Client(string endpointConfigurationName) : base(endpointConfigurationName) { [...] base.Endpoint.Behaviors.Add(endpointBehavior); } [...] }
it should be evident that the ClientBase<
T
>
generic makes the description of the endpoint available to the client programmer. Developers writing services can access the Windows Communication Foundation’s description of the service via the Description
property of the System.ServiceModel.ServiceHost
class, thus:
ServiceHost host = new ServiceHost(typeof(Service)); host.Description.Endpoints[0].Behaviors.Add( new MyMessageInspector());
How would the developer of a service to be hosted in IIS access the description, then, for when a service is hosted in IIS one does not need to construct an instance of the System.ServiceModel.ServiceHost
class? To access the properties of the service’s host when a service is hosted in IIS, one refers, in the .svc
file for the service, not directly to the service type, as one usually does
<%@ServiceHost Service="MyServiceType" %>
but rather to a type that derives from the abstract class, System.ServiceModel.Activation.ServiceHostFactoryBase
:
<%@ServiceHost Factory="MyServiceHostFactory" %>
Types that derive from that abstract base must override its abstract method, CreateServiceHost()
:
public class MyServiceHostFactory: ServiceHostFactoryBase { public override ServiceHostBase CreateServiceHost( string constructorString, Uri[] baseAddresses) { ServiceHost host = new ServiceHost(baseAddresses); return host; } [...] }
In overriding that method, one is able to construct the host for the service, and access the members thereof.
To inform the Windows Communication Foundation Service Model of the existence of a custom behavior through configuration, it is necessary to provide a class that derives from the abstract base class System.ServiceModel.Configuration.BehaviorExtensionElement
. Listing 13.2 provides an example.
The abstract members of System.ServiceModel.Configuration.BehaviorExtensionElement
, the ones that must be implemented, are the BehaviorType
property and the CreateBehavior()
method. The BehaviorType
property informs the Windows Communication Foundation of the type of a custom behavior, and the CreateBehavior()
method provides an instance of that type, an instance of the custom behavior.
Given an implementation of the abstract base class, System.ServiceModel.Configuration.BehaviorExtensionElement
, one can use it to inform the Windows Communication Foundation of a custom behavior through configuration. A sample configuration is provided in Listing 13.3. In that configuration, the behaviorExtensions
element
<behaviorExtensions> <add name="myCustomElement" type="Extensibility.MyBehaviorExtensionElement, BehaviorExtensionElement , Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </behaviorExtensions>
defines an extension to the Windows Communication Foundation configuration language for identifying an endpoint behavior. That extension takes the form of a new element called myCustomElement
. The element is implemented by the type, Extensibility.MyBehaviorExtensionElement
, the definition of which was shown in Listing 13.2. It is identified in the configuration by a complete assembly-qualified name. In this case, a complete assembly-qualified name is mandatory, and that name must be on a single, unbroken line, which is impossible to reproduce here.
Having defined an extension to the Windows Communication Foundation configuration language for identifying an endpoint behavior, that extension can be used to configure a Windows Communication Foundation client, which, in the configuration in Listing 13.3, is done in this way:
<client> <endpoint address="http://localhost:8000/SimpleService/Endpoint" binding="basicHttpBinding" contract="Extensibility.ISimple" behaviorConfiguration="SimpleServiceEndpointBehavior" name="SimpleService" /> </client> <behaviors> <endpointBehaviors> <behavior name="SimpleServiceEndpointBehavior"> <myMessageInspector/> </behavior> </endpointBehaviors> </behaviors>
Here, the expression
<client> <endpoint [...] behaviorConfiguration="SimpleServiceEndpointBehavior"
signifies that a set of behaviors with the arbitrary name SimpleServiceEndpointBehavior
apply to the client endpoint that is being configured. The inclusion of the previously defined extension to the Windows Communication Foundation configuration language, the element called myCustomElement
, within that set of behaviors, performs the crucial step of informing the Windows Communication Foundation Service Model of the existence of the custom behavior.
<behaviors> <endpointBehaviors> <behavior name="SimpleServiceEndpointBehavior"> <myCustomElement/>
When the Windows Communication Foundation parses the configuration and finds that element, it loads the type associated with the element, which is the type Extensibility.MyBehaviorExtensionElement
. Knowing that the type derives from System.ServiceModel.Configuration.BehaviorExtensionElement
, the Windows Communication Foundation calls the type’s CreateBehavior()
method, and is given an instance of the custom endpoint behavior, MyMessageInspector
. Because that type is indeed a custom endpoint behavior, and because it was applied to the configuration of a client endpoint, the client runtime components for that endpoint will be passed to the ApplyClientBehavior()
method of MyMessageInspector
. When that method is called, the MyMessageInspector
object attaches itself to the client endpoint runtime.
Example 13.3. Informing of a Custom Behavior Through Configuration
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <extensions> <behaviorExtensions> <add name="myCustomElement" type="Extensibility.MyBehaviorExtensionElement, BehaviorExtensionElement, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </behaviorExtensions> </extensions> <client> <endpoint address="http://localhost:8000/SimpleService/Endpoint" binding="basicHttpBinding" contract="Extensibility.ISimple" behaviorConfiguration="SimpleServiceEndpoint Behavior" name="SimpleService"/> </client> <behaviors> <endpointBehaviors> <behavior name="SimpleServiceEndpointBehavior"> <myCustomElement/> </behavior> </endpointBehaviors> </behaviors> <bindings> <customBinding> <binding name="SimpleServiceBinding"> <httpTransport/> </binding> </customBinding> </bindings> </system.serviceModel> </configuration>
If the foregoing explanation of how to identify custom behaviors to the Windows Communication Foundation was complicated to follow, then, alas, there are some additional details of which one should be aware. First, it might be necessary to pass data to a custom behavior through the configuration. In that case, the class that derives from System.ServiceModel.Configuration.BehaviorExtensionElement
must define those data items as System.Configuration.ConfigurationProperty
members, and is responsible for passing the values of members to the instance of the custom behavior that it supplies to the Windows Communication Foundation, as shown in Listing 13.4. In this way, any data items can be passed to a custom behavior through configuration, as in this example:
<behaviors> <endpointBehaviors> <behavior name="SimpleServiceEndpointBehavior"> <myMessageInspector myProperty="whatever"/> </behavior> </endpointBehaviors> </behaviors>
Example 13.4. Defining Custom Configurable Properties in a Behavior Extension Element
public class MyBehaviorExtensionElement: BehaviorExtensionElement { private ConfigurationPropertyCollection properties = null; [ConfigurationProperty( "myProperty", DefaultValue = "", IsRequired = true)] public string MyProperty { get { return (string)base["myProperty"]; } set { base["myProperty"] = value; } } protected override ConfigurationPropertyCollection Properties { get { if(this.properties == null) { this.properties = new ConfigurationPropertyCollection(); this.properties.Add( new ConfigurationProperty( "myProperty", typeof(string), "", ConfigurationPropertyOptions.IsRequired)); } return properties; } } public override void CopyFrom(ServiceModelExtensionElement from) { base.CopyFrom(from); MyBehaviorExtensionElement element = (MyBehaviorExtensionElement)from; this.MyProperty = element.MyProperty; } protected override object CreateBehavior() { return new MyMessageInspector(this.MyProperty); } [...] }
A further detail is that only behaviors that apply to endpoints can be directly identified to the Windows Communication Foundation through configuration, not behaviors that apply to operations. To identify a custom operation behavior to the Windows Communication Foundation through configuration, have the behavior implement the System.ServiceModel.Description.IEndpointBehavior
as an endpoint behavior would. Then identify it to the Windows Communication Foundation through configuration as if it were an endpoint behavior. When the Windows Communication Foundation invokes the behavior’s implementation of the System.ServiceModel.Description.IEndpointBehavior
interface’s ApplyClientBehavior()
or ApplyDispatchBehavior()
, one has the opportunity to attach the custom behavior to the client operation runtime or the operation dispatcher:
public void ApplyClientBehavior( ServiceEndpoint serviceEndpoint, ClientRuntime behavior) { behavior.Operations[0].ParameterInspectors.Add( this); } public void ApplyDispatchBehavior( ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher) { endpointDispatcher.DispatchRuntime.Operations[0].ParameterInspectors.Add( this); }
Now the three steps for implementing a custom behavior—declaring what type of behavior it is, attaching it to a client runtime component or a dispatcher, and informing the Windows Communication Foundation of its existence—will be shown for each of the various types of custom behaviors. For the simplicity of the exposition, in each case, the simpler option of informing the Windows Communication Foundation of the existence of the custom behavior through code will be illustrated, rather than the option of doing so through configuration.
Operation selectors can be applied to both clients and services.
Here are the steps for applying an operation selector to a client.
Declare. Implement the System.ServiceModel.Dispatcher.IClientOperationSelector
interface:
public class MyOperationSelector: IClientOperationSelector { public string SelectOperation( MethodBase method, object[] parameters) { //Select the operation based on the name of the method. } [...] }
Attach. Implement the ApplyClientBehavior()
method of the System.ServiceModel.Description.IEndpointBehavior
interface:
public class MyOperationSelector: IClientOperationSelector, IEndpointBehavior { public void ApplyClientBehavior( ServiceEndpoint serviceEndpoint, ClientRuntime behavior) { behavior.OperationSelector = this; } [...] }
Inform. In the code for a Windows Communication Foundation client, add the operation selector to the Behaviors
collection of a System.ServiceModel.Description.ServiceEndpoint
object:
public class Client: ClientBase<ISimple>, ISimple { public Client(string endpointConfigurationName) : base(endpointConfigurationName) { base.Endpoint.Behaviors.Add( new MyOperationSelector()); } [...] }
Here are the steps for applying an operation selector to a service.
Declare. Implement the System.ServiceModel.Dispatcher.IDispatchOperationSelector
interface:
public class MyOperationSelector : IDispatchOperationSelector { public string SelectOperation( refMessage message) { //Select the operation based on the Action header of the message. } }
Attach. Implement the ApplyDispatchBehavior()
method of the System.ServiceModel.Description.IEndpointBehavior
interface:
public class MyOperationSelector : IDispatchOperationSelector, IEndpointBehavior { public void ApplyDispatchBehavior( ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher) { endpointDispatcher.DispatchRuntime.OperationSelector = this; } }
Inform. In the code for a Windows Communication Foundation service host, add the instance context provider to the Behaviors
collection of a System.ServiceModel.Description.EndpointDescription
object:
ServiceHost host = new ServiceHost(typeof(Service)); host.Description.Endpoints[0].Behaviors.Add( new MyOperationSelector());
Parameter inspectors can be applied to both clients and services.
Here are the steps for applying a parameter inspector to a client.
Declare. Implement the System.ServiceModel.Dispatcher.IParameterInspector
interface:
public class MyParameterInspector: IParameterInspector { public void AfterCall( string operationName, object[] outputs, object returnValue, object correlationState) { //Inspect return values here. } public object BeforeCall( string operationName, object[] inputs) { //Inspect parameters here. return null; } }
Attach. Implement the ApplyClientBehavior()
method of the System.ServiceModel.Description.IOperationBehavior
interface:
public class MyParameterInspector: IParameterInspector, IOperationBehavior { public void ApplyClientBehavior( OperationDescription description, ClientOperation proxy) { proxy.ParameterInspectors.Add(this); } [...] }
Inform. In the code for a Windows Communication Foundation client, add the parameter inspector to the Behaviors
collection of the System.ServiceModel.Description.OperationDescription
object corresponding to whichever operation’s parameters are to be inspected:
public class Client: ClientBase<ISimple>, ISimple { public Client(string endpointConfigurationName) : base(endpointConfigurationName) { base.Endpoint.Contract.Operations[0].Behaviors.Add( new MyParameterInspector()); } [...] }
Here are the steps for applying a parameter inspector to a service.
Declare. Parameter inspectors are declared in the same way for services as they are for clients: by implementing the System.ServiceModel.Dispatcher.IParameterInspector
interface.
Attach. Implement the ApplyDispatchBehavior()
method of the System.ServiceModel.Description.IParameterInspector()
interface:
public class MyParameterInspector: IOperationBehavior, IParameterInspector { public void ApplyDispatchBehavior( OperationDescription description, DispatchOperation dispatch) { dispatch.ParameterInspectors.Add(this); } [...] }
Inform. In the code for a Windows Communication Foundation host, add the parameter inspector to the Behaviors
collection of the System.ServiceModel.Description.OperationDescription
object corresponding to whichever operation’s parameters are to be inspected:
ServiceHost host = new ServiceHost(typeof(Service)); host.Description.Endpoints[0].Contract.Operations[0].Behaviors.Add( new MyParameterInspector());
Message formatters can be applied to both clients and services.
Here are the steps for applying a message formatter to a client.
Declare. Implement the System.ServiceModel.Dispatcher.IClientMessageFormatter
interface:
public class MyMessageFormatter: IClientMessageFormatter { public Message SerializeRequest( MessageVersion messageVersion, object[] parameters) { //Serialize request data items into a request message here. } public object DeserializeReply( Message message, object[] parameters) { //De-serialize response message here. } }
Attach. Implement the ApplyClientBehavior()
method of the System.ServiceModel.Description.IOperationBehavior
interface:
public class MyMessageFormatter: IClientMessageFormatter, IOperationBehavior { public void ApplyClientBehavior( OperationDescription description, ClientOperation proxy) { proxy.Formatter = this; } [...] }
Inform. In the code for a Windows Communication Foundation client, add the message formatter to the Behaviors
collection of the System.ServiceModel.Description.OperationDescription
object corresponding to whichever operation’s parameters are to be serialized by the message formatter:
public class Client: ClientBase<ISimple>, ISimple { public Client(string endpointConfigurationName) : base(endpointConfigurationName) { base.Endpoint.Contract.Operations[0].Behaviors.Add( new MyMessageFormatter()); } [...] }
Here are the steps for applying a message formatter to a service.
Declare. Implement the System.ServiceModel.Dispatcher.IDispatchMessageFormatter
interface:
public class MyMessageFormatter: IDispatchMessageFormatter { public void DeserializeRequest( Message message, object[] parameters) { //De-serialize request message here. } public Message SerializeReply( MessageVersion messageVersion, object[] parameters, object result) { //Serialize response data items into a response message here. } }
Attach. Implement the ApplyDispatchBehavior()
method of the System.ServiceModel.Description.IOperationBehavior
interface:
public class MyMessageFormatter: IDispatchMessageFormatter, IOperationBehavior { public void ApplyDispatchBehavior( OperationDescription description, DispatchOperation dispatch) { dispatch.Formatter = this; } [...] }
Inform. In the code for a Windows Communication Foundation service host, add the message formatter to the Behaviors
collection of the System.ServiceModel.Description.OperationDescription
object corresponding to whichever operation’s parameters are to be serialized by the message formatter:
ServiceHost host = new ServiceHost(typeof(Service)); host.Description.Endpoints[0].Contract.Operations[0].Behaviors.Add( new MyMessageFormatter());
Message inspectors can be applied to both clients and services.
Here are the steps for applying a message inspector to a client.
Declare. Implement the System.ServiceModel.Dispatcher.IClientMessageInspector
interface:
public class MyMessageInspector: IClientMessageInspector { public object BeforeSendRequest( ref Message request, IClientChannel channel) { //Inspect outbound request message here. } public void AfterReceiveReply( Message reply, object correlationState) { //Inspect inbound response message here. } }
Attach. Implement the ApplyClientBehavior()
method of the System.ServiceModel.Description.IEndpointBehavior
interface:
public class MyMessageInspector: IClientMessageInspector, IEndpointBehavior { public void ApplyClientBehavior( ServiceEndpoint serviceEndpoint, ClientRuntime behavior) { behavior.MessageInspectors.Add(this); } [...] }
Inform. In the code for a Windows Communication Foundation client, add the message inspector to the Behaviors
collection of the System.ServiceModel.Description.EndpointDescription
object corresponding to whichever endpoint’s messages are to be inspected:
public class Client: ClientBase<ISimple>, ISimple { public Client(string endpointConfigurationName) : base(endpointConfigurationName) { base.Endpoint.Behaviors.Add( new MyMessageInspector()); } [...] }
Here are the steps for applying a message inspector to a service.
Declare. Implement the System.ServiceModel.Dispatcher.IDispatchMessageInspector
interface:
public class MyMessageInspector: IDispatchMessageInspector { public object AfterReceiveRequest( ref Message request, IClientChannel channel, InstanceContext instanceContext) { //Inspect inbound request message here. } public void BeforeSendReply( ref Channels.Message reply, object correlationState) { //Inspect outbound response message here. } }
Attach. Implement the ApplyDispatchBehavior()
method of the System.ServiceModel.Description.IEndpointBehavior
interface:
public class MyMessageInspector: IDispatchMessageInspector, IEndpointBehavior { public void ApplyDispatchBehavior( ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher) { endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this); } [...] }
Inform. In the code for a Windows Communication Foundation service host, add the message inspector to the Behaviors
collection of the System.ServiceModel.Description.EndpointDescription
object corresponding to whichever endpoint’s messages are to be inspected:
ServiceHost host = new ServiceHost(typeof(Service)); host.Description.Endpoints[0].Behaviors.Add( new MyMessagInspector());
Instance context providers can be applied to services.
Here are the steps for applying an instance context provider to a service.
Declare. Implement the System.ServiceModel.Dispatcher.IInstanceContextProvider
interface:
public class MyInstanceContextProvider : IInstanceContextProvider { public InstanceContext GetExistingInstanceContext( Message message, IContextChannel channel) { //Retrieve previously initialized instance context. } public void InitializeInstanceContext( InstanceContext instanceContext, Message message, IContextChannel channel) { //Initialize instance context. } [...] }
Attach. Implement the ApplyDispatchBehavior()
method of the System.ServiceModel.Description.IEndpointBehavior
interface:
public class MyInstanceContextProvider : IInstanceContextProvider, IEndpointBehavior { public void ApplyDispatchBehavior( ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher) { endpointDispatcher.DispatchRuntime.InstanceContextProvider = this; } [...] }
Inform. In the code for a Windows Communication Foundation service host, add the instance context provider to the Behaviors
collection of a System.ServiceModel.Description.EndpointDescription
object:
ServiceHost host = new ServiceHost(typeof(Service)); host.Description.Endpoints[0].Behaviors.Add( new MyInstanceContextProvider());
Instance providers can be applied to services.
Here are the steps for applying an instance provider to a service.
Declare. Implement the System.ServiceModel.Dispatcher.IInstanceProvider
interface:
public class MyInstanceProvider : IInstanceProvider { public object GetInstance( InstanceContext instanceContext, Message message) { //Retrieve or create the service instance here. } public object GetInstance( InstanceContext instanceContext) { //Retrieve or create the service instance here. } public void ReleaseInstance( InstanceContext instanceContext, object instance) { //Store or dispose of the service instance. } }
Attach. Implement the ApplyDispatchBehavior()
method of the System.ServiceModel.Description.IEndpointBehavior
interface:
public class MyInstanceProvider : IInstanceProvider, IEndpointBehavior { public void ApplyDispatchBehavior( ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher) { endpointDispatcher.DispatchRuntime.InstanceProvider = this; } [...] }
Inform. In the code for a Windows Communication Foundation service host, add the instance context provider to the Behaviors
collection of a System.ServiceModel.Description.EndpointDescription
object:
ServiceHost host = new ServiceHost(typeof(Service)); host.Description.Endpoints[0].Behaviors.Add( new MyInstanceProvider());
Operation invokers can be applied to services.
Here are the steps for applying an operation invoker to a service.
Declare. Implement the System.ServiceModel.Dispatcher.IOperationInvoker
interface:
public class MyOperationInvoker: IOperationInvoker { public object Invoke( object instance, object[] inputs, out object[] outputs) { //Invoke a method of the service. } [...] }
Attach. Implement the ApplyDispatchBehavior()
method of the System.ServiceModel.Description.IOperationBehavior
interface:
public class MyOperationInvoker : IOperationInvoker, IOperationBehavior { public void ApplyDispatchBehavior( OperationDescription description, DispatchOperation dispatch) { dispatch.Invoker = this; } [...] }
Inform. In the code for a Windows Communication Foundation service host, add the instance context provider to the Behaviors
collection of a System.ServiceModel.Description.OperationDescription
object:
ServiceHost host = new ServiceHost(typeof(Service)); host.Description.Endpoints[0].Contract.Operations[0].Behaviors.Add( new MyOperationInvoker());
The custom behaviors dealt with thus far in this chapter have all been behaviors applied to a client runtime component or a dispatcher at either the endpoint or operation level. However, there is a useful type of custom behavior that is a little different. It is a WSDL export extension, which is useful for modifying the WSDL that the Windows Communication Foundation generates to describe a service.
A WSDL export extension gets attached to the description of either a contract or an endpoint. When a request for the metadata of a service is received, the Windows Communication Foundation generates that metadata using a System.ServiceModel.Description.WsdlExporter
object. That object identifies any WSDL export extensions attached to the service’s endpoints or to any of the contracts thereof, and gives them the opportunity to add to or modify the WSDL that gets provided in response to the request.
Here are the steps for implementing a WSDL export extension.
To provide a WSDL export extension, create a class and declare that it is a WSDL export extension by implementing the System.ServiceModel.Description.IWsdlExportExtension
interface:
public class MyContractBehavior: IWsdlExportExtension, IContractBehavior { public void ExportContract( WsdlExporter exporter, WsdlContractConversionContext context) { //Modify the WSDL represented by the context parameter here. } public void ExportEndpoint( WsdlExporter exporter, WsdlEndpointConversionContext context) { //Modify the WSDL represented by the context parameter here. } }
Specify that the WSDL export extension attaches to an endpoint by having it implement the System.ServiceModel.Description.IEndpointBehavior
interface, or specify that it attaches to a contract by having it implement the System.ServiceModel.Description.IContractBehavior
interface.
public class MyContractBehavior: IWsdlExportExtension, IContractBehavior { public void AddBindingParameters( ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior( ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime) { } public void ApplyDispatchBehavior( ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime) { } public void Validate( ContractDescription contractDescription, ServiceEndpoint endpoint) { } [...] }
In this case, no code needs to be written in the implementations of any of the methods of the interfaces. The WSDL export extension does not have to actively attach itself to an endpoint or contract in the way that the custom behaviors discussed previously in this chapter did. Implementing the interface simply declares whether it is to an endpoint, or to a contract, or to both of those, that the WSDL export extension attaches.
If the WSDL export extension is to be attached to an endpoint, it implements the System.ServiceModel.Description.IEndpointBehavior
interface, and the Windows Communication Foundation can be informed of it in code or in configuration in the same way as the other endpoint-level behaviors described in this chapter. However, if the WSDL export extension is to be attached to a contract, it is a contract behavior, and the Windows Communication Foundation cannot be informed about those through configuration. It can be informed of them through imperative code:
host.Description.Endpoints[0].Contract.Behaviors.Add( new MyContractBehavior());
The other option is to have the contract behavior also be a custom attribute by having it derive from System.Attribute
[AttributeUsage(AttributeTargets.Interface)] public class MyContractBehavior: Attribute, IWsdlExportExtension, IContractBehavior { [...] }
and then it can be applied to the contract declaratively:
[MyContractBehavior] [ServiceContract] public interface ISimple { [...] }
The sample code provided for this chapter provides a simple implementation of every type of custom behavior that has been described. So, to see all the custom behaviors at work, follow these steps:
Copy the code associated with this chapter downloaded from http://www.cryptmaker.com/WindowsCommunicationFoundationUnleashed to the folder C:WCFHandsOn. The code is all in a folder called CustomBehaviors
.
Open the Visual Studio 2005 solution, CustomBehaviors.sln. The solution contains a project called ServiceHost for building a console application to host the Windows Communication Foundation service built from the project called Service. The project called Client is for building a console application that is a Windows Communication Foundation client of the service. The projects called CustomServiceBehaviors and CustomClientBehaviors are for building class libraries that contain the custom behaviors applied to the service and the client. A project called BehaviorExtensionElement is for building a class library that contains a class used for identifying one of the custom client behaviors to the Windows Communication Foundation through configuration.
Choose Build, Build Solution from the Visual Studio 2005 menus.
Choose Debug, Start Debugging from the Visual Studio 2005 menus. Consoles for the ServiceHost and Client applications should open.
When the output in the console of the service host indicates that the service is ready, navigate a browser to the address http://localhost:8000/SimpleService?wsdl. Doing so will generate a request for the service’s metadata, and in the process of responding, the Windows Communication Foundation will use a WSDL export extension that adds this WSDL import statement to the WSDL that gets produced:
<wsdl:import namespace="http://someorganization.org/somenamespace/" location="http://someorganization.org/somenamespace/their.wsdl"/>
The WSDL export extension also announces its having been used in the console of the service host.
Enter a keystroke into the console of the client application, and observe the output in the both the console of the client application and the console of the service host. What will have happened is that a request will have been sent from the client to the service, and each custom behavior attached to the client runtime components and the dispatchers will have reported its involvement in the process with output to the console. Thus, one can see not only the evidence of the custom behaviors having been used, but also the order in which they are activated.
Choose Debug, Stop Debugging from the Visual Studio 2005 menus to terminate the applications.
This chapter has presented the first set of options for extending the Windows Communication Foundation—the addition of custom behaviors to the Windows Communication Foundation Service Model. All the various types of custom behaviors that can be attached to clients and services were enumerated. The role of each custom behavior was explained, and its sequential position within the Service Model’s processing was identified. The steps for implementing each type of custom behavior were explained and demonstrated.