Controlling Client Communications


You have now seen how to configure the channel stack for a service and how to create a behavior that can modify the way in which the WCF runtime processes messages. In this section, you will examine how to programmatically connect a client application to a service, send messages, and process responses.

Connecting to a Service Programmatically

When a client application runs and connects to a service, the WCF runtime creates an infrastructure that is a simplified mirror of that created for the service. When you use a proxy object to connect to a service, behind the scenes the WCF runtime creates a binding object using the binding elements specified in the configuration file, and an endpoint based on the selected endpoint definition, and then uses these items to construct a ChannelFactory object. The WCF runtime uses the ChannelFactory object to instantiate the channel stack and connect it to the URI specified by the endpoint. When the client application invokes operations through the proxy object, the WCF runtime routes these requests through the channel stack and transmits them to the service. When a response message arrives from the service, it passes back up through the channel stack to the WCF runtime, and the proxy object then passes it back to the client application code.

You can create a client proxy class for a service by using the svcutil utility to query the metadata for the service and generate an assembly that you can add to the client project (you have performed this task at regular intervals during the exercises in this book). For security reasons, the administrator managing the host computer running a WCF service can elect to disable service metadata publishing. However, the WCF service developer can distribute an assembly containing the service contract, and you can use this to create a proxy object instead of using svcutil. In the next exercise, you will see how to connect to a service by using the service contract to create a proxy object at runtime.

Note 

You can also use the svcutil to generate the proxy class from an assembly containing the service interface (you will use this technique in later chapters), but performing this task by using code provides you with additional flexibility and helps you to understand how the proxy class generated by using svcutil actually works.

