Chapter 14. Custom Channels

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

Introduction

This chapter continues the exposition of opportunities for customizing the Windows Communication Foundation. As explained in Chapter 13, “Custom Behaviors,” there are two primary ways of extending the technology to accommodate scenarios for which it does not explicitly provide. The first is through the addition of custom behaviors that control the Windows Communication Foundation’s internal communication functions, such as the function of serializing outbound data into messages.

The second primary way of extending the technology is through the addition of custom bindings. Custom bindings supplement the Windows Communication Foundation’s external communication capabilities, increasing the variety of protocols it supports for exchanging messages with external parties. The subject of extending the Windows Communication Foundation through the addition of custom binding elements will be covered in two parts. This chapter covers the implementation of generic custom binding elements. Chapter 15, “Custom Transports,” deals with the construction of a special category of custom binding element, the custom transport binding element.

Binding Elements

In developing a Windows Communication Foundation application, one uses the Windows Communication Foundation’s Service Model language to create a model of how one’s application is to communicate with other software entities. When the application prepares to send or receive data, the Windows Communication Foundation examines the model that the developer has created, and constructs the runtime components necessary to facilitate the communication.

A model of how an application is to communicate that is expressed in the language of the Windows Communication Foundation Service Model consists, at a minimum, of an address, a binding, and a contract. The binding constituent defines the protocols the application is to use to communicate. The binding constituent consists of nothing more than a list of binding elements. A binding element is a piece of software provided by the Windows Communication Foundation’s channel layer that knows how to construct runtime components that implement a particular protocol. So, in implementing a model for communication created by a Windows Communication Foundation developer, the Windows Communication Foundation examines the binding included in that model, and retrieves from it a list of binding elements. Then it has the first of those binding elements construct the runtime components it provides for supporting some protocol. That binding element is responsible for having the next binding element in the list construct its runtime components, and so on.

The runtime components that a binding element creates to provide support for a protocol are channel factories and channel listeners. Channel factories create channels for outbound communication, whereas channel listeners provide channels for inbound communication.

It is those channels that actually implement a protocol. Messages pass through the channels, and each channel manipulates the messages passing through it in accordance with the protocol that the channel implements.

Outbound Communication

When the binding element that is first in the list of binding elements specified in a binding is told by the Windows Communication Foundation to construct its runtime components in preparation for outbound communication, it creates its channel factory. In the process of initializing itself, that channel factory tells the binding element that is next in the list to create its channel factory, and that next channel factory will instruct any subsequent binding element to do the same, and so on. Consequently, what the Windows Communication Foundation receives in response to its instruction to the first binding element to prepare for outbound communication is the first in a stack of channel factories that the binding elements have cooperated in constructing.

The Windows Communication Foundation then instructs that first channel factory to create a channel for outbound communication. The channel factory responds by telling the next channel factory in the stack to create a channel, and that channel factory instructs the subsequent channel factory to construct a channel, and so on. Each channel factory takes the channel provided by the channel factory beneath, and passes that subordinate channel to the channel that it itself constructs.

That channel now has two responsibilities. Its first responsibility is to pass instructions and outbound messages on to the subordinate channel, which will in turn pass them on to its own subordinate channel. The channel’s second responsibility is to implement whatever protocol it is meant to support by performing operations on the outbound messages passing through it.

Having told the first channel factory in the stack of channel factories to create a channel for outbound communication, the Windows Communication Foundation will have received in response an outbound channel that is the first in a stack of outbound channels that the channel factories have cooperated in building. Outbound channels have standard methods for sending messages, so when a message is to be transmitted, the Windows Communication Foundation will be able to use a method of that first channel to send the message. That first channel will do to the message whatever the protocol that it implements dictates should be done, and then it will pass the message on to the next channel in the stack.

When the application that sent the message wants to dispose of the resources that the Windows Communication Foundation has provided for outbound communication, the Windows Communication Foundation instructs the first channel in the stack of channels to close, which is that channel’s opportunity to dispose of its resources. That channel will in turn tell the next channel in the stack to dispose of its resources, and that next channel will pass the instruction along to the next channel in the stack, and so on. When the first channel has finished disposing of its own resources, and detects that its subordinate channel has finished closing as well, it reports to the Windows Communication Foundation that it is indeed closed. More generally, channels are state machines, and the Windows Communication Foundation can have the channels in a stack transition to a new state by instructing the first channel in the stack to make the transition, and that first channel will pass the instruction along to the next channel, and so on.

Inbound Communication

When the binding element that is first in the list of binding elements specified in a binding is told by the Windows Communication Foundation to construct its runtime components in preparation for inbound communication, it creates its channel listener. In the process of initializing itself, that channel listener tells the binding element that is next in the list to create its channel listener, and that channel listener instructs any subsequent binding element to do the same, and so on. Consequently, what the Windows Communication Foundation receives in response to its instruction to the first binding element to prepare for inbound communication is the first in a stack of channel listeners that the binding elements have cooperated in constructing. The Windows Communication Foundation constructs a channel dispatcher to use that channel listener.

The channel dispatcher proceeds to ask the first channel listener for an inbound communication channel from which it will be able to read incoming messages. The channel listener responds by asking the next channel listener in the stack for an inbound communication channel, and so on. Each channel listener takes the channel provided by the channel listener beneath, and passes that subordinate channel to the channel that it itself constructs.

That new inbound channel now has three responsibilities. Its first responsibility is to pass on instructions to its subordinate channel, which will in turn pass them on to its own subordinate channel, and so on. Its second responsibility is to pass on incoming messages received from its subordinate channel. The inbound channel’s third responsibility is to implement whatever protocol it is meant to support by performing some operation on the incoming messages that pass through it.

