Chapter 13. Custom Behaviors

<feature><title>In This Chapter</title> </feature>

Introduction

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.

Extending the Windows Communication Foundation

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.

Extending the Service Model with Custom Behaviors

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.

Declare What Sort of Behavior You Are Providing

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:

  1. The client operation selector determines to which operation of the service to direct a request, based on which method of the client was invoked.

  2. 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.

  3. The client message formatter serializes the data items into XML, and writes the XML into the body of a Windows Communication Foundation message.

  4. The System.ServiceModel.Channels.Message object representing the message is passed to the client runtime component at the endpoint level.

  5. The client message inspector is permitted to examine and optionally modify the System.ServiceModel.Channels.Message object representing the message.

  6. 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.

  7. 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.

  8. The instance context provider retrieves any state information.

  9. 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.

  10. The dispatch message inspector is permitted to examine and optionally modify the System.ServiceModel.Channels.Message object representing the incoming message.

  11. The instance provider creates or retrieves the instance of the service type that will be passed the data extracted from the message.

  12. The message is passed to the dispatcher component for the operation identified by the dispatch operation selector.

  13. The dispatch message formatter for the operation deserializes the body of the message into an array of data items.

  14. Parameter inspectors attached to the dispatcher components for the operation are permitted to examine and optionally modify the data items.

  15. 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.

  16. 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.

  17. 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.

  18. That response message is passed to the dispatcher component at the endpoint level.

  19. The dispatch message inspector is permitted to examine and modify the response message.

  20. The instance context provider is allowed to persist or discard the state information.

  21. 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.

  22. 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.

  23. The response message is received by the client and passed from its Channel Layer to the client runtime component for the receiving endpoint.

  24. The client message inspector of that client runtime component examines and optionally modifies the response message.

  25. The client operation selector identifies the operation to which the response message pertains based on the addressing of the message.

  26. The response message is passed to the client runtime component for that operation.

  27. The client message formatter deserializes the body of the response message into an array of data items.

  28. 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.

  29. The data items are provided to the client developer’s code as values returned by the operation that code invoked.

Attach the Custom Behavior to an Operation or Endpoint

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.

Inform the Windows Communication Foundation of the Custom Behavior

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.

Implementing a Custom Behavior

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.

Declare

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;
        }
}

Attach

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)
         {
         }

         [...]
}

Inform

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.

Informing the Windows Communication Foundation of a Custom Behavior in Code

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.

Informing the Windows Communication Foundation of a Custom Behavior Through Configuration

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.

Example 13.2. A Behavior Extension Element

public class MyBehaviorExtensionElement:
         BehaviorExtensionElement
{
         public override Type BehaviorType
     {
         get
         {
             return typeof(MyMessageInspector);
         }
     }

     protected override object CreateBehavior()
     {
         return new MyMessageInspector(this.MyProperty);
     }
}

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
A Behavior Extension Element, 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,
Informing of a Custom Behavior Through Configuration 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);
}

Implementing Each Type of Custom Behavior

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 Selector

Operation selectors can be applied to both clients and services.

Client

Here are the steps for applying an operation selector to a client.

DeclareImplement 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.
    }
    [...]
}

AttachImplement 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;
        }
        [...]
}

InformIn 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());
    }

    [...]
}

Service

Here are the steps for applying an operation selector to a service.

DeclareImplement 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.
    }
}

AttachImplement 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;
    }
}

InformIn 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 Inspector

Parameter inspectors can be applied to both clients and services.

Client

Here are the steps for applying a parameter inspector to a client.

DeclareImplement 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;
        }
}

AttachImplement 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);
        }

        [...]
}

InformIn 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());
    }

    [...]
}

Service

Here are the steps for applying a parameter inspector to a service.

DeclareParameter inspectors are declared in the same way for services as they are for clients: by implementing the System.ServiceModel.Dispatcher.IParameterInspector interface.

AttachImplement 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);
        }

        [...]
}

InformIn 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 Formatter

Message formatters can be applied to both clients and services.

Client

Here are the steps for applying a message formatter to a client.

DeclareImplement 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.
        }
}

AttachImplement 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;
        }

        [...]
}

InformIn 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());
    }

    [...]
}

Service

Here are the steps for applying a message formatter to a service.

DeclareImplement 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.
        }
}

AttachImplement 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;
        }

        [...]
}

InformIn 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 Inspector

Message inspectors can be applied to both clients and services.

Client

Here are the steps for applying a message inspector to a client.

DeclareImplement 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.
        }
}

AttachImplement 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);
        }

        [...]
}

InformIn 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());
    }

    [...]
}

Service

Here are the steps for applying a message inspector to a service.

DeclareImplement 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.
        }
}

AttachImplement 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);
        }

        [...]
}

InformIn 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 Provider

Instance context providers can be applied to services.

Service

Here are the steps for applying an instance context provider to a service.

DeclareImplement 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.
    }
   [...]
}

AttachImplement 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;
    }
    [...]
}

InformIn 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 Provider

Instance providers can be applied to services.

Service

Here are the steps for applying an instance provider to a service.

DeclareImplement 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.
    }
}

AttachImplement 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;
    }
    [...]
}

InformIn 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

Operation invokers can be applied to services.

Service

Here are the steps for applying an operation invoker to a service.

DeclareImplement 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.
    }
    [...]
}

AttachImplement 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;
    }
    [...]
}

InformIn 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());

Implementing a WSDL Export Extension

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.

Implementation Steps

Here are the steps for implementing a WSDL export extension.

Declare That a Type Is 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 Whether the WSDL Export Extension Attaches to an Endpoint or to a Contract

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.

Inform the Windows Communication Foundation of the WSDL Export Extension

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
{
        [...]
}

Custom Behaviors in Action

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:

  1. 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.

  2. 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.

  3. Choose Build, Build Solution from the Visual Studio 2005 menus.

  4. Choose Debug, Start Debugging from the Visual Studio 2005 menus. Consoles for the ServiceHost and Client applications should open.

  5. 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.

  6. 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.

  7. Choose Debug, Stop Debugging from the Visual Studio 2005 menus to terminate the applications.

Summary

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.

References

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset