Designing Contracts

Summary

Programming Lifecycle

Recall that WCF allows applications to communicate whether they are on the same machine, over the Internet, or on different application platforms. The basic tasks required to build a WCF application are:

Any WCF service must be built with the principles of SOA: These services must be designed to be autonomous such that they can be independently built, deployed, managed, versioned and secured. The expectation is that a service provides only part of the functionality offered by a larger distributed application, and these services must be flexible and scalable.

Service contracts

Recall that a service contract specifies what an endpoint communicates to the outside world. In other words, a service contract tells the outside world about its:

Now to convey this kind of information, WCF services use managed attributes, interfaces to define the structure, and classes to implement the structure. For clients of these services, the resulting contract must be exported as metadata, usually WSDL and XSD. This results in a programming model that can be described as public metadata to any interested client application. The details of the underlying SOAP messages, transport protocols and security can be left to WCF.

Messages

Using interfaces, classes and methods to model service operations is very familiar when you are used to RPC-style method signatures. Other programmers however are more used to message-oriented application programming interfaces such as message queue (MSMQ), or sending unstructured XML in HTTP requests, to name a few. These programmers might be wondering where the messages are.
 
The answer is that WCF applications are built entirely on message exchanges. While class and interface methods model the service contract and enable its implementation, there is no reason why you cannot use the same structure to make messages explicit. For example, the following contract is used to create a service that sends a message and returns a message:

[ServiceContract]
public interface IContract
{
    [OperationContract(Action="*")]
    Message SendMessage(Message msg)
}

Designing Service Contracts

Recall that you define a service operation by declaring an interface method and annotating it with the [OperationContract] attribute. These annotated methods must exist within an interface declaration that is itself annotated with [ServiceContract] attribute:

[ServiceContract]
public interface IService
{
    [OperationContract] void f();
    [OperationContract] void g();
    ...
}

Any methods that do not have the [OperationContract] attribute are normal managed methods (not a service operations) and are not exposed for use by WCF clients. Like any managed method, they can only be called by objects within their declared space.

Interfaces and Classes

Without an implementation, an interface is nothing more than a grouping of related methods, likewise, a service contract without an implementation is nothing more than a grouping of operations with certain signatures. As was seen from last examples, an interface is the recommended way to declare service contracts for the following reasons:

Parameters and return values

Unlike local methods in which references to objects are possible to pass from one object to another, service operations cannot pass references to objects; they pass only copies. The significance of this is that each type used in a parameter or a return value must be serializable.
 
For the widest possible interoperability, you should mark your types with [DataContract] and [DataMember] attributes to create a data contract. A data contract is the portion of the service contract that describes the data that your service operations exchange. A data contract describes only the data transferred from one side to another - it does not describe the message that carries the data.
 
Although a typical WCF application may use [DataContract] and [DataMember] attributes to create data contracts for operations, you can use other serialization mechanisms such as the standard ISerializable, [Serializable] and IXmlSerializable. (see Specifying Data Transfer under MSDN.)

Mapping parameters and return values to exchanges

Service operations are represented by an underlying exchange of SOAP messages that transfer application data back and forth, in addition to other data required by the application to support standard security , transaction and session-related features. Because of this, the signature of a service operation dictates a certain underlying message exchange patter (MEP).

WCF supports three patterns - request/reply, one-way, and duplex message patterns:

Request/Reply

In this default message exchange pattern, a sender sends a request and receives a reply that is related to the request. For example:

[OperationContract]
string GetName(long lID);            // request-reply

[OperationContract]
voids LogMessage(string strText);   // request-reply

Note that unless you specify a different message pattern, even service operations that return void are request/replay message exchanges. The result is that unless a client invokes the operation asynchronously, the client stops processing until the return message is received, even though that message is empty in the normal case. Even though a service operation returns void and can slow client performance, there are advantages to request/reply operations that return void: the most obvious is that SOAP faults can be returned in the response message (see Specifying and Handling Faults in Contracts and Services)

One-Way

If a client of a WCF service 1) does not need to wait for the operation to process and 2) neither expects nor wants SOAP faults, then the operation can specify a one-way message pattern. A one-way message pattern is one in which the client invokes an operation and continues processing after WCF queues the message for sending by the client. This type of message exchange pattern supports event-like behaviour from a client to a service operation. One-way contracts are often used to publish notifications to many subscribers or to perform logging via a dedicated logging service.
 
