The WCF Service Model


WCF provides a comprehensive infrastructure for sending and receiving messages by creating a number of objects that manage and control the communications. This infrastructure is extensible, and you can augment it with your own functionality if you need to customize the way it works. For example, in Chapter 9, “Implementing Reliable Sessions,” you saw how to compose the channels provided with WCF into a custom binding. If you have a very specific requirement or need to transmit messages using a protocol that has no corresponding channel in the .NET Framework 3.0 class library, you can develop your own channel (or buy one from a third party) and then easily integrate it into your configuration without needing to modify the code for a service or client application. You can also customize other parts of the WCF infrastructure, such as the way that WCF maps incoming messages to operations. You will see some examples of this in Chapter 13, “Routing Messages.”

More Info 

Detailed discussion about creating a custom channel is beyond the scope of this book. For information about creating custom channels, see the Building Custom Channels page on the Microsoft .NET Framework 3.0 Community Web site at http://wcf.netfx3.com/content/BuildingCustomChannels.aspx.

For more information about how to add your own functionality to the WCF infrastructure, consult the topics in the “Extending WCF” section of the Microsoft Windows SDK Documentation.

This section introduces you to some of the main components in the WCF infrastructure, which is sometimes referred to as the WCF Service Model.

Services and Channels

You can think of a binding as a description of the channels in a channel stack. When a host application starts a service running, the WCF runtime uses the bindings defined for each endpoint to instantiate a ChannelListener object and a channel stack. A ChannelListener object connects an endpoint to the channel. The WCF runtime creates a ChannelListener object for each URI on which the service can accept messages. When a request message arrives at a URI, the corresponding ChannelListener object receives the message and passes it to the transport channel at the bottom of the corresponding channel stack. To the transport channel, a message is nothing more than a stream of bytes; it makes no attempt to understand the contents of the message. The transport channel passes the message to the next channel in the stack, which by convention is an encoding channel.

The purpose of an encoding channel is to parse the incoming request message and convert it into a format that the channels above it in the channel stack can understand–usually SOAP. When sending an outgoing response message, the encoding channel converts a SOAP message passed in from the channels above it in the stack into a specified format for transmission by the transport channel. The .NET Framework 3.0 class library provides encoding channels for converting between SOAP messages and plain text, binary data, and an optimized format called the Message Transmission Optimization Mechanism, or MTOM. You will learn more about MTOM in Chapter 12, “Implementing a WCF Service for Good Performance.” The transport and encoding channels are mandatory parts of a binding. Above the encoding channel, you can add channels handling reliability, security, transactions, and other non-functional aspects of SOAP messaging.

Note 

The binary encoding channel implements a WCF-specific encoding. You cannot use it to communicate with non-WCF client applications and services, so you should only use it in situations where interoperability is not an issue. MTOM is an OASIS approved specification and should be used if you need to transmit data in a binary format in an interoperable manner.

Additionally, each transport channel will load a default encoding channel if you don’t specify one in the channel stack. The HTTP and HTTPS transport channels load the text encoding channel, but the TCP channel defaults to using the binary encoding channel.

When an incoming request message reaches the top of the channel stack, a ChannelDispatcher object takes the message, examines it, and passes it to an EndpointDispatcher object that invokes the appropriate method in the service, passing the data items in the message as parameters to the method. ChannelDispatcher and EndpointDispatcher objects are created automatically when you use a ServiceHost object to run a service.

Note 

It is possible to associate multiple endpoints with the same URI. When a WCF service receives a message, the ChannelDispatcher will query the EndpointDispatcher for each endpoint in turn to establish which one, if any, can process the message. You will learn more about this process in Chapter 13.

When a method implementing an operation in a service finishes, the data it returns passes back through the channel stack to the transport channel where it is transmitted back to the client application. The WCF runtime on the client builds a similar structure to that used by the service, except that it is slightly simpler, as it does not have to listen for requests or manage multiple instances of the application in the way that a service does. The WCF runtime for the client creates a ChannelFactory object and uses this object to construct a channel from the binding definition. The proxy object in the client application is responsible for converting method calls into outgoing request messages and passing them to the channel for transmission. Incoming response messages received on the transport channel work their way back up through the channel stack, where the proxy object converts them into the format expected by the client code and returns them as the results of the original method call.