Having told the first channel listener in the stack of channel listeners to provide a channel for inbound communication, the channel dispatcher will have received in response an inbound channel that is first in the a stack of inbound channels that the channel listeners have cooperated in building. Inbound channels have standard asynchronous methods for waiting for incoming messages, methods that complete when a message arrives. So, the channel dispatcher is able to use a method of the first inbound channel in the stack to wait for an incoming message. When that method completes, there will be an inbound message to process. The channel dispatcher immediately has the first inbound channel in the stack go back to waiting for the subsequent messages. Meanwhile, the channel dispatcher takes the message that it already received, determines from its address the endpoint to which the message is directed, and passes the message to the appropriate endpoint dispatcher. The endpoint dispatcher will determine the operation to which the message corresponds based on the message’s action header, and pass over to the appropriate operation dispatcher. That operation dispatcher will deserialize the message into data items, and pass those data items to an instance of a service type for processing.

When the application that is hosting the service that was receiving communications no longer wants to do so, the Windows Communication Foundation instructs the channel dispatcher to close, which in turn tells the first channel in the stack of inbound channels to close. That channel disposes of its resources, and tells the next channel in the stack of channels to dispose of its resources too. That channel will in turn tell the next channel in the stack to dispose of its resources, and so on. When the first channel has finished disposing of its own resources, and detects that its subordinate channel has finished closing as well, it reports to the channel dispatcher that it is indeed closed. Other instructions for state transitions from the channel dispatcher to the stack of inbound channels are passed on in the same way.

Channels Have Shapes

The foregoing explanation of how binding elements work used the notion of inbound communication channels and outbound communication channels for simplicity. It is more correct to say that channels have shapes. Being able to receive messages is one shape that a channel might have, whereas being able to send messages is another, and being able to reply directly to messages that have been received is yet another, and there are other shapes, too.

The fundamental shapes that a channel can assume are defined by a family of interfaces that derive from the System.ServiceModel.Channels.IChannel interface:

  • System.ServiceModel.Channels.IInputChannel is the shape of a channel for receiving messages.

  • System.ServiceModel.Channels.IOutputChannel is the shape of a channel for sending messages.

  • System.ServiceModel.Channels.IRequestChannel is the shape of a channel for sending a message for which a response is expected.

  • System.ServiceModel.Channels.IReplyChannel is the shape of a channel for sending messages in reply to request messages.

  • System.ServiceModel.Channels.IDuplexChannel implements both System.ServiceModel.Channels.IOutputChannel and System.ServiceModel.Channels.IInputChannel to define the shape of a channel that can both send and receive messages.

A channel declares its shape by implementing one or more of those interfaces:

public class MyCustomRequestChannel: IRequestChannel

Crucially, the shapes of the channels that a Windows Communication Foundation application will need for sending and receiving its messages are implied by Windows Communication Foundation contracts. For example, this contract

[ServiceContract]
public interface IEcho
{
    [OperationContract]
    string Echo(string input);
}

has an operation by which some data is received in response to data that is sent. A channel with only the shape defined by the System.ServiceModel.Channels.IOutputChannel interface would not suffice for the exchange of messages required by that operation because such a channel could not convey the response. A channel with only the shape defined by the System.ServiceModel.Channels.IInputChannel interface would not suffice either because such a channel could not transmit the data to be sent. A channel with the shape defined by the System.ServiceModel.IRequestChannel interface would be sufficient, as would a channel with the shape defined by the System.ServiceModel.Channels.IDuplexChannel interface.

In general, one can say that contracts describe a message exchange pattern—a pattern for the exchange of messages between applications. The variety of message exchange patterns that a channel can support is determined by its shape, which is defined by the System.ServiceModel.Channels.IChannel interfaces that it implements.

Channels Might Be Required to Support Sessions

Besides describing a particular message exchange pattern that the channels defined by a binding must be able to support, service contracts might or might not require that the exchange of messages take place within the context of a session. Whether or not a contract requires a session is determined by the SessionMode property of the contract:

[ServiceContract(SessionMode=SessionMode.Required)]
public interface IExchange
{
    [OperationContract(IsInitiating=true,IsTerminating=false)]
    string Start(string input);
    [OperationContract(IsInitiating=true,IsTerminating=true)]
    string Stop(string input);
}

If a contract requires that an exchange of messages takes place within the context of a session, the messages will have to convey the state of the session. For that to be possible, at least one channel through which messages are sent will have to be capable of writing information about the session into those messages, and at least one channel through which messages are received will have to be capable of reading information about the sessions from the messages. Channels that are capable of supporting sessions signal that by implementing an interface derived from the System.ServiceModel.Channels.ISession interface—System.ServiceModel.Channels.IOutputSession or System.ServiceModel.Channels.IInputSession. Versions of the shape-defining System.ServiceModel.Channels.IChannel interfaces that implement the System.ServiceModel.Channels.ISession interfaces are predefined in the System.ServiceModel.Channels namespace. For example, there is a predefined System.ServiceModel.Channels.IRequestSession interface that implements both System.ServiceModel.Channels.IRequestChannel and System.ServiceModel.Channels.IOutputSession.

Matching Contracts to Channels

So, evidently, how a contract is defined has implications for the types of channels that can be used for exchanging messages in accordance with it. Those implications of the contract are referred to as its implicit communication requirements, whereas the exchange of messages described by its operations are referred to as its explicit communication semantics.

When a Windows Communication Foundation application is preparing to send or receive data, the Windows Communication Foundation determines the implicit communication requirements of the contract, and ascertains whether those can be satisfied by the channels that can be constructed from the selected binding’s binding elements. If those requirements cannot be met, the Windows Communication Foundation throws a System.InvalidOperation exception.