To specify a one-way message exchange pattern for an operation that returns void:

OperationContract(IsOneWay=true)]
void LogMessage( string strMsg );

Providing this event-like behaviour from a service to a client requires a client application to expose a service endpoint to the service application, or to use the duplex message exchange pattern.

duplex message patterns

A duplex message exchange pattern allows both the service and client to send messages to each other independently using one-way or request/reply messaging. This form of two-way communication is useful for services that need to communicate directly with the client or for providing an asynchronous experience to either side of a message exchange including event-like behaviours. Use this pattern when the service must query the client for more information or to explicitly raise events on the client.
 
To implement a duplex pattern, you must also implement a second contract interface that contains the method declarations that are called back on the client. A duplex contract consists of a pair of one-way contracts between the client and the service. Note that a duplex contract requires a session because a context must be established to correlate the set of messages being sent between the client and the server.

  1. Create the service-side contract like the previous examples (except that the contract is one-way.)
  2. Create the client-side contract like the previous examples (except that the contract is one-way). This contract defines the set of operations that the service can invoke on the client.
  3. Link the two interfaces together by setting the CallbackContract property in the primary interface.

The following code snippets illustrate:

[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples",
                 SessionMode=SessionMode.Required,
                 CallbackContract=typeof(ICalculatorDuplexCallback))]
public interface ICalculatorDuplex
{
    [OperationContract(IsOneWay=true)] void Clear();
    [OperationContract(IsOneWay=true)] void AddTo(double n);
}

public interface ICalculatorDuplexCallback
{
    [OperationContract(IsOneWay=true)] void Equals(double result);
    [OperationContract(IsOneWay=true)] void Equation(string eqn);
}

Specifying Data Transfer in Service Contracts

This topic discusses how to ensure that data structures passed to and from service operations can be serialized. It also describes how to control serialization of both data and messages that carry data. Recall that service operations cannot pass references to objects. Instead they pass copies of the objects. This implies that each type used as a parameter must be serializable.

Enabling/Controlling serialization

Specifying how your data is serialized into XML elements enables WCF to create a description of this form in a publicly usable language - WSDL and XSD - that can be understood and processed by client applications on many platforms. Specifying how the data is serialized is very easy- .NET Framework primitive types are already serializable and when included in a WCF application, they are serialized automatically.
 
Note that no type of data member is serialized unless you explicitly apply the [DataContract] attribute. For example, if an application needed to receive the following data type in an operation:

public class Person
{
    string name;
    string nickName;
    Address address;
}

public class Address
{
    string addressLine1;
    string addressLine2;
    string addressLine3;
    string cityName;
    string postcode;
    string countryName;
}

To make both types serializable, you would apply the [DataContract] and [DataMember] attributes as in the following code example to specify which data is serialized when used with a service operation. Note that in the following example the nickName data member is not serialized for use by others. In addition, even though none of the members is public, they are all serialized as publicly available.

[DataContractAttribute]
public class Person
{
    [DataMemberAttribute] string name;
    string nickName;
    [DataMemberAttribute] Address address;
}

public class Address
{
    [DataMemberAttribute] string addressLine1;
    [DataMemberAttribute] string addressLine2;
    [DataMemberAttribute] string addressLine3;
    [DataMemberAttribute] string cityName;
    [DataMemberAttribute] string postcode;
    [DataMemberAttribute] string countryName;
}

Enabling/Controlling Streams

By default, WCF applications transfer data using buffered messages. Buffered messages are messages that must be read into memory or read from memory in their entirety before they can be used by other parts of the application. If a 100KB message arrives at a service, the operation for which the message was destined will not be invoked until the entire message has been read in from the channel.
 
Streams however, provide the ability to begin handling data whether or not all of the message has arrived. Using streams as parameters or return values for an operation lets an application transfer very large pieces of data and enables the other side of the communication to begin working with the data without waiting for all of it to arrive. (see Enabling Stream for an example)
 
Note that WCF supports the following forms of serialization:

Specifying and Handling Faults in Contracts and Services

