The dispatcher is a collection of types in the ServiceModel layer in a receiving application. The most important type in the dispatcher is the System.ServiceModel.Dispatcher. ChannelDispatcher type. The ChannelDispatcher type references the other dispatcher types, and the ChannelDispatcher delegates quite a bit of its work to these other types. Following are some of the tasks performed by the ChannelDispatcher and the types referenced by the ChannelDispatcher:
Creating a channel listener from a binding
Managing how channels are received from the channel listener
Managing the listening loop
Managing the lifetime of the channel listener and the resultant channel stacks
Limiting the pace at which messages are received from the channel stack (also called throttling)
Managing the the creation, lifetime, and number of service objects
Routing received messages to the intended service object instance
Deserializing meaningful objects from received messages
Using these deserialized objects to invoke a method on a service object
Serializing the return values of service object methods into reply messages
Routing reply messages to the appropriate channel stack and sending them back to the sender via that channel stack
Handling errors in the preceding tasks
Managing the execution of default and custom behaviors in the preceding tasks
Figure 10-1 summarizes the roles of a ChannelDispatcher.
Figure 10-1: The roles of a ChannelDispatcher object
The ChannelDispatcher defines over 30 members. Some of these members allow the ChannelDispatcher to do work on its own, and other members allow the ChannelDispatcher to delegate work to other dispatcher types. In general, a receiving application has a ChannelDispatcher for each address it is listening on. Because channel listeners listen for incoming messages, every ChannelDispatcher has a reference to a channel listener, and that channel listener listens on a Uniform Resource Identifier (URI) unique to that receiving application. Because a receiving application can listen on multiple URIs, a receiving application can have multiple ChannelDispatcher objects. Likewise, a single channel listener may have multiple channel dispatchers. At run time, a ChannelDispatcher must be attached to a ServiceHost object, so a ChannelDispatcher object never exists in isolation, and several of the members on the ChannelDispatcher type reference either a ServiceHost or a ServiceHostBase type. You’ll learn more about the ServiceHost type in the section “The ServiceHost Type” later in this chapter. Figure 10-2 shows the general composition of the ChannelDispatcher type.
Figure 10-2: ChannelDispatcher anatomy.
Because the ChannelDispatcher must manage the creation and the life cycle of channel managers and channel stacks, the ChannelDispatcher derives from CommunicationObject. As a result, several of the ChannelDispatcher members are CommunicationObject implementations. When one of these CommunicationObject members is invoked, the ChannelDispatcher drives the other CommunicationObject members that it references through the CommunicationObject state machine. For more information about the channel state machine, see Chapter 6, “Channels.”
The ChannelDispatcher type exposes a property named ServiceThrottle that is of type System.ServiceModel.Dispatcher.ServiceThrottle. The public API of the ServiceThrottle is very simple. It has three read/write properties: MaxConcurrentCalls, MaxConcurrentInstances, and MaxConcurrentSessions. There are no public constructors in the ServiceThrottle type. The ServiceHostBase type is the only type that instantiates a ServiceThrottle object. (More on this in the section “The ServiceHost Type,” later in this chapter.) The ServiceThrottle type limits the usage of the entire ServiceHost instance, rather than on one ChannelDispatcher. The ChanneDispatcher uses this type to limit the usage of the receiving application. Because there can be more than one ChannelDispatcher in a receiving application, the ServiceThrottle type tracks the usage of all ChannelDispatcher objects.
By default, the ServiceModel layer and the channel layer control the application of WS-Addressing headers to outgoing messages. In the normal case, the transport channel adds these headers. Some BindingElement-derived types expose a property named ManualAddressing (for example, HttpTransportBindingElement). When this property is set to true, the channel layer will not add WS-Addressing headers (for example, To, ReplyTo, RelatesTo, and so on) to outgoing messages. If you must have these headers, it’s up to you to add them to outgoing messages manually.
Remember that a ChannelDispatcher object also contains a reference to a channel listener and that a binding creates a channel listener. In other words, part of the creation of a ChannelDispatcher demands the existence of a binding, and that binding can have a TransportBindingElement whose ManualAddressing property is set to true. The value of the ManualAddressing property on that TransportBindingElement object determines the value of the ManualAddressing property on a ChannelDispatcher object.
The ChannelDispatcher can also override the value of the ManualAddressing property on a TransportBindingElement. The only reason I see for doing this is to force a receiving application to either use or not use ManualAddressing. To override the value passed from a TransportBindingElement, you have to manually change the ManualAddressing property on the ChannelDispatcher. The one catch is that the ChannelDispatcher must be in the Created state. Once the ChannelDispatcher opens, these parts of the ChannelDispatcher become immutable.
Remember that a ServiceHost object references at least one ChannelDispatcher object. That ServiceHost object is responsible for driving the state changes of the ChannelDispatcher objects it references, and the ServiceHost does not, by default, expose ChannelDispatcher objects when they are in the Created state. To access the ChannelDispatcher collection before each ChannelDispatcher transitions to the Opened state, you can subclass the ServiceHost type or you can create a custom behavior.
In my view, a better approach is to interrogate the ManualAddressing property of the BindingElement at run time and throw an exception if the value is not set to your liking. I’ll demonstrate how to do this in the section “The ServiceHost Type” later in this chapter.
ChannelDispatcher objects swallow exceptions. Because the ChannelDispatcher is near the top of the stack in a receving application, it is able to swallow exceptions from channel listeners, channels, service objects, and behaviors. This is good news if you want your application to stay “up” no matter what. In my view, this approach is like propping up a fighter with a harness in a boxing ring. With the extra help of the harness, the fighter is sure to never lose his or her footing. With this sort of rig, I might even be able to make it to the end of a round with a heavyweight champion (more than likely not, though). Sometimes, however, it is entirely appropriate to lose your footing in a boxing ring. Staying upright when you should be lying on the mat is dangerous.
I believe that if an application throws an exception and that exception is not handled, the application should crash. I don’t view this sort of behavior as a bug, but rather as some circumstance that the developers and architects did not envision, and the application can account for that circumstance in a patch or future release. If as a boxer I keep getting knocked out, either I should reevaluate my career or I should train differently. All too often, developers catch and swallow all exceptions “to keep the application from crashing” when they should really be writing better code. In my view, catching and swallowing exceptions is untenable because it casts too wide a net around what the application can recover from. At a minimum, exceptions that are swallowed should be logged to the Windows Event Log by default. Luckily, we are not stuck with this behavior, because a ChannelDispatcher object can define its own error handling characteristics via the ErrorHandlers collection. All objects in this collection implement the IErrorHandler interface. The IErrorHandler interface defines HandleError and ProvideFault methods. The ProvideFault method is used to specify the fault sent to the other messaging participant. The HandleError method is where you can specify what you want to happen (for example, Environment.FailFast) as a result of an exception thrown elsewhere in the application. If HandleError returns true, the other IErrorHandler.HandleError methods are not called.
The ChannelDispatcher exposes a collection of EndpointDispatcher objects via a property named Endpoints. Once a ChannelDispatcher pulls a Message from the channel, it then forwards the Message to an EndpointDispatcher. An EndpointDispatcher is responsible for matching a received Message to an instance of a service object and invoking a method on that service object. It is also responsible for deserializing the contents of the Message into arguments to that method and serializing the return value into a reply Message.
The EndpointDispatcher has a relatively simple anatomy composed of two major components: filters and the DispatchRuntime type. The EndpointDispatcher type defines an AddressFilter property and a ContractFilter property. These properties work together to ensure that a received message is dispatched to the correct method on a service object. The DispatchRuntime property returns an object of type DispatchRuntime, and it is responsible for selecting the method to invoke on the service object, serialization and deserialization of parameters to that method, and managing the lifetime of that object. Figure 10-3 shows the anatomy of EndpointDispatcher.
Figure 10-3: EndpointDispatcher anatomy
The AddressFilter and ContractFilter properties available on the EndpointDispatcher type derive from the System.ServiceModel.Dispatcher.MessageFilter abstract type. The MessageFilter type defines two Match methods and a CreateFilterTable method. The Match methods accept either a Message or a MessageBuffer as an argument and return a Boolean indicating whether the contents of the Message or MessageBuffer match predefined criteria.
The WCF type system provides six MessageFilter-derived types that match on different criteria: ActionMessageFilter, EndpointAddressMessageFilter, MatchAllMessageFilter, MatchNoneMessageFilter, PrefixEndpointAddressMessageFilter, and XPathMessageFilter. As its name implies, the ActionMessageFilter matches based on the Action header block of a Message. The EndpointAddressMessageFilter matches based on the To header block in a Message. The MatchAllMessageFilter matches all Message objects, and the MatchNoneMessageFilter matches no Message objects. The PrefixEndpointAddressMessageFilter is similar to the EndpointAddressMessageFilter, but the URI used in the comparison is used as a prefix for the match (similar to wildcards). This means that the To header block of a Message can be more specific than the URI used in the PrefixEndpointAddressMessageFilter and the filter will still match the Message. The XPathMessageFilter matches any part of the Message based an an XML Path Language (XPath) expression.
Once a ChannelDispatcher uses the filters to match a Message to an EndpointDispatcher, it forwards the Message to the DispatchRuntime in that EndpointDispatcher. The DispatchRuntime then manages the lifetime of the service object that will ultimately be the target of the Message, passes the Message through a list of MessageInspector instances, selects the method on the service object to dispatch the Message to, and then dispatches the Message to a method on the service object. Like the ChannelDispatcher and the EndpointDispatcher, the DispatchRuntime delegates quite a bit of work to other types. The types related to the instancing work are IInstanceProvider, IInstanceContextProvider, and InstanceContext. The types that inspect Message objects implement the IDispatchMessageInspector interface. The type that selects the method on the service object implements the IDispatchOperationSelector interface. Last but certainly not least, the type responsible for dispatching the Message to a particular method on the service object is the DispatchOperation type. Figure 10-4 shows the anatomy of DispatchRuntime.
Figure 10-4: DispatchRuntime anatomy
The purpose of the InstanceContext related types is to manage the creation and lifetime of the service object. In general, service objects are wrapped by contextual information. This contextual information helps route a Message to the appropriate object, and this is particularly important with sessions. Each channel layer session might need to map to a unique instance of a service object, and the context around the service object provides the mechanism for this mapping. All of the InstanceContext related types are grouped via the interface that they implement.
Types that implement the IInstanceProvider interface are responsible for creating and returning an actual instance of the service object. Within the WCF type system, there are three not publicly visible IInstanceProvider types. One is for creating a COM+ service object (for COM+ interop), another is for creating a service object as a result of a duplex callback, and another is for the normal creation of a service object as a result of a received Message.
Types that implement the IInstanceContextProvider interface are responsible for creating and returning the contextual wrapper around the service object. WCF provides three types that implement the IInstanceContextProvider interface. The difference between these types is the way that they map received Message objects to instances of a service object. The first type maps each received Message to a new service object, the second maps received Message objects to service objects based on a session, and the third maps all received Message objects to a single service object.
The InstanceContext type is the wrapper around a service object. It derives from CommunicationObject, and as such uses the same state machine as the CommunicationObject type. Because a service object can be mapped to a particular set of channels based on the IInstanceContextProvider, the InstanceContext has references to receiving and sending channel stacks. Because the channel stacks use the ICommunicationObject state machine, the InstanceContext type must also implement the state machine.
The MessageInspectors property on the DispatchRuntime type returns a collection of types that implement the IDispatchMessageInspector interface. This interface defines two methods: AfterReceiveRequest and BeforeSendReply. The AfterReceiveRequest method allows the type to inspect the message after the request is received but before it is sent to the operation, and the BeforeSendReply allows the type to inspect the reply message before it is sent to the channel layer. The objects in the collection returned from the MessageInspectors property see all of the Message objects for the service.
The OperationSelector property of the DispatchRuntime returns a type that implements the IDispatchOperationSelector interface. This interface defines one method named SelectOperation that accepts a Message as an argument and returns a String. The String returned from the SelectOperation method is used to look up the DispatchOperation in the DispatchOperation collection. The String returned from the default IDispatchOperationSelector is the value of the Action header block in the Message.
Once the OperationSelector property returns a String, that String is used to look up the DispatchOperation associated with that String. This is done via the Operations property on the DispatchRuntime type. The Operations property returns a dictionary of DispatchOperation objects, and the key in this dictionary is, by default, the value of the Action header block associated with that operation. The value of the key in the dictionary can come from the contract (OperationContract.Action property), but it can also be set manually in code. By default, the values of the Action property on the OperationContract annotation appear as keys in this dictionary.
Once the node is found from the key, the value part of the dictionary is of type DispatchOperation. The DispatchOperation type deserializes method parameters from received Message objects, invokes a method on the service object, and serializes the return value from a service object method into a reply Message. The DispatchOperation deserializes received Message objects and serializes reply Message objects via the Formatter property. This property returns a type that implements the IDispatchMessageFormatter interface. The IDispatchMessageFormatter interface defines two methods: DeserializeRequest and SerializeReply. The DeserializeRequest method accepts a Message argument and populates an array of objects. The SerializeReply method accepts arguments of type MessageVersion, Object, and Object, and it returns a Message. The MessageVersion argument is used during the construction of the Message, and the Object argument is used to serialize the body of the Message. The Object argument consists of the parameters that were originally passed to the service object method.
The ChannelDispatcher, EndpointDispatcher, DispatchRuntime, and DispatchOperation types are never used outside the context of a ServiceHost or a ServiceHostBase type. In fact, the ChannelDispatcher will throw an InvalidOperationException if you attempt to use it on its own. The ServiceHost type is at the very top of the call stack in a receiving application, and it encapsulates the complexity of the ChannelDispatcher, EndpointDispatcher, DispatchRuntime, and DispatchOperation types. The ServiceHost type defines an easy-to-use API that simplifies the addition of listening endpoints. At run time, the ServiceHost type ultimately creates the channel listeners, channel stacks, ChannelDispatcher, EndpointDispatcher, DispatcherRuntime, and DispatchOperation. In essence, the ServiceHost type leverages the types we have examined in this book to build a coherent receiving application, thereby shielding developers from the gory details of messaging. Much has been written about the ServiceHost type, so I will not repeat it here (see Windows SDK for examples).