Behaviors

You can customize the way in which components in the WCF infrastructure operate by applying behaviors. For example, the .NET Framework 3.0 provides a number of built-in endpoint behaviors you can use to modify the way in which the endpoint serializes data, how it batches operations together in a transaction, the specific credentials it uses when sending messages or receiving messages, and so on. You can also attach behaviors to other objects in the WCF runtime, such as the service instance created by the ServiceHost object. As another example, WCF supplies the serviceDebug service behavior that you can use to specify that the service should transmit complete error information to the client application in the event of an exception. WCF provides different sets of behaviors for customizing different types of objects, and they each have a scope that depends on the type of object they apply to. For example, you use service behaviors for modifying the behavior of an entire service, operation behaviors for influencing the way in which individual methods in a service are invoked, and contract behaviors for affecting the way in which all operations in a contract are called. You can define your own custom behaviors, and you will see an example of a custom service behavior later in this chapter.

The .NET Framework 3.0 enables you to apply behaviors by using a configuration file, declaratively by specifying an attribute, or imperatively by adding code to a service that instantiates the behavior and sets its properties. Not all behaviors are available through all mechanisms. The general rule is that you can only change behaviors that are critical to the way in which a service functions (such as operation behaviors) by using an attribute or writing code. These behaviors are the concern of the developers building a service rather than an administrator configuring it. On the other hand, WCF exposes the behaviors that are a matter of administrative policy rather than implementation strategy through the configuration file.

Composing Channels into Bindings

The channels in a channel stack implement the various protocols and specifications used by SOAP messaging. The binding used by a client application should correspond to the binding implemented by the service that the client application communicates with; if a channel is omitted, or placed in a different position in the channel stack, there is the possibility that either the client application or the service will not be able to interpret messages correctly.

In Chapter 2, “Hosting a WCF Service,” you were first introduced to the predefined bindings in the .NET Framework 3.0, such as basicHttpBinding, wsHttpBinding, and netTcpBinding. These predefined bindings combine channels in configurations that meet the requirements of many common scenarios. The .NET Framework 3.0 contains classes that correspond to these bindings in the System.ServiceModel namespace, and these classes expose properties that enable you to configure the channels used by these bindings. You can also create your own custom bindings by combining binding elements and setting the properties of each of these binding elements to determine exactly which channels the WCF runtime uses.

You build a custom binding by adding binding elements to a CustomBinding object. The predefined bindings restrict the channels in a binding to various meaningful combinations. When you create a custom binding, you must ensure that you combine binding elements in a sensible manner. To some extent, the WCF runtime protects you and will throw an exception if, for example, you try and add two encoding binding elements to a binding. However, the WCF runtime is not able to perform complete sanity checking of your bindings, especially if you are using custom bindings. If you get it wrong, the client application and service might not understand each other’s messages, which will consequently cause faults, timeouts, and exceptions.

The order of the binding elements in a custom binding is also important. It has been mentioned before that the transport channel must be at the bottom of the stack, followed by the encoding channel. Microsoft recommends that you layer channels according to the function that they perform. Table 10-1 lists the layers and the channels appropriate to each layer. The class names for the binding element classes associated with the corresponding channels provided by the .NET Framework 3.0 in each layer are shown in italics:

Table 10-1: Recommended Channel Organization
Open table as spreadsheet

Layer

Function

Channel Binding Element Class

1 (top)

Transaction

Flow TransactionFlowBindingElement

2

Reliable Sessions

ReliableSessionBindingElement

3

Security

AsymmetricSecurityBindingElement, SymmetricSecurityBindingElement, or TransportSecurityBindingElement, and others created by factory methods in the SecurityBindingElement class.

4

Stream Upgrades

SslStreamSecurityBindingElement or WindowsStreamSecurityBindingElement