The common way to generate faults in object-oriented languages is to use exceptions. By default, exceptions in service-oriented architectures (SOA) are not propagated from a service to its clients. In the following example, when the service throws a non-WCF exception, the client will not be able to catch it. In fact, the channel between the service and the client is aborted because a service operation threw an exception that was not expected according to the service contract:

[ServiceContract]
public class MyService
{
    [OperationContract]
    public void foo()
    {
        throw new InvalidOperationException( "Not caught by client!" );
    }
}

Empty Faults

An empty fault is an exception of type FaultException that only contains a textual description of the error. Any two way operation (IsOneWay=false) can throw an empty fault at any time, and this fault will automatically propagate back to the client. For example:

[ServiceContract]
public class MyService
{
    [OperationContract]
    public void foo()
    {
        throw new FaultException( "Caught by client!" );
    }
}

Strongly Typed Faults

The recommended way to notify any client of a problem in the service is to create strongly-typed fault exceptions (equivalent to using a specific exception type in the .NET Framework). For strongly typed faults, the contract must include fault information via the [FaultContract] attribute which is applied at the method level. To return a fault, throw a generic FaultException supplying the exception types that specified by the [FaultContract] attribute:

// Any type can be used as a fault as bong as it has a data contract
// (explicitly as shown below) or implicitly by having the [Serializable] attribute
[DataContract]
public class MyFaultException
{
    [DataMember] public string Message;         // Note: no body!
}

[ServiceContract]
public class MyService
{
    // Note that multiple [FaultContract] attributes can be specified, allowing the method
    // to throw multiple types of faults
   
[OperationContract]
    [FaultContract(typeof(MyFaultException))]

    public void foo()
    {
        // Throw a fault back to the client
        MyFaultException fault = new MyFaultException();
        fault.Message = "Some error message";
        throw new FaultException<MyFaultException>( fault);
    }
}

For convenience, you can apply the [FaultContract] attribute to an entire service contract. In this case, every operation in the contract is able to throw that type of fault:

[FaultContract(typeof(MyFaultException))]
[ServiceContract]
public interface IMyInterface
{
    [OperationContract] ...
}

Catching Faults

Catch a specific fault on the client side by catching the FaultException exception. The fault type on the client side does not have to be the same fault type on the service side, it just requires the same data contract. Note that fault types are automatically generated for you in the proxy code (for use on the client) when you import a WSDL and XSD that describe a service that contains faults.

To catch all faults, catch the FaultException or the CommunicationException (which is the parent class of FaultException VERIFY). The example in Session/Faults section illustrates all of the above concepts.

Sending all exceptions back to the client

To see on the client all faults generated by a service use the ReturnUnknownExceptionsAsFaults behaviour, either via the [ServiceBehaviour] attribute or via the ReturnUnknownExceptionsAsFaults configuration element. To catch these exceptions on the client, catch the FaultException exception.

Sending all exceptions back to the client is useful for the following reasons:

  1. Exception information does not appear in WSDL and client has no way of knowing what problems can occur on the service.
  2. Exceptions sent in this way are not interoperable.
  3. Using this feature introduces tight coupling between client and service.

Note that this mechanism is intended for debugging only and should not be used in a production environment.

Sessions

'Session' refers to data that is shared between two or more endpoints to enable some sort of functionality that would not otherwise be possible. For example, a cookie allows a web site to identify the current user. Unless this cookie is passed back to the web site, each request for a web page must be treated as if it were from a different (unknown) user.

There are many types of sessions. For example a 'security session' is one in which two endpoints have authenticated each other and agreed upon an encryption process. There are also sessions used by TCP/IP connections to ensure that if a data packet fails to arrive, it is resent until both endpoints are certain that data was sent and received or certain that data was not received.

The only kind of session that you can specify in a service contract is a Reliable Session. A WCF Reliable Session is an implementation of WS-ReliableMessaging specification which enables applications to be certain either that messages have arrived exactly once and in order, or that an error condition exists.

Because a session is a channel-level concept that the application model uses,  there is an interaction between the SessionMode enumeration (which is used by a service contract to indicate what support it has for reliable sessions) and the ServiceBehaviourAttribute.InstanceContextMode property (which specifies the number of service instances available for handling client calls). This interaction controls the association between channels and specific service objects.

Session Initiation and termination

