Accessing Services Using a Client

Client applications must create, configure, and use WCF client or channel objects to communicate with services. The WCF Client Overview topic provides an overview of the objects and steps involved in creating basic client and channel objects and using them.

This topic provides in-depth information about some of the issues with client applications and client and channel objects that may be useful depending upon your scenario.

Overview

This topic describes behavior and issues relating to:

  • Channel and session lifetimes.

  • Handling exceptions.

  • Understanding blocking issues.

  • Initializing channels interactively.

Channel and Session Lifetimes

Windows Communication Foundation (WCF) applications includes two categories of channels, datagram and sessionful.

A datagram channel is a channel in which all messages are uncorrelated. With a datagram channel, if an input or output operation fails, the next operation is typically unaffected, and the same channel can be reused. Because of this, datagram channels typically do not fault.

Sessionful channels, however, are channels with a connection to the other endpoint. Messages in a session on one side are always correlated with the same session on the other side. In addition, both participants in a session must agree that the requirements of their conversation were met for that session to be considered successful. If they cannot agree, the sessionful channel may fault.

Open clients explicitly or implicitly by calling the first operation.

Note

Trying to explicitly detect faulted sessionful channels is not typically useful, because when you are notified depends upon the session implementation. For example, because the System.ServiceModel.NetTcpBinding (with the reliable session disabled) surfaces the session of the TCP connection, if you listen to the ICommunicationObject.Faulted event on the service or the client you are likely to be notified quickly in the event of a network failure. But reliable sessions (established by bindings in which the System.ServiceModel.Channels.ReliableSessionBindingElement is enabled) are designed to insulate services from small network failures. If the session can be reestablished within a reasonable period of time, the same binding—configured for reliable sessions—might not fault until the interruption continued for a longer period of time.

Most of the system-provided bindings (which expose channels to the application layer) use sessions by default, but the System.ServiceModel.BasicHttpBinding does not. For more information, see Using Sessions.

The Proper Use of Sessions

Sessions provide a way to know if the entire message exchange is complete, and if both sides considered it successful. It is recommended that a calling application open the channel, use it, and close the channel inside one try block. If a session channel is open, and the ICommunicationObject.Close method is called once, and that call returns successfully, then the session was successful. Successful in this case means that all delivery guarantees the binding specified were met, and the other side did not call ICommunicationObject.Abort on the channel before calling Close.

The following section provides an example of this client approach.

Handling Exceptions

Handling exceptions in client applications is straightforward. If a channel is opened, used, and closed inside a try block, then the conversation has succeeded, unless an exception is thrown. Typically, if an exception is thrown the conversation is aborted.

Note

Use of the using statement (Using in Visual Basic) is not recommended. This is because the end of the using statement can cause exceptions that can mask other exceptions you may need to know about. For more information, see Use Close and Abort to release WCF client resources.

The following code example shows the recommended client pattern using a try/catch block and not the using statement.

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Microsoft.WCF.Documentation;

public class Client
{
  public static void Main()
  {
    // Picks up configuration from the config file.
    SampleServiceClient wcfClient = new SampleServiceClient();
    try
    {
      // Making calls.
      Console.WriteLine("Enter the greeting to send: ");
      string greeting = Console.ReadLine();
      Console.WriteLine("The service responded: " + wcfClient.SampleMethod(greeting));

      Console.WriteLine("Press ENTER to exit:");
      Console.ReadLine();

      // Done with service.
      wcfClient.Close();
      Console.WriteLine("Done!");
    }
    catch (TimeoutException timeProblem)
    {
      Console.WriteLine("The service operation timed out. " + timeProblem.Message);
      Console.ReadLine();
      wcfClient.Abort();
    }
    catch (FaultException<GreetingFault> greetingFault)
    {
      Console.WriteLine(greetingFault.Detail.Message);
      Console.ReadLine();
      wcfClient.Abort();
    }
    catch (FaultException unknownFault)
    {
      Console.WriteLine("An unknown exception was received. " + unknownFault.Message);
      Console.ReadLine();
      wcfClient.Abort();
    }
    catch (CommunicationException commProblem)
    {
      Console.WriteLine("There was a communication problem. " + commProblem.Message + commProblem.StackTrace);
      Console.ReadLine();
      wcfClient.Abort();
    }
  }
}