5

Encoding

BinaryMessageEncodingBindingElement, MtomMessageEncodingBindingElement, or TextMessageEncodingBindingElement

6 (bottom)

Transport

HttpTransportBindingElement, HttpsTransportBindingElement, PeerTransportBindingElement, TcpTransportBindingElement, NamedPipeTransportBindingElement, MsmqTransportBindingElement, or MsmqIntegrationBindingElement

This table shows the common binding elements that you have met in earlier chapters in this book. There are others, and you will see some of them later. Most of these binding elements are self-explanatory, but some warrant a little more explanation.

  • The SecurityBindingElement class acts as a factory for security binding elements and exposes methods that you can use to create channels that implement them. You will see an example of this in the exercise that follows this section.

  • The AsymmetricSecurityBindingElement and SymmetricSecurityBindingElement classes represent channels that implement message level security. The TransportSecurityBindingElement class represents a channel that implements transport level security. (For more information about message level and transport level security, refer back to Chapter 4, “Protecting an Enterprise WCF Service.”) However, you are more likely to use channels for specific scenarios, such as CreateAnonymousForCertificateBindingElement, which creates a symmetric binding element that supports anonymous client authentication and certificate-based server authentication. You can create these channels by using the factory methods of the SecurityBindingElement class.

  • Stream upgrades such as the SslStreamSecurityBindingElement and WindowsStreamSecurityBindingElement classes do not actually represent channels but rather objects that can modify the way in which data is transmitted over the network. You can use them in conjunction with a transport that supports a stream-oriented protocol, such as TCP and named pipes. A stream upgrade operates on a stream of data rather than individual WCF messages. For example, the WindowsStreamSecurityBindingElement class enables you to specify that data should be encrypted and/or signed before being transmitted. Another example (not currently implemented by the .NET Framework 3.0) would be to use a streaming upgrade channel that compresses the data using a specified algorithm before transmission.

When you use a configuration file to create a binding, you are not necessarily aware of which binding elements you are using. For example, when you specify a <security> element, the “mode” attribute determines whether the WCF runtime uses a message level <security binding> element or a transport level one. Setting other attributes in the <security> element enable the WCF runtime to determine exactly which of the many possible security binding elements it should use when constructing the channel. When you create a binding programmatically, you have to be explicit.

That’s the theory. The exercises that follow show how to put some of what you have just read into action.