Service operations can be marked as initiating or terminating a reliable messaging operation by setting the IsInitiating or IsTerminating properties. These properties are used to control which operations are allowed to initiate a channel on the server and which operations cause the server to close the channel after the reply message is sent.

Initiating operations can be called any number of times and in any order, as no additional channels are created. However, non-initiating operations must be called only after at least one initiating operation has been called. Calling an operation marked as terminating causes the service to close the channel after the reply message is sent. This is identical to a client calling a non-terminating operation then calling Close on the channel.

Sessions create shared state between two or more endpoints that enable useful features such as callbacks and associations between clients and service instances. The overall procedure to create a service that requires sessions:

  1. Create a service contract passing a [SessionMode] attribute.
  2. Configure service endpoint to use a binding that supports sessions.

For example:

[ServiceContract(Name = "SampleDuplexHello", SessionMode = SessionMode.Required)]
public interface IHello
{
    [OperationContract(IsOneWay = true)] void Hello(string greeting);
    ...
}

The example in Session/Faults section illustrates all of the above concepts.

Synchronous and Asynchronous Operations

All service contracts, no matter what the types of parameters and return values, use WCF attributes to specify a particular message exchange pattern. WCF offers three forms of asynchronous execution:

Examples

Duplex Message Pattern

Duplex communication occurs when a client establishes a sessions with a server and gives the service a channel on which the service can send messages back to the client. A duplex contract is a pair of interfaces - a primary interface from the client to the server and a callback interface from the server to the client. Recall that a duplex contract requires a session because a context must be established to correlate the set of messages being sent between the client and the server.

In this sample, ISystemInfoDuplex allows the client to query the operating system. The service returns results to the client on the ISystemInfoDuplexCallback interface which is implemented by the client.

In this sample, the service is self-hosted in a console application (.exe). The client is also a console application (.exe). The code for the client proxy was generated with svcutil.exe command as follows:

Service

// namespace DuplexService

/* IOrderDuplex represents the primary interface in a duplex contract. Note the use of names parameters:
*  1. CallbackContract: Sets the type of the callback contract that will be called by this primary contract
*  2. SessionMode: Required by duplex contracts. SessionMode.Required means that bindings are required to use
*  reliable sessions between endpoints (a reliable session is a way of correlating a set of messages
*  exchanged between two or more endpoints to provide assurances of the reliability of the connection)
*/

[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ISystemInfoDuplexCallback))]
public interface ISystemInfoDuplex
{
    /* Recall that each contract method must be annotated with [OperationContract] attribute.
    *  Note all operations in a duplex contract must be 'one way' operations, i.e., they do not
    *  return a reply message for each request message. Also note that one-way operations cannot
    *  return a value.
    *  Because this is the primary interface for the service contract, it will be implemented
    *  by this containing service project
    */
    [OperationContract(IsOneWay = true)] void GetUserName();
    [OperationContract(IsOneWay = true)] void GetLogicalDrives();
}

/* IOrderDuplexCallback is the callback interface in a duplex interface. It is used to
* send reply messages from the server back to the client. Note that onw-way operations
* must return void. Results are sent as method parameters.
* Because this is the callback interface that the service will use to call back
* on the client, this interface will be implemented in the client.
*/
public interface ISystemInfoDuplexCallback
{
    [OperationContract(IsOneWay = true)] void UserName(string username);
    [OperationContract(IsOneWay = true)] void LogicalDrives(string drives);
}

// namespace DuplexService
/* Because the ISystemInfoDuplex requires a session ( to correlate set of messages
*  exchanged between client and service), we have to use the InstanceContextMode behaviour
*  attribute to specify the relationship between the channel session and instances of the
*  service contract. Here, InstanceContextMode.PerSession indicates that the client
*  can use a binding that supports reliable sessions to make repeated calls to the same
*  service object (contract this to InstanceContextMode.PerCall, where the client will
*  be making calls to a new object instance each time)
*/