Connect to the ProductsService service by using a service contract

  1. In Visual Studio 2005, close the ShoppingCartService solution and then open the ProductsService solution in the Microsoft Press\WCF Step By Step\Chapter 10\ProductsServiceV2 folder under your \My Documents folder.

    This solution contains a copy of the ProductsService service and ProductsServiceHost application from Chapter 6, “Maintaining Service Contracts and Data Contracts,” and a version of the ProductsClient application that has most of the code from the image from book Program.cs file removed. The application configuration file and the file containing the proxy class definition have also been removed from the client application.

  2. In Solution Explorer, open the image from book ProductsServiceContract.cs file in the ProductsClient project and examine its contents. This file contains the definition of the service contract for the ProductsService service, IProductsServiceV2, and the associated data contracts (it was copied from the image from book ProductsService.cs file in the ProductsService project). It does not contain any code that implements the service.

  3. In Solution Explorer, open the image from book Program.cs file in the ProductsClient project. This file contains the basic framework for the client application in the Main method, but the code that connects to the service and invokes operations is currently missing.

  4. Add the following using statement to the list at the top of the file:

     using System.ServiceModel.Security;

  5. In the Main method, in the try block, add the statements shown in bold below:

     … try {     NetTcpBinding tcpBinding = new NetTcpBinding(SecurityMode.Message);     NetTcpSecurity tcpSec = tcpBinding.Security;     tcpSec.Message.AlgorithmSuite = SecurityAlgorithmSuite.Basic128;     tcpSec.Message.ClientCredentialType = MessageCredentialType.Windows; }  

    The NetTcpBinding class implements the standard TCP binding. The ProductsService service exposes a TCP endpoint with the URI, “net.tcp://localhost:8080/TcpProductsService.” If you examine the application configuration file for the ProductsServiceHost project, you will see that the binding configuration for the service endpoint uses message level security, with 128-bit encryption of messages and Windows authentication. The code you have just added sets the corresponding security properties for the NetTcpBinding object in the client application.

  6. Add the following statement:

     EndpointAddress address = new EndpointAddress(     "net.tcp://localhost:8080/TcpProductsService");

    The EndpointAddress object encapsulates the address that the client application uses to communicate with the service.

  7. Add the code shown below:

     Products.IProductsServiceV2 proxy =     ChannelFactory<Products.IProductsServiceV2>.CreateChannel(         tcpBinding, address);

    The generic ChannelFactory class creates a channel by calling the static CreateChannel method. The new channel uses the binding specified in the first parameter and connects to the address provided in the second parameter. The value returned is a reference to the channel just created. A channel has a type based on the service contract. In this case, the channel is assigned to a variable of type Products.IProductsServiceV2. Remember that IProductsServiceV2 is the interface implemented by the service contract in the image from book ProductsServiceContract.cs file. You can create channels based on any interface that is annotated with the ServiceContract attribute.

  8. You can now invoke methods through the proxy variable. Add the following statements that invoke the ListSelectedProducts operation to retrieve a list of bicycle frames and display the results:

     Console.WriteLine("Test 1: List all bicycle frames"); List<string> productNumbers = proxy.ListSelectedProducts("Frame"); foreach (string productNumber in productNumbers) {     Console.WriteLine("Number: " + productNumber); } Console.WriteLine();

    There is one very subtle difference between this code and the corresponding code you used in Chapter 6: the value returned by the ListSelectedProducts method is now returned as a List<string> object rather than the array of strings passed back when using the generated proxy.

  9. Close the connection to the service by setting the proxy variable to null, as follows:

     proxy = null;

    The complete code for the Main method should look like this (comments have been added to help clarify the code):

     static void Main(string[] args) {     Console.WriteLine("Press ENTER when the service has started");     Console.ReadLine();     try     {         // Create the TCP binding and configure security         NetTcpBinding tcpBinding = new NetTcpBinding(SecurityMode.Message);         NetTcpSecurity tcpSec = tcpBinding.Security;         tcpSec.Message.AlgorithmSuite = SecurityAlgorithmSuite.Basic128;         tcpSec.Message.ClientCredentialType =             MessageCredentialType.Windows;                      // Create an endpoint         EndpointAddress address = new EndpointAddress(             "net.tcp://localhost:8080/TcpProductsService");                      // Build the channel for communicating with the service         Products.IProductsServiceV2 proxy =             ChannelFactory<Products.IProductsServiceV2>.CreateChannel(                 tcpBinding, address);                          // Obtain a list of all bicycle frames         Console.WriteLine("Test 1: List all bicycle frames");         List<string> productNumbers = proxy.ListSelectedProducts("Frame");         foreach (string productNumber in productNumbers)         {             Console.WriteLine("Number: " + productNumber);         }         Console.WriteLine();         // Close the connection to the service         proxy = null;     }     catch (Exception e)     {         Console.WriteLine("Exception: {0}", e.Message);     }     Console.WriteLine("Press ENTER to finish");     Console.ReadLine(); }

  10. Build the solution, and then exit Visual Studio 2005.

Before you run the client application, you must make one configuration change to the security of your computer: you must add your user account to the WarehouseStaff group. This is because the ProductsService service expects the user requesting the ListSelectedProducts operation to be a member of this group.

Configure security and test the client application

  1. On the Windows Start menu, right-click My Computer and then click Manage.

    The Computer Management console appears.

  2. In the Computer Management console, expand the Local Users and Groups node in the System Tools folder, and then click the Groups folder. In the right pane, right-click the WarehouseStaff group and then click Add to Group.

    The WarehouseStaff Properties window appears.

  3. In the WarehouseStaff Properties window, click Add.

    The Select Users window appears.

  4. In the Select Users window, enter the name of your own user account and then click OK.

  5. In the WarehouseStaff Properties window, click OK.

  6. Close the Computer Management console.

  7. Log off from Windows and log back in again.

    This step is necessary for Windows to recognize your new membership of the WarehouseStaff group.

  8. Start Visual Studio 2005, and open the ProductsService solution again.

  9. Start the solution without debugging. In the ProductsServiceHost form, click Start. In the client console window, press Enter.

    The client application connects to the service, requests a list of bicycle frames, and displays the results, like this:

    image from book

  10. Press Enter in the client console window to close the client application. In the ProductsServiceHost form, click Stop and then close the form.

image from book
Using the ClientBase Abstract Class

In earlier chapters, you used the ClientCredentials property of the proxy object to specify the credentials to send to the service. The Products.IProductsServiceV2 interface does not include this functionality. If you need to provide credentials other than your current Windows identity, you must define a class that extends the System.ServiceModel.ClientBase generic abstract class. This class incorporates the client-side ChannelFactory infrastructure through a series of constructors. You can expose whichever of the base class constructors are appropriate for your situation. The class should also provide an implementation of the interface that defines service contract. You can use the Channel property of the base class to route method calls through the channel to the service in each method implementing the service interface. The code below shows an example, creating a ClientBase class based on the Products.IProductsServiceV2 service contract and implementing the methods of the Products.IProductsServiceV2 interface. This example also implements one of the ten available ClientBase constructors:

 class ProductsServiceProxy : ClientBase<Products.IProductsServiceV2>,                              Products.IProductsServiceV2 {     public ProductsServiceProxy(Binding binding, EndpointAddress address) :         base(binding, address)     {     }     #region IProductsServiceV2 Members     public List<string> ListSelectedProducts(string match)     {         return base.Channel.ListSelectedProducts(match);     }     public Products.Product GetProduct(string productNumber)     {         return base.Channel.GetProduct(productNumber);     }     public int CurrentStockLevel(string productNumber)     {         return base.Channel.CurrentStockLevel(productNumber);     }     public bool ChangeStockLevel(string productNumber, int newStockLevel,                                  string shelf, int bin)     {         return base.Channel.ChangeStockLevel(productNumber, newStockLevel,                                              shelf, bin);     }     public void UpdateProductDetails(Products.Product product)     {         base.Channel.UpdateProductDetails(product);     }     #endregion }

You can instantiate this class and use it as your proxy object. The ClientBase class provides the ClientCredentials property that you can use to specify the credentials to transmit to the service by using the following familiar code:

 ProductsServiceProxy proxy = new ProductsServiceProxy(tcpBinding, address); proxy.ClientCredentials.Windows.ClientCredential.UserName = "Fred";

 proxy.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd"; proxy.ClientCredentials.Windows.ClientCredential.Domain = "LON-DEV-01";

If you examine the code for any client proxy class generated by using the svcutil utility, you will see that it follows this approach.

image from book

Sending Messages Programmatically

A major objective of WCF is to provide a platform for interoperability. You can build WCF client applications that communicate with services created by using other, non-WCF technologies, such as Java. In this situation, if the administrator of the computer hosting the service disables service metadata publishing, the service developer is unlikely to provide you with a .NET Framework assembly containing the service contract. However, if you have documentation describing the SOAP messages that the service can accept and the responses the service emits, you can still access the service from a WCF client application; you can send messages directly through the channel. This is a very low-level, but extremely flexible approach that also gives a valuable insight into how the WCF runtime on the client converts proxy object method calls into SOAP messages. This is the subject of the final exercise in this chapter.

Send a message and process the response in the client application

  1. In Visual Studio 2005, close the ProductsService solution and then open the SimpleProductsService solution in the Microsoft Press\WCF Step By Step\Chapter 10\SimpleService folder under your \My Documents folder.

    This solution contains a simplified version of the ProductsService service called SimpleProductsService. The settings in the image from book app.config file cause the host application, SimpleProductsServiceHost, to publish the service with an HTTP endpoint using the BasicHttpBinding binding at the URI http://localhost:8040/SimpleProductsService/SimpleProductsService.svc.

  2. Open the image from book ProductsService.cs file in the SimpleProductsService project. Locate the ISimpleProductsService interface defining the service contract. It looks like this:

     // Simplified service contract [ServiceContract(Namespace="http://adventure-works.com/2006/09/30",                  Name="SimpleProductsService")] public interface ISimpleProductsService {     [OperationContract(Name = "ListProducts")]     List<string> ListProducts(); }

    The service contract defines a single operation: ListProducts (this is the same as the corresponding operation in the original ProductsService service). Note the namespace and name of the service contract. WCF uses the service contract namespace and name in conjunction with the name of the operations to define the SOAP messages, or actions, that the service publishes. In this case, the service will accept and process SOAP messages with an action of “http://adventure-works.com/2006/09/30/SimpleProductsService/ListProducts.” Also, notice that the return type is List<string>, so the service will return a SOAP message containing a serialized list of strings.

    Note 

    If you don’t want to base the name of an action on the name and namespace properties of the service contract, you can provide your own name by specifying the Action and ReplyAction properties for the OperationContract attribute. You will learn more about the Action and ReplyAction properties in Chapter 13.

  3. Edit the image from book Program.cs file in the ProductsClient project. The Main method in this project currently creates a default BasicHttpTcpBinding and an EndpointAddress object. The URI in this endpoint is http://localhost:8040/SimpleProductsService/SimpleProductsService.svc; this is the address that the SimpleProductsService is configured to listen on.

    Add the following using statement to the list at the top of the file:

     using System.ServiceModel.Channels;

  4. In the try block in the Main method, add the following statements shown in bold immediately after the code that creates the EndpointAddress object:

     … EndpointAddress address = new EndpointAddress(   "http://localhost:8040/SimpleProductsService/SimpleProductsService.svc"); IChannelFactory<IRequestChannel> factory =     httpBinding.BuildChannelFactory<IRequestChannel>(); factory.Open();  

    The first statement creates a client-side ChannelFactory object that the client application can use for sending and receiving messages. The Open method instantiates the channel factory ready for building the channel stack.

    A channel implements interfaces that specify the messaging model that it supports. A channel can be an input channel, an output channel, an input and output channel (a duplex channel), a special form of output channel known as a request channel, or an equivalent input channel known as a reply channel. These interfaces are collectively referred to as channel shapes. The shapes available to a transport channel depend on several factors, including the type of the transport channel and the current value of its properties. For example, a TCP transport channel cannot act as a reply channel if it uses the buffered transfer mode; it can only operate as a bi-directional duplex channel in this case. However, the HTTP protocol operates using a send/receive pattern, and by default the HTTP transport channel conforms to the request channel shape in a client application, and the reply channel shape in a service.

  5. The next step is to create the channel stack by using the channel factory. Add the following statements to perform this task:

     IRequestChannel channel = factory.CreateChannel(address); channel.Open();

  6. You can now send messages and receive replies through the channel stack. You create a message by using the static CreateMessage method of the Message class. Add the following statement to your code:

     Message request = Message.CreateMessage(MessageVersion.Soap11,     "http://adventure-works.com/2006/09/30/SimpleProductsService/ListProducts");

    When creating a message, you must specify the message version and a string specifying the requested action. The SOAP messaging specification has undergone several changes since it was first released, and the various bindings in WCF support different versions of the specification. The BasicHttpBinding binding is intended to be compatible with SOAP 1.1 messaging. The constant MessageVersion.Soap11 indicates that the message should be formatted according to this specification. As discussed earlier, the action string combines the namespace and name of the service with the name of the operation.

    The CreateMessage method is overloaded. This is the simplest version. Other overloads enable you to specify parameters to send to messages and to generate SOAP fault messages (useful if you are creating a service using this low-level mechanism).

  7. To send a message by using the request/response pattern, you use the Request method of the channel. Add the statements shown below to your code:

     Message reply = channel.Request(request); Console.Out.WriteLine(reply);

    The Request method blocks until a response is received from the service. The incoming response message is passed back as the return value from the Request method. After the application has received the response, the client simply displays it to the console.

  8. At this point, you can send further requests to the service, but this simple client application will simply disconnect and finish. Add these statements to your code immediately before the end of the try block.

     request.Close(); reply.Close(); channel.Close(); factory.Close();

    Messages, channels, and channel factories all consume resources, so you should close them when you have finished using them.

  9. Start the solution without debugging. In the ProductsServiceHost form, click Start. In the client application console window, press Enter.

    The client application sends the ListProducts request to the service, which responds with a message containing a list of products. The client application displays the SOAP message containing this list, which has the following format:

     <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">   <s:Header />   <s:Body>     <ListProductsResponse xmlns="http://adventure-works.com/2006/09/30">       <ListProductsResult xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">         <a:string>AR-5381</a:string>         <a:string>BA-8327</a:string>                  <a:string>VE-C304-S</a:string>         <a:string>WB-H098</a:string>       </ListProductsResult>     </ListProductsResponse>   </s:Body> </s:Envelope>

    You can use the generic GetBody<> method of the reply message to parse the contents, as described earlier in this chapter.

  10. Press Enter to close the client application console window. In the ProductsServiceHost form, click Stop and then close the form.




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