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:
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.
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)
}
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.
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:
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.)
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:
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)
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.
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.
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);
}
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.
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;
}
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:
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!" );
}
}
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!" );
}
}
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] ...
}
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.
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:
Note that this mechanism is intended for debugging only and should not be used in a production environment.
'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.
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:
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.
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:
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:
svcutil.exe DuplexService.exe
This exports the contract implemented in DuplexService.dll
to WSDL and XSD files. These files contain the metadata of the contract..
.svcutil.exe *.wsdl *.xsd /lanugage:C#
This command - executed in the same directory as DuplexService.exe - generates a proxy file from the WSDL and XSD files.
// 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();
}
}
/* 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>
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:
svcutil.exe Service.exe
This exports the contract implemented in Service.exe
to WSDL and XSD files. These files contain the metadata of the contract..
svcutil.exe *.wsdl *.xsd /lanugage:C#
This command - executed in the same directory as
Service.exe - generates a proxy file from the WSDL and XSD files.
[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>
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>
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:
svcutil.exe BasicServiceImplementation.dll
This exports the contract implemented in
BasicServiceImplementation.dll
to WSDL and XSD files. These files contain the metadata of the contract..
.svcutil.exe *.wsdl *.xsd /lanugage:C#
/async
This command - executed in the same directory as
BasicHost.exe - generates a proxy file from the WSDL and XSD files.
// 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)");
}
}
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):