[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class SystemInfoDuplex : ISystemInfoDuplex

{
    string strResult = String.Empty;

    public void GetUserName()
    {
        // Operation logic ...
        strResult = System.Environment.UserName;

        // Note how result is stroed in an instance variable
        Callback.UserName(strResult);
    }

    public void GetLogicalDrives()
    {
        // Operation logic ...
        string[] drives = System.Environment.GetLogicalDrives();

        // Note how result is stroed in an instance variable
        strResult = string.Join("'", drives);
        Callback.LogicalDrives(strResult);
    }

    private ISystemInfoDuplexCallback Callback
    {
        get
        {
            // Get the execution context of this service
            System.ServiceModel.OperationContext ctx = System.ServiceModel.OperationContext.Current;

            // Return a channel that connects to the client instance that called the current method
            return ctx.GetCallbackChannel<ISystemInfoDuplexCallback>();
        }
    }
}

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <services>
            <!-- name corresponds to the fully-qualified name of the type implementing the contract-->
            <service name="DuplexService.SystemInfoDuplex">

                <!--- Note the use of the wsDualHttpBinding binding which is required for duplex service contracts -->
                <endpoint address="http://localhost:8080/SystemInfoDuplex"
                          binding="wsDualHttpBinding"
                          contract="DuplexService.ISystemInfoDuplex">
                </endpoint>
            </service>
        </services>
    </system.serviceModel>
</configuration>

/* A WCF service consists of a contract (defined in BasicService project), and
*  configuration entries that specify behaviors and endpoints associated with that
*  implementation (see <system.serviceModel> in your application configuration file)
*
*  To define a WCF service, the initialization code is embedded inside a managed
*  application (console application). We then add an endpoint for the service
*  (in this example via configuration entries). And finally we create an instance
*  of ServiceHost and open it to automatically create listeners and start listening
*  for incoming messages from clients.
*/

internal class Host
{
    internal static ServiceHost serviceHost = null;

    internal static void StartService()
    {
        serviceHost = new ServiceHost(typeof(DuplexService.SystemInfoDuplex));

        //Open myServiceHost
        serviceHost.Open();
   }

    internal static void StopService()
    {
        //Call StopService from your shutdown logic (i.e. dispose method)
       
Trace.WriteLine(" Service state: " + serviceHost.State.ToString());

        if (serviceHost.State != CommunicationState.Closed)
        {
            serviceHost.Close();
            Trace.WriteLine(" Service closed!");
        }
    }
}

 class Program
{
    static void Main(string[] args)
    {
        Host.StartService();

        // The service can now be accessed until user presses <return> to terminate the service
        Console.WriteLine("The service is ready");
        Console.WriteLine("Press <RETURN> to terminate the service");
        Console.ReadLine();

        // Terminate the service
        Host.StopService();
    }
}

Client

/* Define a class which implements the callback interface of a duplex interface.
*  Because this is the callback interface that the service will use to call back
*  on the client, this interface is implemented in the client. When the main program
*  creates the client proxy, it will pass an instance of this class to proxy constructor.
*
*  In other words, the client must provide a class that implements the callback interface
*  of the duplex contract, in order to receive messages from the service.
*/

public class SystemInfoDuplexCallback : ISystemInfoDuplexCallback
{
    #region ISystemInfoDuplexCallback Members

    // Place breakspoints on these methods to see how service calls back on these methods
    // after the client sends its requests

    public void UserName(string username)
    {
        Console.WriteLine( "UserName: " + username);
    }

    public void LogicalDrives(string drives)
    {
        Console.WriteLine( "LogicalDrives: " + drives);
    }
    #endregion
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Press <ENTER> to terminate client once the output is displayed.");
        Console.WriteLine();

        // Note that a client proxy in a duplex communication must be initialized with an
        // instnace of InstanceContext. InstanceContext is initialized with the class that
        // implements the callback interface so that InstanceContext can be used to handle
        // messages sent back from the service to the client.
        InstanceContext context = new InstanceContext(new SystemInfoDuplexCallback());
        SystemInfoDuplexClient proxy = new SystemInfoDuplexClient(context);

        // Invoke methods on the service proxy. Response will come back on methods of SystemInfoDuplexCallback
        // (place breakpoints on methods of SystemInfoDuplexCallback)
        proxy.GetUserName();
        proxy.GetLogicalDrives();

        // Wait for a keystroke to terminate the application
        Console.ReadLine();
    }
}

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.serviceModel>

        <!-- Recall that a <client> element specifies the list of endpoints that an element can connect to -->
        <client>
            <endpoint address="http://localhost:8080/SystemInfoDuplex"
                      contract="ISystemInfoDuplex"
                      binding="wsDualHttpBinding"
                      bindingConfiguration="DuplexBinding" />
        </client>

        <bindings>
            <!-- configure a binding that support duplex communication -->
            <wsDualHttpBinding>
                <binding name="DuplexBinding" clientBaseAddress="http://localhost:8080/MyDuplexClient/">
                </binding>
            </wsDualHttpBinding>
        </bindings>