How does the Windows Communication Foundation determine whether the implicit communication requirements of a contract can be satisfied by the channels that can be constructed from a binding’s binding elements? It calls the generic CanBuildChannelFactory<T>() and CanBuildChannelListener<T>() methods of the first binding element, using, as T, one of the interfaces that a channel could implement that would satisfy the contract’s implicit communication requirements. If that first binding element can construct channels that implement that interface, the first binding element calls the CanBuildChannelFactory<T>() and CanBuildChannelListener<T>() methods of the next binding element, which will then call the same methods of the next binding element, and so on. The first binding element reports the consensus back to the Windows Communication Foundation. Here are sample implementations of CanBuildChannelFactory<T>() and CanBuildChannelListener<T>():

public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
{
    if (typeof(TChannel) == typeof(IRequestChannel))
    {
        return context.CanBuildInnerChannelFactory<TChannel>();
    }
    else
    {
        return false;
    }
}

public override bool CanBuildChannelListener<TChannel>(BindingContext context)
{
    if (typeof(TChannel) == typeof(IReplyChannel))
    {
        return context.CanBuildInnerChannelListener<TChannel>();
    }
    else
    {
        return false;
    }
}

In this case, the implementation implies that the binding element can only provide output channels with the shape defined by System.ServiceModel.Channels.IRequestChannel, and can only provide input channels with the shape defined by System.ServiceModel.Channels.IReplyChannel. The System.ServiceModel.Channels.BindingContext object that is passed to both methods as a parameter provides the current binding element access to the next one.

The Windows Communication Foundation invokes the CanBuildChannelFactory<T>() and CanBuildChannelListener<T>() methods for each of the interfaces that a channel could implement to satisfy the implicit communication requirements of the contract. After having done so, it knows whether those requirements can be met by the binding elements of the selected binding.

Assuming that the implicit communication requirements of the contract can be satisfied by the binding elements, the Windows Communication Foundation then selects one of the interfaces that could be used to satisfy those requirements, and that is supported by the binding elements. In the case of a client that is preparing to send a message to a service, the Windows Communication Foundation then tells the first of the binding elements to provide a channel factory that can create channels that conform to the selected interface. To do so, it calls that binding element’s BuildChannelFactory<T>() method, using the selected interface as T:

public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(
        BindingContext context)
{
    return (IChannelFactory<TChannel>)
                (object)new MyCustomChannelFactory<TChannel>(
                        this,context);
}

In the case of a service that is preparing to listen for messages, the Windows Communication Foundation tells the first of the binding elements to provide a channel listener that can provide input channels that conform to the selected interface. To do so, it calls the binding element’s BuildChannelListener<T>() method, using the selected interface as T:

public override IChannelListener<TChannel> BuildChannelListener<TChannel>(
         BindingContext context)
{
     return(IChannelListener<TChannel>)
                 (object)new MyCustomChannelListener<TChannel>(
                         this,context);
}

It should be apparent that in providing custom binding elements, a key decision is which implicit communication requirements of contracts will be accommodated. Some binding elements might not be compatible with some contracts.

Communication State Machines

Channels, channel factories, and channel listeners are all state machines. More specifically, the Windows Communication Foundation defines a communication state machine in its System.ServiceModel.Channels.ICommunicationObject interface, and channels, channel factories, and channel listeners all implement that interface.

The interface defines a property, State, for ascertaining the current state of a communication state machine. The states are defined as Created, Opening, Opened, Faulted, Closing, and Closed.

Three methods, Open(), Close(), and Abort(), are provided for initiating state transitions. It is via these methods that the Windows Communication Foundation is able to communicate the state of an application to channel factories, channel listeners, and channels, and have them respond appropriately. In particular, the Open() method signifies that the application is preparing to send or receive messages, so a stack of channels for sending or receiving messages needs to be constructed. The Close() method is the signal to dispose of resources. The Abort() method indicates that not only should resources be disposed of, but any outstanding operations should be cancelled immediately. All communication state machines are responsible for passing on instructions to perform state transitions to the subordinate communication state machines in their stack.

The System.ServiceModel.Channels.ICommunicationObject interface also defines several events by which a communication state machine can signal a state transition. Those events are Opening, Opened, Faulted, Closing, and Closed.

Building Custom Binding Elements

To create a custom binding element to support a protocol for outbound communication, it is necessary to implement, at a minimum, the binding element itself, a channel factory, and an outbound communication channel. To support a protocol for input communication, the binding element is required along with a channel listener and an inbound communication channel.

The steps for implementing a custom binding element to support both outbound and inbound communication will be presented in the pages that follow. The starting point will be a simple Windows Communication Foundation client that sends a message to a service.

Understand the Starting Point