Imports System.ServiceModel
Imports System.ServiceModel.Channels
Imports Microsoft.WCF.Documentation

Public Class Client
    Public Shared Sub Main()
        ' Picks up configuration from the config file.
        Dim wcfClient As New SampleServiceClient()
        Try
            ' Making calls.
            Console.WriteLine("Enter the greeting to send: ")
            Dim greeting As String = Console.ReadLine()
            Console.WriteLine("The service responded: " & wcfClient.SampleMethod(greeting))

            Console.WriteLine("Press ENTER to exit:")
            Console.ReadLine()

            ' Done with service. 
            wcfClient.Close()
            Console.WriteLine("Done!")
        Catch timeProblem As TimeoutException
            Console.WriteLine("The service operation timed out. " & timeProblem.Message)
            Console.ReadLine()
            wcfClient.Abort()
        Catch greetingFault As FaultException(Of GreetingFault)
            Console.WriteLine(greetingFault.Detail.Message)
            Console.ReadLine()
            wcfClient.Abort()
        Catch unknownFault As FaultException
            Console.WriteLine("An unknown exception was received. " & unknownFault.Message)
            Console.ReadLine()
            wcfClient.Abort()
        Catch commProblem As CommunicationException
            Console.WriteLine("There was a communication problem. " & commProblem.Message + commProblem.StackTrace)
            Console.ReadLine()
            wcfClient.Abort()
        End Try
    End Sub
End Class

Note

Checking the value of the ICommunicationObject.State property is a race condition and is not recommended to determine whether to reuse or close a channel.

Datagram channels never fault even if exceptions occur when they are closed. In addition, non-duplex clients that fail to authenticate using a secure conversation typically throw a System.ServiceModel.Security.MessageSecurityException. However if the duplex client using a secure conversation fails to authenticate, the client receives a System.TimeoutException instead.

For more complete information about working with error information at the application level, see Specifying and Handling Faults in Contracts and Services. Expected Exceptions describes expected exceptions and shows how to handle them. For more information about how to handle errors when developing channels, see Handling Exceptions and Faults.

Client Blocking and Performance

When an application synchronously calls a request-reply operation, the client blocks until a return value is received or an exception (such as a System.TimeoutException) is thrown. This behavior is similar to local behavior. When an application synchronously invokes an operation on a WCF client object or channel, the client does not return until the channel layer can write the data to the network or until an exception is thrown. And while the one-way message exchange pattern (specified by marking an operation with OperationContractAttribute.IsOneWay set to true) can make some clients more responsive, one-way operations can also block, depending upon the binding and what messages have already been sent. One-way operations are only about the message exchange, no more and no less. For more information, see One-Way Services.

Large data chunks can slow client processing no matter what the message exchange pattern. To understand how to handle these issues, see Large Data and Streaming.

If your application must do more work while an operation completes, you should create an asynchronous method pair on the service contract interface that your WCF client implements. The easiest way to do this is to use the /async switch on the ServiceModel Metadata Utility Tool (Svcutil.exe). For an example, see How to: Call Service Operations Asynchronously.

For more information about increasing client performance, see Middle-Tier Client Applications.

Enabling the User to Select Credentials Dynamically

The IInteractiveChannelInitializer interface enables applications to display a user interface that enables the user to choose credentials with which a channel is created before the timeout timers start.

Application developers can make use of an inserted IInteractiveChannelInitializer in two ways. The client application can call either ClientBase<TChannel>.DisplayInitializationUI or IClientChannel.DisplayInitializationUI (or an asynchronous version) prior to opening the channel (the explicit approach) or call the first operation (the implicit approach).

If using the implicit approach, the application must call the first operation on a ClientBase<TChannel> or IClientChannel extension. If it calls anything other than the first operation, an exception is thrown.

If using the explicit approach, the application must perform the following steps in order:

  1. Call either ClientBase<TChannel>.DisplayInitializationUI or IClientChannel.DisplayInitializationUI (or an asynchronous version).

  2. When the initializers have returned, call either the Open method on the IClientChannel object or on the IClientChannel object returned from the ClientBase<TChannel>.InnerChannel property.

  3. Call operations.

It is recommended that production-quality applications control the user-interface process by adopting the explicit approach.

Applications that use the implicit approach invoke the user-interface initializers, but if the user of the application fails to respond within the send timeout period of the binding, an exception is thrown when the user interface returns.

See also