</system.serviceModel>
</configuration>

Sessions/Handling Faults

The following example illustrates how to implement a service contract that requires sessions as well as illustrating how to implement fault handling. A session is usually used to allow a service to perform multiple operations while maintaining state within these operations.

Recall that there is an interaction between the SessionMode enumeration (which is used by a service contract to indicate what support the service has for reliable sessions) and the ServiceBehaviourAttribute.InstanceContextMode property (which specifies the number of service instances available for handling client calls). This interaction controls the association between channels and specific service objects.

In this sample, the service is self-hosted in a console application (.exe). The client is also a console application (.exe). The code for the client proxy was generated with svcutil.exe  command as follows:

In this sample, the service is self-hosted in a console application (.exe). The client is also a console application (.exe). The code for the client proxy was generated with svcutil.exe command as follows:

Server

[ServiceContract(SessionMode=SessionMode.Required)]
public interface ISessionService
{
    [OperationContract(IsInitiating=true, IsTerminating=false)]
    [FaultContract( typeof(MyFaultExcpetion))]
    void SetOrderID(string name, int nID);

    [OperationContract(IsInitiating=false, IsTerminating=true)]
    [FaultContract( typeof(MyFaultExcpetion))]
    int GetOrderID(string name);
}

// Note how an exception need to be defined with a [DataContract] attribute to specify
// that MyFaultException type implements a data contract and is serializable by the
// DataContractSerializer (which serializes/desterilizes an instance of a type into an
// XML stream or XML document using the supplied data contract)

[DataContract]
public class MyFaultExcpetion
{
    [DataMember] private string strMessage;
    public MyFaultExcpetion(string s) { strMessage = s; }
}