These steps are for getting acquainted with the starting point:

  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 CustomChannels. The completed solution is the subfolder called CompletedSolution, and the starting point for the step-by-step instructions that follow is in the subfolder called StartingPoint.

  2. Open the solution CustomChannels.sln in the StartingPoint subfolder. It consists of four projects. The Service project is for building a simple Windows Communication Foundation service. The ServiceHost project is for building a console application for hosting that service. The Client project is for building a console application that is a client of the service. The project called MyCustomBindingElement is a blank class library project to which the classes required for the custom binding element will be added.

  3. Examine the service contract in the ISimple.cs file of the Service project. It has a single operation that anticipates a reply in response to a request:

    [ServiceContract]
    public interface ISimple
    {
        [OperationContract]
        string AcceptRequest(string someRequest);
    }
    
  4. Study the code for the client application in the Program.cs file of the Client project. The application constructs a Windows Communication Foundation client using a custom binding consisting solely of an instance of one of the predefined transport binding elements, System.ServiceModel.Channels.HttpTransportBindingElement. The application then calls the client’s Open() method, and pauses for input. Then the application proceeds to use the client to send two messages to the service before calling the client’s Close() method.

    public class Program
    {
      static void Main(string[] args)
      {
        Console.WriteLine("Press any key when the service is ready.");
        Console.ReadKey(true);
        Client client = new Client(
          new CustomBinding(
            new BindingElement[]{new HttpTransportBindingElement()}),
          new EndpointAddress(
            "http://localhost:8000/SimpleService/Endpoint"));
        client.Open();
        Console.WriteLine("The client is open: press any key to continue.");
        Console.ReadKey(true);
        Console.WriteLine(client.AcceptRequest("Hello, World!"));
        Console.WriteLine(client.AcceptRequest("Hello, World!"));
        client.Close();
        Console.WriteLine("Done.");
        Console.ReadKey(true);
       }
    }
    
  5. Choose Debug, Start Debugging from Visual Studio 2005’s menus.

  6. When the console of the ServiceHost application confirms that the service is ready, enter a keystroke into the console of the client application.

  7. When the console of the client application confirms that the Windows Communication Foundation client is open, enter a keystroke into the console of the client application. The output in both consoles should confirm that the service received both messages from the client and responded to each of them.

  8. Enter a keystroke into both consoles to close the applications.

Provide a Custom Binding Element That Supports Outbound Communication

