Introduction
WCF supports 3 ways for a client to call a service. This article demonstrate all
3 ways.
Background
WCF supports 3 ways for a client to invoke a service. this article covers these
3 basic methods of innvoking service through a client. There are two other ways
invoking operations asynchronously and queued, those ways are not covered in
this article.
- Request Reply : default way of invocation, the client would
issue a call, block while the call was in progress,
and would continue executing once the method returned. If
service doesn't respond to the service within
receiveTimeout
,
client will receive TimeOutException
. - One way calls: fire and forget type operations. that simply
means client will send a request to the server and does not care whether service
execution was a success or failure. There is no return value from the server
side. Client will be blocked only for a moment till it dispatches its
call to service. Client can continue to execute its statement,
after making one-way call to the service. sometime calls are
queued at the server side to be dispatched one at a time. if the number of
queued messages has exceeded the queue's capacity, client will be blocked for
the moment till the message is being queued. and as soon as the call is queued,
the client will be unblocked and can continue executing, while the service
processes the operation in the background.
- Call back Operation: WCF Supports allowing a service to call
back to its clients. They are specially useful, for notifying client(s)
that something happened on the service side. call backs are also
commonly referred as duplex operations. not all binding types support call back
operations. for example HTTP (for obvious reasons of being a connectionless
nature). only two commonly used binding that offer call backs are
NetTcpBinding and NetNamedBinding. to offer call backs over HTTP, WCF
offers the
WSDualHttpBinding
, which actually sets up two WS channels.
Using the code
This article has been divided into 3 modules:
- WCF Service Library (OperationLib.dll): Actual Service
logic, which defines a Service Contract Interface,
OperationContract
,
and implements them, and exposes few functions to the world to use them - Console based Host Application to host the WCF Service Library
OperationLibHost.exe): Host the WCF library
- Console Client Application (OperationClient.exe): Client
Application which will use this service
First Module: WCF Service Library (OperationLib.dll)
To create this project, you can simply take a "Class Library"
project, while choosing from project wizard option. let's name it "OperationLib
",
it is the actual service which implements the business logic. The project
already contains a file Class1.cs, let us do some house keeping, before
we write any code for the service library
Little House Keeping
- Delete the file Class1.cs from the project workspace.
- Add a new Interface named
IRequestReplyService
to the project, a new file IRequestReplyService.cs will be
added to the project. - Add a new Class named
RequestReplyService
, to
the project. that will implement the IRequestReplyService
interface, a new file RequestReplyService.cs will be added to
the project. - Add a new Interface named
IOneWayCallService
to the project, a new file IOneWayCallService.cs will be added
to the project. - Add a new Class named
OneWayCallService
, to
the project. that will implement the IOneWayCallService
interface, a new file OneWayCallService.cs will be added to the
project. - Add a new Interface named
IDuplexService
to
the project, a new file IDuplexService.cs will be added to the
project. - Add a new Class named
DuplexService
, to the
project. that will implement the IDuplexService
interface, a
new file DuplexService.cs will be added to the project.
Defining Interfaces
Defining IRequestReply Interface
using System;
using System.Text;
using System.ServiceModel;
namespace OperationLib
{
[ServiceContract]
interface IRequestReplyService
{
[OperationContract]
double AddNumer(double dblNum1, double dblNum2);
}
}
Explanation
Interface simple defines 1 method that takes 2 parameter(s) of double type, and returns a double
Defining IOneWayCallService Interface
using System;
using System.ServiceModel;
using System.Text;
namespace OperationLib
{
[ServiceContract(SessionMode = SessionMode.Required)]
interface IOneWayCallService
{
[OperationContract(IsOneWay = true)]
void AddNumer(double dblNum1, double dblNum2 );
[OperationContract]
double GetResult();
}
}
Explanation
Interface simple defines 1 method that 1 parameter of double type, and returns nothing. notice [ServiceContract(SessionMode = SessionMode.Required)]
attribute. this has been done to make
make use of One-way operation in Sessionful service, it is just to preserve the value of the last operation, as the operation marked as
[OperationContract(IsOneWay = true)]
should not return any value.
to get the result of the operation. we have defined another method
double GetResult();
Defining IDuplexService Interface
using System;
using System.ServiceModel;
using System.Text;
namespace OperationLib
{
public interface IDuplexServiceCallback
{
[OperationContract]
void OnValueAdded(double dblNum1, double dblNum2, double dblResult);
}
[ServiceContract(CallbackContract = typeof(IDuplexServiceCallback))]
public interface IDuplexService
{
[OperationContract()]
void AddNumer(double dblNum1, double dblNum2);
}
}
Explanation
Callback service can be enabled by using CallbackContract property in the ServiceContract attribute.
as shown, this defines two interfaces, one interface (IDuplexService
) which will be implemented by the service, IDuplexService
interface simply defines a method that takes two double parameters, and returns nothing.
the other interface (IDuplexServiceCallback
) has been defined, which will be implemented by the client.
IDuplexServiceCallback
has been defined as callback property of (IDuplexService
) interface, (IDuplexServiceCallback
) simply defines one method prototype.
void OnValueAdded(double dblNum1, double dblNum2, double dblResult);
Implementing Interfaces
let's implement interface for each service.
Implementing IRequestReply Interface
using System;
using System.Text;
namespace OperationLib
{
public class RequestReplyService : IRequestReplyService
{
public double AddNumer(double dblNum1, double dblNum2)
{
return (dblNum1 + dblNum2);
}
}
}
Explanationn
the method simply adds 2 values and returns the result.
Implementing IOneWayCallService Interface
using System;
using System.Text;
namespace OperationLib
{
public class OneWayCallService : IOneWayCallService
{
private double m_dblResult = 0;
public void AddNumer(double dblVal1, double dblVal2)
{
m_dblResult = dblVal1 + dblVal2;
}
public double GetResult()
{
return m_dblResult;
}
}
}
Explanationn
Simply adds 2 values and stores the result in a private varibale, Interface was marked with
SessionMode.Required
, to make it a sessionful service,
as we need to return the value of the operation, and methods was marked as
[OperationContract(IsOneWay = true)]
can not return any value,
so we added one more method, that will simply return the value of private
variable (result). that means, client needs to call double GetResult ()
method to get the result,
after calling the AddNumber
method.
Implementing IDuplexService Interface
using System;
using System.ServiceModel ;
using System.Text;
namespace OperationLib
{
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class DuplexService : IDuplexService
{
public void AddNumber(double dblNum1, double dblNum2)
{
double dblResult = dblNum1 + dblNum2;
IDuplexServiceCallback callbackInstance = OperationContext.Current.GetCallbackChannel<IDuplexServiceCallback>();
callbackInstance.OnNumberAdded(dblNum1, dblNum2, dblResult);
}
}
}
Explanationn
You can note that We have set the ConcurrencyMode to
Multile. If you are not using ConcurrencyMode
to Multiple or Reentent,
you will be end up with deadlock exception as shown below. This is
because when a client made a call to the service, channel is created and
lock by WCF service. If you are calling the Callback method inside the
service method. Service will try to access the lock channel, this may
leads to deadlock. So you can set ConcurrencyMode
to Multiple
or Reentent
so it will release the lock silently.
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class DuplexService : IDuplexService
{
. . .
}
The OperationContext
class provides the service with easy access to the callback
reference via the generic method GetCallbackChannel<T>( )
.
IDuplexServiceCallback callbackInstance = OperationContext.Current.GetCallbackChannel<IDuplexServiceCallback>();
Service then can
call the client side callback method using the obtained reference. The following code shows the callback method invocation.
callbackInstance.OnNumberAdded(dblNum1, dblNum2, dblResult);
Build the Class Libray, and the first part is complete.
Second Module:
Host Application (OperationLibHost.exe)
To create this project, you can simply take a "Console based
Application"
project, while choosing from project wizard option. let's name it "OperationLibHost
",
a new Project OperationLibHost will be added to the workspace.
Assumption
The Host application will expose the following endpoints for the service.
RequestReplyService
will expose HTTP endpoint at Port 9011- Corresponding mex End Point (IMetadatExchange)
for the HTTP end point.
RequestReplyService
will expose TCP endpoint at Port 9012- Corresponding mex End Point (IMetadatExchange)
for the TCP end point.
OneWayCallService
will expose HTTP endpoint at Port 9013- Corresponding mex End Point (IMetadatExchange)
for the HTTP end point.
OneWayCallService
will expose TCP endpoint at Port 9014- Corresponding mex End Point (IMetadatExchange)
for the TCP end point.
DuplexService
will expose HTTP endpoint at Port 9015- Corresponding mex End Point (IMetadatExchange)
for the HTTP end point.
DuplexService
will expose TCP endpoint at Port 9016- Corresponding mex End Point (IMetadatExchange)
for the TCP end point.
Defining configuration for RequestReplyService
<service name="OperationLib.RequestReplyService" behaviorConfiguration="RequestReplyServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:9011/RequestReplyService"/>
<add baseAddress="net.tcp://localhost:9012/RequestReplyService"/>
</baseAddresses>
</host>
<endpoint address="http://localhost:9011/RequestReplyService" binding="wsHttpBinding" contract="OperationLib.IRequestReplyService"/>
<endpoint address="net.tcp://localhost:9012/RequestReplyService" binding="netTcpBinding" contract="OperationLib.IRequestReplyService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
</service>
Explanationn
Application configuration files defines 2 endpoints, one for WSHttpBinding and one for TCP Binding, at port 9011 and 9012 respectively and 1 mex end point for each. WSHttpBinding is similar to the BasicHttpBinding, but provides more web service features.
Defining configuration for OneWayCallService
<service name="OperationLib.OneWayCallService" behaviorConfiguration="OneWayCallServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:9013/OneWayCallService"/>
<add baseAddress="net.tcp://localhost:9014/OneWayCallService"/>
</baseAddresses>
</host>
<endpoint address="http://localhost:9013/OneWayCallService" binding="wsHttpBinding" contract="OperationLib.IOneWayCallService"/>
<endpoint address="net.tcp://localhost:9014/OneWayCallService" binding="netTcpBinding" contract="OperationLib.IOneWayCallService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
</service>
Defining configuration for DuplexService
<service name="OperationLib.DuplexService" behaviorConfiguration="DuplexServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:9015/DuplexService"/>
<add baseAddress="net.tcp://localhost:9016/DuplexService"/>
</baseAddresses>
</host>
<endpoint address="http://localhost:9013/DuplexService" binding="wsDualHttpBinding" contract="OperationLib.IDuplexService"/>
<endpoint address="net.tcp://localhost:9014/DuplexService" binding="netTcpBinding" contract="OperationLib.IDuplexService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
</service>
Important:
in the Duplex service binding has beed defined as wsDualHttpBinding
, unlike the previous interfaces where binding was defined as wsHttpBinding
. wsDualHttpBinding
provides the same support for web service protocol as WSHttpBinding
, but for use with duplex contracts.
Defining Behavior(s) for Services.
<behaviors>
<serviceBehaviors>
<behavior name="RequestReplyServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true "/>
</behavior>
<behavior name="OneWayCallServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true "/>
</behavior>
<behavior name="DuplexServiceBehavior" >
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true "/>
</behavior>
</serviceBehaviors>
</behaviors>
Explanation
each service behavior defines two attributes,
<serviceMetadata httpGetEnabled="true" />
Gets or sets a value that indicates whether to publich service medtadata for retrieval using an HTTP/GET request, true means yes, metadata is available for retrieval using a HTTP/GET request.
<serviceDebug includeExceptionDetailInFaults="true "/>
Set IncludeExceptionDetailsInFaults
to true
to enble clients to obtain information about internal service method exceptions; it is only recommended as a way of temporarily debugiing a service application. this property must be set to false on production servers.
once the configuration is in place, let's write the code to host the services.
Hosting RequestReplyService
ServiceHost m_RequestReplyHost = null;
try
{
m_RequestReplyHost = new ServiceHost(typeof(OperationLib.RequestReplyService));
m_RequestReplyHost.Open();
}
catch (Exception eX)
{
Console.WriteLine("Failed while starting RequestReplyService [" + eX.Message + "]");
m_RequestReplyHost = null;
}
if (m_RequestReplyHost != null) Console.WriteLine("RequestReplyService hosted successfully . . .");
Hosting OneWayService
ServiceHost m_OneWayCallHost = null;
try
{
m_OneWayCallHost = new ServiceHost(typeof(OperationLib.OneWayCallService));
m_OneWayCallHost.Open();
}
catch (Exception eX)
{
Console.WriteLine("Failed while starting OneWayCallService [" + eX.Message + "]");
m_RequestReplyHost = null;
}
if (m_OneWayCallHost != null) Console.WriteLine("OneWayCallService hosted successfully . . .");
Hosting DuplexService
ServiceHost m_DuplexServiceHost = null;
try
{
m_DuplexServiceHost = new ServiceHost(typeof(OperationLib.DuplexService));
m_DuplexServiceHost.Open();
}
catch (Exception eX)
{
Console.WriteLine("Failed while starting DuplexService [" + eX.Message + "]");
m_RequestReplyHost = null;
}
if (m_DuplexServiceHost!= null) Console.WriteLine("DuplexService hosted successfully . . .");
Build and Execute the Host.
Open a command prompt and execute the host. here is the output.
Third Module: Client Application (OperationClient.exe)
To create this project, you can simply take a "Console based
Application"
project, while choosing from project wizard option. let's name it "OperationClient
",
a new Project OperationClient
will be added to the workspace.
Generating proxies
While the host application is running, right click on the client application project, click on
Generate Proxy for RequestReplyService
References –> Add Service Reference
In the address bar type the address of the mex endpoint address of RequestReplyService
as shown below.
Generate Proxy for OneWayCallService
References –> Add Service Reference
In the address bar type the address of the mex endpoint address of OneWayCallService
as shown below.
Generate Proxy for DuplexService
References –> Add Service Reference
In the address bar type the address of the mex endpoint address of DuplexService
as shown below.
Now when you have added references of all 3 services, let’s write code for the client to use these services.
Call RequestReplyService
double dblVal1 = 100; double dblVal2 = 200;
try
{
RequestReplyServiceReference.RequestReplyServiceClient obj1 = new RequestReplyServiceReference.RequestReplyServiceClient("WSHttpBinding_IRequestReplyService");
RequestReplyServiceReference.RequestReplyServiceClient obj2 = new RequestReplyServiceReference.RequestReplyServiceClient("NetTcpBinding_IRequestReplyService");
Console.WriteLine("\nCalling Request Reply Service");
double dblResult1 = obj1.AddNumber (dblVal1, dblVal2);
Console.WriteLine("Using HTTP Binding >> Value 1: {0:F2} Value 2: {1:F2} Returns : {2:F2}", dblVal1, dblVal2, dblResult1);
dblVal1 = 100; dblVal2 = 200;
double dblResult2 = obj2.AddNumber(dblVal1, dblVal2);
Console.WriteLine("Using TCP Binding >> Value 1: {0:F2} Value 2: {1:F2} Return : {2:F2}", dblVal1, dblVal2, dblResult2);
}
catch (Exception eX)
{
Console.WriteLine("Error while calling Request Reply Service [ " + eX.Message + "]");
}
Explanation
Create the object from the Service proxy and call the method, and display the result.
Calling OneWayCallService
try
{
OneWayCallServiceReference.OneWayCallServiceClient obj3 = new OneWayCallServiceReference.OneWayCallServiceClient("WSHttpBinding_IOneWayCallService");
OneWayCallServiceReference.OneWayCallServiceClient obj4 = new OneWayCallServiceReference.OneWayCallServiceClient("NetTcpBinding_IOneWayCallService");
Console.WriteLine("\nCalling OneWayCall Service");
obj3.AddNumber(dblVal1, dblVal2 );
double dblResult3 = obj3.GetResult();
Console.WriteLine("Using HTTP Binding >> Value 1: {0:F2} Value 2: {1:F2}", dblVal1, dblVal2 );
Console.WriteLine("Result : {0:F2}", dblResult3);
obj4.AddNumber (dblVal1, dblVal2);
double dblResult4 = obj4.GetResult();
Console.WriteLine("Using TCP Binding >> Value 1: {0:F2} Value 2: {1:F2}", dblVal1, dblVal2);
Console.WriteLine("Result : {0:F2}", dblResult4);
}
catch (Exception eX)
{
Console.WriteLine("Error while calling One way Service [ " + eX.Message + "]");
}
Explanation
Create the object from the Service proxy and call the method, passing values, as the method is defined as OneWay, so it does not return any value.
call another method to collect the result.
double dblResult3 = obj4.GetResult();
and
double dblResult4 = obj4.GetResult();
Calling Duplex Service
Before you call a duplex service, you need to do some home work. you need to implement the Callback Interface at the client side. so define a class which is derived from IDuplexServiceCallback
. let's call it DuplexServiceCallbackSink
Implementing IDuplexServiceCallback interface at the client side
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OperationClient
{
class DuplexServiceCallbackSink : DuplexServiceReference.IDuplexServiceCallback
{
public void OnNumberAdded(double dblNum1, double dblNum2, double dblResult)
{
Console.WriteLine(">> Duplex Service <<CallBack>> Value 1 : {0:F2} Value 2 : {1:F2} Result : {2:F2}", dblNum1, dblNum2, dblResult);
}
}
}
Now, when you have written the implementation for the Callback Interface at the client side, it is now time to call the Duplex Service.
try
{
DuplexServiceCallbackSink callback = new DuplexServiceCallbackSink();
InstanceContext insCntxt = new InstanceContext(callback);
DuplexServiceReference.DuplexServiceClient obj5 = new DuplexServiceReference.DuplexServiceClient(insCntxt, "WSDualHttpBinding_IDuplexService");
DuplexServiceReference.DuplexServiceClient obj6 = new DuplexServiceReference.DuplexServiceClient(insCntxt, "NetTcpBinding_IDuplexService");
Console.WriteLine("\nCalling Duplex Service");
obj5.AddNumber(dblVal1, dblVal2);
obj6.AddNumber(dblVal1, dblVal2);
}
catch (Exception eX)
{
Console.WriteLine("Error while calling Duplex Service [ " + eX.Message + "]");
}
Explanation
Create the Callback Sink, as the Callback method invoke expect an implementation object, who implements the callback method.
DuplexServiceCallbackSink callback = new DuplexServiceCallbackSink();
InstanceContext insCntxt = new InstanceContext(callback);
Call the service using appropriate binding, passing the InstanceContext object.
DuplexServiceReference.DuplexServiceClient obj5 = new DuplexServiceReference.DuplexServiceClient(insCntxt, "WSDualHttpBinding_IDuplexService");
obj5.AddNumber(dblVal1, dblVal2);
or/and
DuplexServiceReference.DuplexServiceClient obj6 = new DuplexServiceReference.DuplexServiceClient(insCntxt, "NetTcpBinding_IDuplexService");
obj6.AddNumber(dblVal1, dblVal2);
and here is the collective output of the client.
Points of Interest
Explains the working the different operation modes of a WCF Service.
History
Initial version.