[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class SessionService : ISessionService
{
    #region Data members
   
public Dictionary<string, int> _mapNameToID = new Dictionary<string, int>();
    #endregion

    #region ISessionService Members
    public void SetOrderID(string name, int nID)
    {
        if (!_mapNameToID.ContainsKey(name))
            _mapNameToID.Add(name, nID);
        else
        {
            MyFaultExcpetion fault = new MyFaultExcpetion("Name " + name + " already exists" );
            throw new FaultException<MyFaultExcpetion>( fault, "invalid lookup name" );
        }
    }

    public int GetOrderID(string name)
    {
        int nID;
        if (!_mapNameToID.TryGetValue(name, out nID))
        {
            MyFaultExcpetion fault = new MyFaultExcpetion("Name " + name + " does not exit" );
            throw new FaultException<MyFaultExcpetion>( fault, "invalid lookup name" );
        }
        return nID;
    }
    #endregion
}

<configuration>
    <system.serviceModel>
        <services>
            <!-- name corresponds to the fully-qualified name of the type implementing the contract-->
           
<service name="Sessions.SessionService">
                <endpoint address="http://localhost:8080/SessionService"
                      binding="wsHttpBinding" contract="Sessions.ISessionService"></endpoint>
            </service>
        </services>
    </system.serviceModel>
</configuration>

Client

class Program
{
    SessionServiceClient _proxy = null;

    // Main program starts here
    static void Main(string[] args)
    {
        // Create a test object and invoke its methods which in turn call service functions
        Program ob = new Program();
        ob.TestSessions();
        ob.TestFaults();

        // Terminate service when user presses any key
        Console.WriteLine("Press <RETURN> to terminate the service");
        Console.ReadLine();
    }

    public Program()
    {
        // Create a proxy to the remote object on console app start up
        _proxy = new SessionServiceClient();
    }

    // Shows that the proxy object is statelful as it remembers customer name and
    // the associated order id
    private void TestSessions()
    {
        // Shows how to send and retrieve the same ID using a session-ful session
        _proxy.SetOrderID("yazan", 12345);
        int nOrderID = _proxy.GetOrderID("yazan");
        Console.WriteLine("TestSessions: Sent (yazan, 12345)");
        Console.WriteLine("TestSessions: Retrieved (yazan, {0})", nOrderID);
        Console.WriteLine("TestSessions: Session ID: {0}", _proxy.InnerChannel.SessionId );

        try
        {
            // The previous GetOrderID has IsTerminating = true. This terminate the session
            // and the user is no longer to access the object. The following code proves it
            Console.WriteLine("TestSessions: Sending (xyz, 123)");
            _proxy.SetOrderID("xyz", 123);     // throws an exception
            nOrderID = _proxy.GetOrderID("yazan");
        }
        catch (Exception e)
        {
            Console.WriteLine("TestSessions: " + e.GetType().Name + " - " + e.Message);
        }
        Console.WriteLine(System.Environment.NewLine);
    }

    // Tests faults generated from a service. Recall that SetOrderID was flagged with
    // IsInitiating = true, which means that any session should start with a call to
    // SetOrderID. Likewise, GetOrderID was flagged with IsInitiating = false, which
    // means that any session should not start with a call to GetOrderID. This code
    // generates faults by calling GetOrderID first
    private void TestFaults()
    {
        try
        {
            // Close and start a new session
            ISessionService ob = _proxy.ChannelFactory.CreateChannel();
            ob.SetOrderID("yazan", 12345);
            int nOrderID = ob.GetOrderID("xyz");
        }
        catch (FaultException<Sessions.MyFaultExcpetion> e)
        {
            Console.WriteLine("TestFaults: FaultException<MyFaultExcpetion>: custom fault exception while doing " +
                e.Message + ". Problem: " + e.Detail.strMessage);
        }
        catch (FaultException e)
        {
            Console.WriteLine("TestFaults: Unknown FaultException: " + e.GetType().Name + " - " + e.Message);
        }
        catch (Exception e)
        {
            Console.WriteLine("TestFaults: EXCEPTION: " + e.GetType().Name + " - " + e.Message);
        }
        Console.WriteLine(Environment.NewLine);
    }
}

<configuration>
    <system.serviceModel>
        <client>
            <endpoint address="http://localhost:8080/SessionService" contract="ISessionService" binding="wsHttpBinding"/>
        </client>
    </system.serviceModel>
</configuration>

Output

Asynchronous Messages

The following project shows 1) how a client can asynchronously access a synchronous service operation, and 2) how a service can implement its operations asynchronously. This sample is based on Example 2 in the Introduction chapter. Recall that using asynchronous or synchronous invocation is a local decisions and does not affect how a message is sent on the wire.

In this sample, the service is self-hosted in a console application (.exe). The client is also a console application (.exe). The code for the client proxy was generated with svcutil.exe  command using the /a (async) switch as follows:

In this sample, the service is self-hosted in a console application (.exe). The client is also a console application (.exe). The code for the client proxy was generated with svcutil.exe command as follows:

Service

 // Declare a service contract
[ServiceContract()]
public interface IMyBasicService
{
    // A synchronous service operation. Client may invoke it synchronously or asynchronously
    // (the asynchronous version will be generated by the /async switch)

    [OperationContract()]
    bool         Trace(string value);

    // An asynchronous service operation. Note the use of the AsyncPattern named attribute.
    // Also note how the method declaration follows the .NET Framework patter for asynchronous
    // methods
   
[OperationContract(AsyncPattern = true)]
    IAsyncResult BeginLog(string value, AsyncCallback acb, object state);
    bool         EndLog(IAsyncResult ar);
}