Now a custom binding element will be constructed by which some protocol could be applied to outbound messages. Code for the binding element itself, a channel factory, and a channel for outbound communication will be required.

  1. Add a class called MyCustomBindingElement.cs to the MyCustomBindingElement project.

  2. Modify the contents of the class to look like this:

    using System;
    using System.Collections.Generic;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.Text;
    
    namespace Extensibility
    {
        public class MyCustomBindingElement: BindingElement
        {
        }
    }
    

    By deriving from System.ServiceModel.Channels.BindingElement, the class signifies that it is in fact a binding element.

  3. Add a default constructor and a copy constructor:

    public MyCustomBindingElement()
    {
        Console.WriteLine("Constructing binding element.");
    }
    
    public MyCustomBindingElement(MyCustomBindingElement original)
    {
        Console.WriteLine("Copying binding element.");
    }
    
  4. Implement the abstract Clone() method using the copy constructor. That method allows the Windows Communication Foundation to make deep copies of the binding element:

    public override BindingElement Clone()
    {
        Console.WriteLine("Cloning binding element.");
        return new MyCustomBindingElement(this);
    }
    
  5. Implement the abstract, generic GetProperty<T>() method by which the Windows Communication Foundation can query the stack of binding elements for the values of properties:

    public override T GetProperty<T>(BindingContext context)
    {
        return context.GetInnerProperty<T>();
    }
    
  6. Provide an override for the key CanBuildChannelFactory<T>() method, the significance of which was explained earlier in this chapter:

    public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
    {
        Console.WriteLine(
    "Querying if the binding element can build a channel factory of type {0}.",
                    typeof(TChannel).Name);
    
        if (typeof(TChannel) == typeof(IRequestChannel))
        {
            return context.CanBuildInnerChannelFactory<TChannel>();
        }
        else
        {
            return false;
        }
    }
    
  7. Add a class called MyCustomChannelFactory.cs to the MyCustomBindingElement project.

  8. Modify the contents of that class to look like this:

    using System;
    using System.Collections.Generic;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.Text;
    
    namespace Extensibility
    {
        class MyCustomChannelFactory<TChannel> :
                    ChannelFactoryBase<IRequestChannel>
        {
        }
    }
    

    By deriving from System.ServiceModel.Channels.ChannelFactoryBase<IRequestChannel>, the class signifies that it is in fact a channel factory, and that it can provide channels with the shape defined by the System.ServiceModel.Channels.IRequestChannel interface.

  9. Recall that a channel factory is responsible for having the next binding element after its own create another channel factory—the next channel factory in the stack of channel factories that the binding elements construct. Recall, as well, that when a channel factory creates a channel, it has the next channel factory in the stack create a channel that it then provides to its own channel, so that its own channel can pass messages through to a subordinate channel. So, a channel factory has need of a reference to the next channel factory in the stack. Add a member by which to maintain that reference:

    class MyCustomChannelFactory<TChannel>:
            ChannelFactoryBase<IRequestChannel>
    {
        IChannelFactory<TChannel> innerChannelFactory = null;
    }
    
  10. Add a constructor that has the next channel factory in the stack constructed as well, and that stores the reference to that next channel factory in the member that was added in the previous step:

    public MyCustomChannelFactory(
            MyCustomBindingElement bindingElement,
            BindingContext context)
        : base(context.Binding)
    {
        Console.WriteLine("Constructing the channel factory.");
        this.innerChannelFactory =
                    context.BuildInnerChannelFactory<TChannel>();
        if (this.innerChannelFactory == null)
        {
            throw new InvalidOperationException(
            "MyCustomChannelFactory requires an inner IChannelFactory.");
        }
    }
    
  11. Right-click on the term ChannelFactoryBase<IRequestChannel> in the declaration of the class, and choose Implement Abstract Class from the context menu. Visual Studio will create stubs for the OnCreateChannel(), OnBeginOpen(), OnEndOpen(), and OnOpen() methods.

  12. Modify the implementation of the OnOpen() method so that when the Windows Communication Foundation tells the channel factory that the host application has called the Open() method of the Windows Communication Foundation client in preparation for outbound communication, the channel factory passes that information on to the next channel factory:

    protected override void OnOpen(TimeSpan timeout)
    {
            Console.WriteLine("Channel factory OnOpen() method.");
            this.innerChannelFactory.Open(timeout);
    }
    
  13. Add a class called MyCustomRequestChannel.cs to the solution, and modify the contents to look like this:

    using System;
    using System.Collections.Generic;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.Text;
    
    namespace Extensibility
    {
        class MyCustomRequestChannel: IRequestChannel
        {
        }
    }
    

    By implementing the System.ServiceModel.Channels.IRequestChannel interface, the class signifies that it is a channel with the shape defined by that interface.

  14. As a channel, it is responsible for passing instructions and messages along to the channel beneath itself in a stack of channels. Define a member for maintaining a reference to that next channel:

    class MyCustomRequestChannel: IRequestChannel
    {
            private IRequestChannel innerChannel = null;
    }
    
  15. Provide a constructor by which the channel will accept a reference to the channel beneath itself from the channel factory, and store that in the member defined in the previous step. Also have the constructor monitor events of the subordinate channel so as to be notified of changes in that channel’s state:

    public MyCustomRequestChannel(IRequestChannel innerChannel)
    {
            Console.WriteLine("Constructing request channel.");
            this.innerChannel = innerChannel;
    
            this.innerChannel.Closed += new EventHandler(innerChannel_Closed);
    }
    
  16. Add the handler for the subordinate channel’s Closed event, referred to in the constructor. Have that handler signify that because the subordinate channel is finished closing, the current channel can be considered to have closed as well.

    protected void innerChannel_Closed(object sender, EventArgs e)
    {
        Console.WriteLine("Inner request channel closed.");
        if (this.Closed != null)
        {
            this.Closed(this, e);
        }
    }
    
  17. Right-click on the term IRequestChannel in the declaration of the class and choose Implement Interface, Implement Interface from the context menu to create stubs for the methods of the System.ServiceModel.Channels.IRequestChannel interface.

  18. Replace the stub for the State property defined by the System.ServiceModel.Channels.ICommunicationObject interface from which the System.ServiceModel.Channels.IRequestChannel interface ultimately derives. That interface defines the Windows Communication Foundation communication state machine, and its State property is for retrieving the current state of such a state machine.

    public CommunicationState State
    {
        get
        {
            Console.WriteLine("Retrieving request channel state.");
            return this.innerChannel.State;
        }
    }
    
  19. Replace the stub for the Open() method of the System.ServiceModel.Channels.ICommunicationObject interface, by which a Windows Communication Foundation communication state machine can be instructed to transition into the Opened state. Recall that a channel like the one being programmed here is responsible for passing on state transition instructions from the Windows Communication Foundation to subordinate channels.

    public void Open(TimeSpan timeout)
    {
        Console.WriteLine("Request channel Open");
        this.innerChannel.Open(timeout);
    }
    
  20. Replace the stub for the Close() method of the System.ServiceModel.Channels.ICommunicationObject interface, by which a Windows Communication Foundation communication state machine can be instructed to transition into the Closed state.

    public void Close(TimeSpan timeout)
    {
        Console.WriteLine("Request channel Close");
        this.innerChannel.Close(timeout);
    }
    
  21. Replace the stub for the Request() method of the System.ServiceModel.Channels.IRequestChannel interface. It is by implementing this method that the channel will be able to serve its function of implementing some communication protocol. The method is passed an outbound message as a parameter and is able to transform the message as the protocol dictates.

    public Message Request(Message message, TimeSpan timeout)
    {
        Console.WriteLine("Request channel request.");
        Console.WriteLine("This is where I can implement my protocol.");
        return this.innerChannel.Request(message,timeout);
    }
    
  22. Now that the coding of the channel has been finished, the channel factory can be completed by having it construct an instance of the channel on cue. The channel factory must have the next channel factory in the stack create a channel that can then be passed to its own channel as the subordinate channel to which messages and instructions must be passed on. Replace the stub for the OnCreateChannel() method of the Extensibility.MyCustomChannelFactory class in the MyCustomChannelFactory.cs file:

    protected override IRequestChannel OnCreateChannel(
            EndpointAddress address,
            Uri via)
    {
        Console.WriteLine("Channel factory OnCreateChannel event.");
        IRequestChannel innerChannel =
                    (IRequestChannel)this.innerChannelFactory.CreateChannel(
                            address,
                            via);
        return new MyCustomRequestChannel(innerChannel);
    }
    
  23. With the channel factory finished, the binding element can be completed by providing an override of the important BuildChannelFactory<T>() method. That is the method by which it will be able to respond to an instruction to create the channel factory that will provide channels to participate in outbound communications. So, add this override to the Extensibility.MyCustomBindingElement class in the MyCustomBindingElement.cs file:

    public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(
            BindingContext context)
    {
        Console.WriteLine(
    "Asking the binding element for a channel factory of type {0}.",
                    typeof(TChannel).Name);
        return (IChannelFactory<TChannel>)
                    (object)new MyCustomChannelFactory<TChannel>(
                            this,context);
    }
    
  24. The binding element is now fully capable of supporting outbound communications. Choose Build, Build Solution from the Visual Studio 2005 menus to confirm that there are no errors in the code.

  25. Now proceed to edit the client application to use the binding element. Specifically, edit the statement in the Program.cs file of the Client project by which the Windows Communication Foundation client is created. Have that statement add an instance of the binding element into the list of binding elements by which the binding is defined:

    Client client = new Client(
        new CustomBinding(
            new BindingElement[] {
                new MyCustomBindingElement(),
                new HttpTransportBindingElement() }),
                new EndpointAddress(
                "http://localhost:8000/SimpleService/Endpoint"));
    
  26. Choose Debug, Start Debugging from Visual Studio 2005’s menus.

  27. When the console of the ServiceHost application confirms that the service is ready, enter a keystroke into the console of the client application.

  28. When the console of the client application confirms that the Windows Communication Foundation client is open, enter a keystroke into the console of the client application. The output in both consoles should show that the service received both messages from the client and responded to each of them. Examine the output in the client application console to confirm that, this time, each message from the client passed through the Request() method of the custom channel Extensibility.MyCustomRequestChannel. That method will have output this statement to the client application’s console, confirming that it has the opportunity to transform the outbound message in accordance with some protocol:

    This is where I can implement my protocol.
    
  29. Enter a keystroke into both consoles to close the applications.