Programmatically create and use a binding in the ShoppingCartService service

  1. Using Visual Studio 2005, open the solution file image from book ShoppingCartService.sln located in the Microsoft Press\WCF Step By Step\Chapter 10\ShoppingCartService folder under your \My Documents folder.

    This solution contains a copy of the ShoppingCartService, and ShoppingCartClient projects from Chapter 9. The ShoppingCartHost project is a little different though. The binding and service endpoint information for the ShoppingCartService service has been removed, leaving only the connection string for the AdventureWorks database. Additionally, the Main method in the image from book Program.cs file is currently empty.

  2. In Solution Explorer, edit the image from book Program.cs file in the ShoppingCartHost project. Add the following using statement to the list at the top of the file:

     using System.ServiceModel.Channels;

    The System.ServiceModel.Channels namespace contains the classes defining the various channels and bindings provided by the WCF.

  3. In the Main method in the Program class, add the following statement:

     CustomBinding customBind = new CustomBinding();

    This statement creates a new, empty custom binding object. You will add binding elements to the custom binding object in the next step.

    Note 

    If you want to use one of the standard bindings, you can create it in much the same way. For example, to create a standard HTTP binding object for the wsHttpBinding binding configuration, you could use:

     WSHttpBinding httpBind = new WSHttpBinding();

  4. The ShoppingCartService service uses transactions and requires reliable sessions. Create the binding elements that correspond to the channels that implement the transaction and reliable messaging protocols, set their properties, and then add them to the custom binding as follows:

     TransactionFlowBindingElement txFlowBindElem = new TransactionFlowBindingElement(); txFlowBindElem.TransactionProtocol = TransactionProtocol.OleTransactions; customBind.Elements.Add(txFlowBindElem); ReliableSessionBindingElement rsBindElem = new ReliableSessionBindingElement(); rsBindElem.FlowControlEnabled = true; rsBindElem.Ordered = true; customBind.Elements.Add(rsBindElem);

    The transaction flow binding element is configured to use OLE transactions; the alternative is to specify WSAtomicTransactionOctober2004, which implements the WS-AtomicTransactions specification. Refer back to Chapter 8, “Supporting Transactions,” for further details.

    The reliable sessions binding element enables flow control and ensures that the order of messages is preserved, as described in Chapter 9.

    It is worth emphasizing again that the order in which you add the elements to the custom binding is important. Binding elements higher up the channel stack must be added to the custom binding before those that occur lower down in the stack.

  5. The ShoppingCartService service also needs to implement secure conversations and replay detection. Use the SecurityBindingElement class to create a SecureConversation BindingElement, as follows:

     SecurityBindingElement secBindElem =     SecurityBindingElement.CreateSecureConversationBindingElement(         SecurityBindingElement.CreateSspiNegotiationBindingElement()); secBindElem.LocalServiceSettings.DetectReplays = true; customBind.Elements.Add(secBindElem);

    The secure conversation protocol uses a handshaking mechanism between the client application and the service to establish a security context token that both parties can use to authenticate the messages that pass between them. This handshake also needs to be secured, and the security binding element passed as a parameter to the CreateSecureBindingElement method specifies how to protect the handshake messages that flow while negotiating the security context. The code in this exercise uses SOAP SSPI negotiation to authenticate messages while handshaking (this is the default).

    After creating the security binding element, the code enables server-side replay detection before adding it to the custom binding.

  6. Add binding elements that implement a text encoding channel and a TCP transport channel, like this:

     customBind.Elements.Add(new TextMessageEncodingBindingElement()); TcpTransportBindingElement tcpBindElem = new TcpTransportBindingElement(); tcpBindElem.TransferMode = TransferMode.Buffered; customBind.Elements.Add(tcpBindElem);

    The reliable sessions channel requires that messages are buffered by the transport. The transport channel must be the last item in the custom binding.

  7. Add code to create a ServiceHost object, as follows:

     ServiceHost host = new ServiceHost(typeof(ShoppingCartService.ShoppingCartServiceImpl));

    This should be a familiar statement to you, but you now appreciate what a ServiceHost object does: it constructs the channel, it manages the lifetimes of various instances of the service defined by the specified type, and it ensures that client requests are dispatched to the correct service instance. It performs these tasks in conjunction with ChannelListener, ChannelDispatcher, and EndpointDispatcher objects that it creates by using the code you will add in the following steps.

  8. Previously, you specified the endpoint definition for the ShoppingCartService in the application configuration file, and the ServiceHost constructor used this information to construct an endpoint and a ChannelListener. You no longer have this information in the application configuration file, so add the endpoint by using code, as shown below:

     host.AddServiceEndpoint(typeof(ShoppingCartService.IShoppingCartService),     customBind, "net.tcp://localhost:9090/ShoppingCartService");

    The parameters to the AddServiceEndpoint method are the service contract that the service implements, the binding, and the URI for the listener.

  9. You can now start the service running. Add the following statements to the Main method:

     host.Open(); Console.WriteLine("Service running"); Console.WriteLine("Press ENTER to stop the service"); Console.ReadLine();

    The Open method starts a ChannelListener object listening for client requests. When a request arrives, the ChannelListener passes it to the channel. The ChannelDispatcher object retrieves the message from the top of the channel and passes it, through the EndpointDispatcher, to an instance of the ShoppingCartService service.

  10. Start the solution without debugging. In the ShoppingCartClient console window displaying the message “Press ENTER when the service has started,” press Enter.

    The client application runs exactly as before, creates a shopping cart, adds two water bottles and a mountain seat assembly, and then purchases the goods.

  11. Press Enter to close the client application console window. In the host application console window, press Enter to stop the service.