// Implement the IMyBasicService service contract
public delegate void delAsyncOperation(string strValue);
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class MyBasicService : IMyBasicService
{
    // The /async switch supplied to svcutil will generate an asynchronous version of this
    // function, BeginTrace and EndTrace
    public bool Trace(string value)
    {
        // Code does not do actual tracing, just prints debugging messages.
        int nID = Thread.CurrentThread.ManagedThreadId;
        bool IsPool = Thread.CurrentThread.IsThreadPoolThread
        Console.WriteLine("Entering Trace on Thread ID {0} thread pool {1}", nID, IsPool);
        Console.WriteLine("Logging value: " + value);
        return true;
        Console.WriteLine("Exiting Trace on Thread ID {0} thread pool {1}", nID, IsPool);
    }

    // The svcutil will generate a synchronous version of this function, Log
    public IAsyncResult BeginLog(string value, AsyncCallback acb, object state)
    {
        // Code does not do actual tracing, just prints debugging messages.
        int nID = Thread.CurrentThread.ManagedThreadId;
        bool IsPool = Thread.CurrentThread.IsThreadPoolThread
        Console.WriteLine("Entering BeginLog on Thread ID {0} thread pool {1}", nID, IsPool);
        delAsyncOperation del = new delAsyncOperation(LogDelegate);
        IAsyncResult iar = del.BeginInvoke(value, acb, state);
        Console.WriteLine("Leaving BeginLog on Thread ID {0} thread pool {1}", nID, IsPool);
        return iar;
    }

    public bool EndLog(IAsyncResult iar)
    {
        int nID = Thread.CurrentThread.ManagedThreadId;
        bool IsPool = Thread.CurrentThread.IsThreadPoolThread
        Console.WriteLine("Entering EndLog on Thread ID {0} thread pool {1}", nID, IsPool);
        AsyncResult ar = (AsyncResult)iar;
        Console.WriteLine("Leaving EndLog on Thread ID {0} thread pool {1}", nID, IsPool);
        return true;
    }

    private void LogDelegate(string strMsg)
    {
        Console.WriteLine("Executing the log delegate (used by BeginLog)");
    }
}

Client

class Program
{
    static void Main(string[] args)
    {
        TestClient client = new TestClient();
        client.Initialize();
        client.TestSynchronousCalls();
        client.TestAsynchronousCalls();

        // Terminate service when user presses any key
        Console.WriteLine("Press <RETURN> to terminate the service");
        Console.ReadLine();
    }
}

class TestClient
{
    MyBasicServiceClient _proxy;

    public void Initialize()
    {
        // Create an instance of the proxy
        _proxy = new MyBasicServiceClient();
    }

    public void TestSynchronousCalls()
    {
        // Invoke a synchronsous operation synchronously.
        _proxy.Trace("This trace was exuected with proxy.Trace");

        // Invoke a synchronous operation asynchronously (the asynchronous version of
        // this function was auto-generated by svcutil using the /async command-line option.
        // Finally, note that we are passing the proxy as the state object so that we
        // can have access to the auto-generated EndTrace method

        // Note that all asynchronous behaviour is local to the client and has no bearing on the way
        // messages are sent from the client, or processed by the service. The typical reason to use
        // this pattern in a UI application is to keep the UI thread free for updating the screen.
        // This pattern is also applicable when a service is acting as a client and you want to free
        // up the message processing thread from calls out to other services
        AsyncCallback acbTrace = new AsyncCallback(TraceCallBack);
        _proxy.BeginTrace("This trace was exuected with proxy.BeginTrace", acbTrace, _proxy);
    }
    public void TestAsynchronousCalls()
    {
        // Call an asynchronous method synchronously
        _proxy.Log("Logging via the autogenerated synchronous version of BeginLog (i.e., Log)");

        // Calling an asynchronous method
        AsyncCallback acbLog = new AsyncCallback(LogCallBack);
        _proxy.BeginLog("Logging directly via BeginLog", acbLog, _proxy);
    }

    // This is the AsycnCallBack function for _proxy.BeginTrace. BeginTrace was auto generated
    // from the service function Trace usin svcutil's /async switch
    private void TraceCallBack(IAsyncResult iar)
    {
        MyBasicServiceClient proxy = (MyBasicServiceClient)iar.AsyncState;
        bool bStatus = proxy.EndTrace(iar);
        Console.WriteLine("Result was logged with a flag of: " + bStatus);
    }

    // This is the AsycnCallBack function for _proxy.BeginLog.
    private void LogCallBack(IAsyncResult iar)
    {
        MyBasicServiceClient proxy = (MyBasicServiceClient)iar.AsyncState;
        bool bStatus = proxy.EndLog(iar);
        Console.WriteLine("Result was logged via BeginLog with status of " + bStatus);
    }
}

Results of executing the TestSynchronousCalls() method (client followed by service):

Results of executing the TestAsynchronousCalls() method (client followed by service):