Amend the Custom Binding Element to Support Inbound Communication

Follow the next set of instructions to enhance the custom binding element so that it could apply some protocol to inbound messages. It will be necessary to add some code to the binding element, and also to add a channel listener and an inbound communication channel. Do that now by following these steps:

  1. Start by adding this override to the binding element defined in the MyCustomBindingElement.cs file of the MyCustomBindingElement project. The override has the binding element assert that it is capable of constructing channel listeners that can provide channels that conform to the shape defined by the System.ServiceModel.Channels.IReplyChannel interface.

    public override bool CanBuildChannelListener<TChannel>(
            BindingContext context)
    {
        Console.WriteLine(
     "Querying if the binding element can build a listener of type {0}.",
                    typeof(TChannel).Name);
    
        if (typeof(TChannel) == typeof(IReplyChannel))
        {
            return context.CanBuildInnerChannelListener<TChannel>();
        }
        else
        {
            return false;
        }
    }
    
  2. For that assertion to be valid, it will be necessary to program the channel listener. Add a class called MyChannelListener.cs to the MyCustomBindingElement project and modify the contents to look like this:

    using System;
    using System.Collections.Generic;
    using System.ServiceModel.Channels;
    using System.Text;
    
    namespace Extensibility
    {
        class MyCustomChannelListener<TChannel> :
            ChannelListenerBase<IReplyChannel>
                where TChannel : class, IChannel
        {
        }
    }
    

    By deriving from System.ServiceModel.Channels.ChannelListenerBase<IReplyChannel>, the class declares that it is in fact a channel listener and that it provides channels that conform to the shape defined by the System.ServiceModel.Channels.IReplyChannel interface.

  3. Give the channel listener a member by which it can maintain a reference to the next channel listener in the stack:

    class MyCustomChannelListener<TChannel> :
        ChannelListenerBase<IReplyChannel>
            where TChannel : class, IChannel
    {
            IChannelListener<TChannel> innerChannelListener = null;
    }
    
  4. Provide a constructor by which the channel listener can obtain a reference to the next channel listener from the binding element that follows its own:

    public MyCustomChannelListener(
            MyCustomBindingElement bindingElement,
            BindingContext context)
        : base(context.Binding)
    {
        Console.WriteLine("Constructing the channel listener.");
        this.innerChannelListener =
                    context.BuildInnerChannelListener<TChannel>();
        if (this.innerChannelListener == null)
        {
            throw new InvalidOperationException(
               "MyCustomChannelListener requires an inner IChannelFactory.");
        }
    }
    
  5. Right-click on the term ChannelListenerBase<IReplyChannel> in the declaration of the class, and choose Implement Abstract Class from the context menu that appears. Visual Studio 2005 will create stubs for a number of abstract methods of the base class.

  6. Replace the stub for the OnOpen() method with this implementation by which the channel listener passes on notification that the host application has transitioned into the Opened state that is defined by the Windows Communication Foundation communication state machine:

    protected override void OnOpen(TimeSpan timeout)
    {
        Console.WriteLine("Channel listener OnOpen.");
        this.innerChannelListener.Open(timeout);
    }
    
  7. Replace the stub for the OnClose() method with an implementation that does the same for notifications of transitions into the Closed state:

    protected override void OnClose(TimeSpan timeout)
    {
        Console.WriteLine("Channel listener OnClose.");
        this.innerChannelListener.Close(timeout);
    }
    
  8. Replace the stub for the Uri property with this implementation by which the Windows Communication Foundation can retrieve the URI on which the stack of listeners is listening for incoming messages:

    public override Uri Uri
    {
        get
        {
            Console.WriteLine("Retrieving channel listener URI.");
            return this.innerChannelListener.Uri;
        }
    }
    
  9. The remainder of the work to be done in programming the channel listener is to override the methods by which it provides inbound communication channels: OnBeginAcceptChannel() and OnEndAcceptChannel(). However, before those methods can be implemented, it will be necessary to define the channel itself. Begin that task now by adding a class called MyCustomReplyChannel.cs to the MyCustomBindingElement project.

  10. Modify the contents of that class to look like this:

    using System;
    using System.Collections.Generic;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.Text;
    
    namespace Extensibility
    {
        class MyCustomReplyChannel : IReplyChannel
        {
        }
    }
    

    By implementing the System.ServiceModel.IReplyChannel interface, the channel declares that it is a channel with the shape defined by that interface.

  11. Add a member by which the channel can maintain a reference to the next channel in the channel stack:

    class MyCustomReplyChannel : IReplyChannel
    {
            private IReplyChannel innerChannel = null;
    }
    
  12. Add a constructor by which the channel accepts a reference to the next channel in the channel stack from the channel listener and stores that reference. Also have the constructor monitor the subordinate channel’s state changes to detect that channel transitioning into the Closed state.

    public MyCustomReplyChannel(IReplyChannel innerChannel)
    {
        Console.WriteLine("Constructing reply channel.");
        this.innerChannel = innerChannel;
        this.innerChannel.Closed += new EventHandler(innerChannel_Closed);
    }
    
    protected void innerChannel_Closed(object sender, EventArgs e)
    {
        Console.WriteLine("Inner reply channel closed.");
        if (this.Closed != null)
        {
            this.Closed(this, e);
        }
    }
    
  13. Right-click on the term IReplyChannel, in the declaration of the class, and choose Implement Interface, Implement Interface from the context menu that appears. Visual Studio 2005 will create stubs for the methods defined by the System.ServiceModel.Channels.IReplyChannel interface.

  14. Replace the stub for the System.ServiceModel.Channels.ICommunicationObject’s State property with this implementation that reports the state of the channel’s state machine to be whatever state its subordinate channel is in:

    public CommunicationState State
    {
        get
        {
            Console.WriteLine("Retrieving reply channel state.");
            return this.innerChannel.State;
        }
    }
    
  15. Replace the stub for the Open() method of the System.ServiceModel.Channels.ICommunicationObject with this implementation that passes on the instruction to transition to the Opened state to the next channel in the stack:

    public void Open()
    {
        Console.WriteLine("Reply channel Open");
        this.innerChannel.Open();
    }
    
  16. Do the same for the instruction to transition to the Closed state passed on by the Close() method the System.ServiceModel.Channels.ICommuncationObject interface:

    public void Close(TimeSpan timeout)
    {
        Console.WriteLine("Reply channel Close.");
        this.innerChannel.Close(timeout);
    }
    
  17. Replace the stubs for the important BeginTryReceiveRequest() and EndTryReceiveRequest() methods defined by the System.ServiceModel.Channels.IReplyChannel interface. The BeginTryReceiveRequest() method is the asynchronous method by which the channel will wait on an inbound message to be passed on by its subordinate channel. That EndTryReceiveRequest() method will execute when that happens, providing the channel with the opportunity to apply whatever protocol it implements to the inbound message. That message is available via the RequestMessage property of the context parameter.

    public IAsyncResult BeginTryReceiveRequest(
            TimeSpan timeout,
            AsyncCallback callback,
            object state)
    {
        Console.WriteLine("Reply channel BeginTryReceiveRequest.");
        return this.innerChannel.BeginTryReceiveRequest(
                    timeout,
                    callback,
                    state);
    }
    
    public bool EndTryReceiveRequest(
            IAsyncResult result,
            out RequestContext context)
    {
        Console.WriteLine("Reply channel EndTryReceiveRequest.");
    
        bool outcome = this.innerChannel.EndTryReceiveRequest(
                    result,
                    out context);
    
        Console.WriteLine(
    "This is where I can apply my protocol to the inbound message.");
        return outcome;
    }
    
  18. Now that the coding of the channel has been finished, the channel listener can be completed by programming the methods by which it will provide the channel. In the MyChannelListener.cs file of the MyCustomBindingElement project, replace the stubs for OnBeginAcceptChannel() and OnEndAcceptChannel() with these implementations. The channel listener retrieves a channel from the next listener in the stack, which is then passed to the listener’s own channel as the subordinate channel, the channel to which it is responsible for passing on instructions and from which it will receive incoming messages:

    protected override IAsyncResult OnBeginAcceptChannel(
            TimeSpan timeout,
            AsyncCallback callback,
            object state)
    {
        Console.WriteLine("Channel listener BeginAcceptChannel.");
        return this.innerChannelListener.BeginAcceptChannel(
                    timeout,
                    callback,
    
                    state);
    }
    
    protected override IReplyChannel OnEndAcceptChannel(
            IAsyncResult result)
    {
        Console.WriteLine("Channel listener EndAcceptChannel.");
        IReplyChannel replyChannel =
                    ((IReplyChannel)this.innerChannelListener.EndAcceptChannel(
                            result));
    
        if (replyChannel == null)
        {
            return null;
        }
        return new MyCustomReplyChannel(replyChannel);
    }
    
  19. The channel listener is now complete, which allows one to add the method to the binding element by which it will create instances of the channel listener. Go to the MyCustomBindingElement.cs file of the MyCustomBindingElement project, and provide this override of the BuildChannelListener<T>() method:

    public override IChannelListener<TChannel> BuildChannelListener<TChannel>(
            BindingContext context)
    {
        Console.WriteLine(
                    "Asking the binding element for a listener of type {0}.",
                            typeof(TChannel).Name);
        return (IChannelListener<TChannel>)
                    (object)new MyCustomChannelListener<TChannel>(this, context);
    }
    
  20. Choose Build, Build Solution from the Visual Studio 2005 menus to ensure that no syntactical errors have been made.