Inspecting Messages

An interesting feature of the WCF service model is the ability to intercept messages as they are dispatched to a service method, and again as they leave the service method prior to traversing the channel and being transmitted back to the client application. You can perform this task by creating a message inspector; you create a class that implements the IDispatchMessageInspector interface and insert it into the WCF infrastructure by using a behavior. The behavior that you use determines the scope of the message interception. If you specify message interception as a service behavior, all messages sent to the service will be intercepted. You can also apply message interception by using operation, endpoint, or contract behaviors, in which case interception applies only to the specified operation, endpoint, or contract.

You can implement message inspection in a client application or a service. In the exercise that follows, you will see how to create a message inspector and integrate it into the dispatch mechanism of the WCF runtime for the service. To continue in the spirit of this chapter, you will perform these tasks programmatically.

Create a message inspector for the ShoppingCartService service

  1. In Visual Studio 2005, select the ShoppingCartService project in Solution Explorer. In the Project menu, click Add Class, and add a new class file called image from book ShoppingCartInspector.cs to the project.

  2. In the image from book ShoppingCartInspector.cs file, add the following using statements to the list at the top:

     using System.ServiceModel.Dispatcher; using System.ServiceModel.Description;

  3. Modify the definition of the ShoppingCartInspector class to implement the IDispatchMessageInspector interface, as follows:

     public class ShoppingCartInspector : IDispatchMessageInspector { }

    The IDispatchMessageInspector interface defines two methods that enable you to view and modify messages flowing into and out of the service.

  4. In the code view window, right-click IDispatchMessageInspector, point to Implement Interface, and then click Implement Interface.

    Visual Studio 2005 generates stubs for the two methods in the IDispatchMessageInspector interface. These methods are called AfterReceiveRequest, which is invoked immediately before the service method is called, and BeforeReplySend, which runs when the service method has completed. Notice that the first parameter to both methods is a reference to a Message object. This is the message that has just been received or is about to be sent. The important point to realize is that you can modify the contents of this message, and any changes you make will be passed to the service method or returned to the client application depending on whether this is an inbound message (AfterReceiveRequest) or an outbound message (BeforeSendReply). For this reason, you should be especially careful that you don’t add any code that inadvertently changes the contents of messages.

  5. Remove the throw statement in the AfterReceiveRequest method, and replace it with the code shown in bold below:

     public object AfterReceiveRequest(     ref System.ServiceModel.Channels.Message request,     System.ServiceModel.IClientChannel channel,     System.ServiceModel.InstanceContext instanceContext) {     Console.WriteLine("Message received: {0}\n{1}\n\n",         request.Headers.Action, request.ToString());     return null; }

    The first statement displays the action that identifies the message, followed by the message itself.

    It is sometimes useful to be able to correlate messages in the AfterReceiveRequest method with the corresponding response sent by the BeforeSendReply method. If you examine the BeforeSendReply method, you will see that it has a second parameter called correlationState. If you need to correlate request and reply messages, you can create a unique identifier in the AfterReceiveRequest method and return it. The WCF runtime will pass this same identifier in as the correlationState parameter to the BeforeSendReplyMethod. In this example, you are not correlating request and reply messages, so the AfterReceiveRequest method simply returns null.

    Caution 

    The Message object contains a SOAP message, comprising XML text. You can use the generic GetBody<> method to parse the contents of the message and retrieve the data in the <Body> element, like this:

     System.Xml.XmlElement data =      request.GetBody<System.Xml.XmlElement>();

    However, the GetBody<> method is destructive. You can use it only once on a message, so doing this destroys the message, and the service method receives incorrect data. To examine a message safely, use the CreateBufferedCopy method of the request message to create a MessageBuffer object containing a copy of the message. You can then extract the copy of the message from this MessageBuffer object by using the CreateMessage method, like this:

     MessageBuffer requestBuffer = request.CreateBufferedCopy(10000); Message requestCopy = requestBuffer.CreateMessage();

  6. Replace the throw statement in the BeforeSendReply method with the code shown in bold below:

     public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply,     object correlationState) {     Console.WriteLine("Reply sent: {0}\n{1}\n\n",         reply.Headers.Action, reply.ToString()); }

    This statement displays the action and the reply message on the console.