Applying a Custom Binding Element Through Configuration

Earlier, the custom binding element was added to the client application’s binding using code. It is more customary for bindings to be defined using configuration. To be able to refer to a custom binding element in the configuration of a Windows Communication Foundation application, it is necessary to extend the Windows Communication Foundation’s configuration language. Follow these steps by which it will be possible to add the newly constructed custom binding element to the binding of the service through configuration:

  1. Examine the current configuration of the service in the App.config file of the ServiceHost project of the CustomChannels solution. It is reproduced in Listing 14.1. The configuration defines a custom binding that has the arbitrary name, MyCustomBinding. That binding has a single binding element, System.ServiceModel.Channel.HttpTransportBindingElement, which is referred to by the element httpTransport in the configuration.

    Example 14.1. Initial Service Configuration

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <system.serviceModel>
            <services>
                <service name="Extensibility.Service">
                          <host>
                                  <baseAddresses>
                                          <add
    
    baseAddress="http://localhost:8000/SimpleService"/>
                                  </baseAddresses>
                          </host>
                    <endpoint address="Endpoint"
                        binding="customBinding"
                                           bindingConfiguration="MyCustomBinding"
                                           contract="Extensibility.ISimple" />
                </service>
            </services>
                    <bindings>
                            <customBinding>
                                    <binding name="MyCustomBinding">
                                            <httpTransport/>
                                    </binding>
                            </customBinding>
                    </bindings>
        </system.serviceModel>
    </configuration>
    
  2. Add a class called MyBindingExtensionElement.cs to the MyCustomBindingElement project.

  3. Modify its contents to look like this:

    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Configuration;
    using System.Text;
    
    namespace Extensibility
    {
        public class MyBindingElementExtension : BindingElementExtensionElement
        {
            public override Type BindingElementType
            {
                get
                {
                     return typeof(MyCustomBindingElement);
                }
            }
    
            protected override BindingElement CreateBindingElement()
            {
                return new MyCustomBindingElement();
            }
        }
    }
    

    Having the class derive from System.ServiceModel.Configuration.BindingElementExtensionElement signifies that it is a class that can be referred to by a custom configuration element in a Windows Communication Foundation configuration, a custom configuration element that can be used to signify a binding element. The class overrides the CreateBindingElement() method. When the Windows Communication Foundation parses the configuration and finds the custom configuration element, it will invoke that method to retrieve an instance of the binding element to which the custom configuration element is meant to refer.

  4. Now modify the App.config file of the ServiceHost project so that it looks like the configuration shown in Listing 14.2. That configuration uses a bindingElementExtension element to define a new configuration element with the arbitrary name myElement. That new configuration element is associated with the System.ServiceModel.Configuration.BindingElementExtensionElement defined in the previous step, and it is used in the new definition of the service’s binding to include the custom binding in the list of binding elements.

    Example 14.2. Extended Service Configuration

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
            <system.serviceModel>
                    <services>
                            <service name="Extensibility.Service">
                                    <host>
                                            <baseAddresses>
                                                    <add
    
    baseAddress="http://localhost:8000/SimpleService"/>
                                            </baseAddresses>
                                    </host>
                                    <endpoint address="Endpoint"
                        binding="customBinding"
                                           bindingConfiguration="MyCustomBinding"
                                           contract="Extensibility.ISimple" />
                            </service>
                    </services>
                    <bindings>
                            <customBinding>
                                    <binding name="MyCustomBinding">
                                            <myElement/>
                                            <httpTransport/>
                                    </binding>
                            </customBinding>
                    </bindings>
                    <extensions>
                            <bindingElementExtensions>
                                    <add name="myElement"
    type="Extensibility.MyBindingElementExtension, MyCustomBindingElement"/>
                            </bindingElementExtensions>
                    </extensions>
            </system.serviceModel>
    </configuration>
    
  5. Now modify the App.config file of the ServiceHost project so that it looks like the configuration shown in Listing 14.2. That configuration uses a bindingElementExtension element to define a new configuration element with the arbitrary name myElement. That new configuration element is associated with the System.ServiceModel.Configuration.BindingElementExtensionElement defined in the previous step, and it is used in the new definition of the service’s binding to include the custom binding in the list of binding elements.

  6. Choose Debug, Start Debugging from Visual Studio 2005’s menus.

  7. When the console of the ServiceHost application confirms that the service is ready, enter a keystroke into the console of the client application.

  8. When the console of the client application confirms that the Windows Communication Foundation client is open, enter a keystroke into the console of the client application. The output in both consoles should show that the service received both messages from the client and responded to each of them. Examine the output in the service host application console to confirm that, this time, each message from the client passed through the EndTryReceiveRequest() method of the custom channel, Extensibility.MyCustomReplyChannel. That method will have output this statement to the client application’s console, confirming that it has the opportunity to transform the inbound message in accordance with some protocol:

    This is where I can apply my protocol to the inbound message.
    

    Also notice that directly after that statement, and before the incoming message is actually passed to the instance of the service type, the Windows Communication Foundation has the channel go back to listening for further incoming messages.

  9. Enter a keystroke into both consoles to close the applications.

Although the custom binding element constructed in this chapter works for both inbound and outbound communication, it is not complete. In particular, its channels do not implement all the state transitions defined by the Windows Communication Foundation’s communication state machine, most notably transitions to the Faulted state. The sample, should, however, have served its purpose of showing how custom binding elements are constructed, and how they can provide channels for applying protocols to both inbound and outbound messages.

Summary

Custom bindings supplement the Windows Communication Foundation’s external communication capabilities, increasing the variety of protocols it supports for exchanging messages with external parties. New protocols are implemented in channels, but it is binding elements that are used to incorporate those channels into the stack of channels that a Windows Communication Foundation application uses to communicate. The Windows Communication Foundation examines the binding defined for an application, either through code or through configuration, and derives from that a list of binding elements. It calls on the first binding element to provide a channel factory for creating outbound communication channels, or a channel listener for providing inbound communication channels.

The channel factory and the channel listener will have the next binding element in the list provide a channel factory or a channel listener as well. In that way, channel factories get to add their outbound communication channels into a stack of such channels that the Windows Communication Foundation will use to send messages. Channel listeners add their inbound communications to a stack of channels, too, and each channel in the stack waits to receive a message from the channel beneath. When a message is sent, each outbound channel gets to apply its protocol to the message, and is responsible for passing it on to the next channel in the stack. Similarly, when a message arrives, each inbound channel applies its protocol to the incoming message, and passes the message along.

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

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