You will integrate the ShoppingCartInspector into the WCF runtime by using a service behavior. Sadly, there is no built-in “IntegrateShoppingCartInspector” service behavior in WCF. Fortunately, it is not difficult to write it yourself.

Create a service behavior for the ShoppingCartService service

  1. Add the following class to the image from book ShoppingCartInspector.cs file, underneath the ShoppingCartInspector class:

     public class ShoppingCartBehavior: IServiceBehavior { }

    The IServiceBehavior interface defines three methods that a class must implement to be able to act as a service behavior in the WCF infrastructure.

  2. In the code view window, right-click IServiceBehavior, point to Implement Interface, and then click Implement Interface.

    Visual Studio 2005 adds the following three methods to the ShoppingCartBehavior class:

    • AddBindingParameters. Some behaviors can take additional data items as parameters to the binding elements, and an administrator or developer can supply this information in the BindingParameterCollection passed to this method. The WCF runtime invokes the AddBindingParameters method once for each URI that the service is listening on. The ShoppingCartBehavior service behavior does not require this facility.

    • ApplyDispatcherBehavior. This method enables you to modify the behavior of ServiceHost object hosting the service. The ServiceHost object is passed in as the second parameter to this method. Use this method to perform tasks such as adding custom error handlers or message inspector objects into the runtime. The ShoppingCartBehavior service behavior will use this method to insert the message inspector into the processing path for each EndpointDispatcher object used by the service.

    • Validate. The WCF runtime invokes this method to verify that the service meets your own custom requirements. For example, you can examine the service description passed in as the first parameter and if it does not conform to expectations (it doesn’t specify how to handle faults, for example), you can reject the contract and throw an exception. The ShoppingCartBehavior service behavior does not use this feature either.

  3. Comment out the throw statements in the AddBindingParameters and Validate methods. Replace the throw statement in the ApplyDispatchBehavior method with the code shown in bold below:

     public void ApplyDispatchBehavior(ServiceDescription serviceDescription,     System.ServiceModel.ServiceHostBase serviceHostBase) {     foreach (ChannelDispatcher chanDispatcher in         serviceHostBase.ChannelDispatchers)     {         foreach (EndpointDispatcher epDispatcher in             chanDispatcher.Endpoints)         {             epDispatcher.DispatchRuntime.MessageInspectors.Add(                 new ShoppingCartInspector());         }     } }

    This block of code iterates through each EndpointDispatcher object for each ChannelDispatcher object created by the ServiceHost object and adds a ShoppingCartInspector object into the MessageInspectors collection of each endpoint. Subsequently, whenever an EndpointDispatcher object dispatches a service method or whenever a service method returns to the EndpointDispatcher object, the message will pass through the ShoppingCartInspector object.

  4. The final step is to apply the ShoppingCartBehavior to the ShoppingCartService when it runs. Open the image from book Program.cs file in the ShoppingCartHost project and add the code shown below in bold between the statement that adds the service endpoint to the service and the statement that opens the service:

     … host.AddServiceEndpoint(typeof(ShoppingCartService.IShoppingCartService),     customBind, "net.tcp://localhost:9090/ShoppingCartService"); host.Description.Behaviors.Add(new ShoppingCartService.ShoppingCartBehavior()); host.Open();  

  5. Start the solution without debugging. In the ShoppingCartClient console window, press Enter.

    The client application runs as before (albeit a little more slowly). However, the console window running the service host now displays the SOAP messages being sent and received, like this:

    image from book

  1. Press Enter to close the client application console window. In the host application console window, press Enter to stop the service.




Microsoft Windows Communication Foundation Step by Step
Microsoft Windows Communication Foundation Step by Step (Step By Step Developer Series)
ISBN: 0735623368
EAN: 2147483647
Year: 2007
Pages: 105
Authors: John Sharp

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net