Introduction
In this article we will discuss about how we can enforce and manage sequencing operations in a WCF service.
We will see how a WCF service can be configured to work in a way such that the call to methods in a WCF service
always be in a specific order and calling the functions out of that order will result in exceptions.
Background
We have already seen how we can configure a WCF service instance as per our application needs
(A Beginner's Tutorial for Understanding WCF Instance Management [^]). Now in case we have configured our WCF service to work in a stateful mode by setting the
InstanceContextMode
to
PerSession
, one more question arises. The question is how can we configure the service in such a way that
a particular order of operations in enforced on this stateful WCF service.
Why is this order important? The order is important because if our service is maintaining state for every
client then perhaps we need to do some initialization of resource acquisitions whenever the instance is being
created. The other functions of the service will then use these resources to perform their operations.
We would also want to ensure the that our resource cleanup is done whenever a particular function is called
(perhaps the last function in the sequence of functions).
Now we can very well define a convention that the clients should call some function first and then call the other
functions of the service so that the initial function will take care of all initialization and the final
function will take care of resource cleanup. But what if any client call some intermediate function without
calling the initialization function or after calling the cleanup function? This could create some possible
inconsistent behavior. WCF provide us a way to enforce this convention and make sure that if any client
calls any function without calling the initialization function or after calling the cleanup function then the client
call will fail.
Understanding the SessionMode property
before looking at the sequence specific configuration, let us understand a very important property called
SessionMode
. The SessionMode
property goes as part of the ServiceContract
attribute and it specifies how the
service will implement sessions. SessionMode
can be set to three possible values:
Allowed
NotAllowed
Required
Allowed
specifies that the service will allow the sessions if the sessions are supported by the protocol
being used i.e. binding and the InstanceContextMode
is specified as PerSession
.
NotAllowed
specifies that the service will not allow the sessions even if the sessions are supported by the
binding and/or the InstanceContextMode
is specified as PerSession
.
Required
option specifies that the service will support sessions and the underlying binding/protocol should
be chosen MUST support sessions.
Configuring the Sequence of Operations
As we have discussed above, if we need to enforce the order of operations in a WCF service, WCF provides a
way to mark a particular function as the function that should be the first function to be called to initiate the
instance creation process. This can be done by setting the IsInitiating
property of the OperationContract
to
true
.
Similarly, to mark a function as the final function in a sequence of operations, we need to set the IsTerminating
property of the OperationContract
to true
. The IsInitiating
property for this function should be set to false
.
Rest all the functions should be have the OperationContract
with the IsInitiating
property set to false
. All
these functions could only be called when the function marked with IsInitiating
true has been called and the function
marked with IsTerminating
set to true
has not been called. Otherwise call to this function will result in an exception.
Using the code
Let us now create a sample service to see this in action.
Creating the Test Service
Let us create a simple service with three operations. There will be one function(Function1
) that will initiate the
instance creation, one function(Function3
) that will terminate the instance. There will be one more function that could only
be called only when Function1
has been called and Function3
has not yet been called. Calling this function otherwise
would lead to an exception.
Also, Let us set the SessionMode
property of the ServiceContract
as SessionMode.Required
so that this
service could not be used in a session less scenario/binding. Let us look at the ServiceContract
and OperationContact
to achieve the same.
[ServiceContract(SessionMode=SessionMode.Required)]
public interface ISampleService
{
[OperationContract(IsInitiating=true)]
string Function1();
[OperationContract(IsInitiating = false)]
string Function2();
[OperationContract(IsInitiating = false, IsTerminating=true)]
string Function3();
}
The service implementation will have the InstanceContextMode
property of ServiceBehavior
set as
InstanceContextMode.PerSession
. The functions will simply return some dummy strings to show what method
has been called. This will ensure that one instance of the service will be created for every client.
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class SampleService : ISampleService
{
public string Function1()
{
return "This function will INITIATE the session";
}
public string Function2()
{
return "This function will work when Session has been INITIATED and not yet TERMINATED";
}
public string Function3()
{
return "This function will TERMINATE the session";
}
}
Creating a Host
Now since the ServiceContract
has the SessionMode
property set to Required, we will have to
choose the binding that supports sessions. So let us self host this service in a console application
that will expose the service via TCP. Since netTcpBinding
supports sessions, hosting this service
using this binding will supports sessions.
Details of the created endpoint is as follows:
-
Address:
net.tcp://localhost/TestService
- Binding:
netTcpBinding
-
Contract:
SampleServiceNamespace.SampleService
And the code to host the service will look like:
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(SampleServiceNamespace.SampleService)))
{
host.Open();
Console.WriteLine("Service up and running at:");
foreach (var ea in host.Description.Endpoints)
{
Console.WriteLine(ea.Address);
}
Console.ReadLine();
host.Close();
}
}
We can run the host to access our test service. Running the host will look like:
Note: Please refer to the app.config file of the host application to see the full configuration.
Test Client and testing sequence of operations
We will now create a simple test client that will call the WCF service functions. First let us try
to call the Function2
directly. Now since the Function1
is marked as IsInitiating=true
, direct call to
Function2
will result in an exception.
static void Main(string[] args)
{
using (ServiceReference1.SampleServiceClient client = new ServiceReference1.SampleServiceClient())
{
Console.WriteLine("Scenario 1: let try to call the function that required session to be present");
try
{
string result = client.Function2();
Console.WriteLine(result);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
Same will be the case if we try to call Function3
, since Function3
is marked as IsTerminating=true,
calling this
without calling Function1
will also result in exception.
static void Main(string[] args)
{
using (ServiceReference1.SampleServiceClient client = new ServiceReference1.SampleServiceClient())
{
Console.WriteLine("Scenario 2: Let us now try to call the terminating function without initating session");
try
{
string result = client.Function3();
Console.WriteLine(result);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
Now let us try the valid sequence of operations. We will call Function1
(which is marked as IsInitiating=true
)
then we will call Function2
(which is supposed to be called after Function1
and before Function3
) and finally we will call
Function 3(which is marked as IsTerminating=false
.
static void Main(string[] args)
{
using (ServiceReference1.SampleServiceClient client = new ServiceReference1.SampleServiceClient())
{
Console.WriteLine("Scenario 3: Now let us call the functions in proper order");
try
{
string result1 = client.Function1();
Console.WriteLine(result1);
string result2 = client.Function2();
Console.WriteLine(result2);
string result3 = client.Function3();
Console.WriteLine(result3);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
And now finally, let us try to call Function2
after call to Function3
has been completed. Since
Function3
has been marked as
IsTerminating=true
, call to
Function2
after this will result in exception.
static void Main(string[] args)
{
using (ServiceReference1.SampleServiceClient client = new ServiceReference1.SampleServiceClient())
{
Console.WriteLine("Scenario 4: Now let us call the function2 again when the session has been terminated");
try
{
string result1 = client.Function1();
Console.WriteLine(result1);
string result2 = client.Function2();
Console.WriteLine(result2);
string result3 = client.Function3();
Console.WriteLine(result3);
string result2_alt = client.Function2();
Console.WriteLine(result2_alt);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
And thus by setting the IsInitating
and IsTerminating
property we have enforced as sequence of operations
in our WCF service.
A Note on Durable WCF Services
WCF also provides a option for marking the service as Durable service. A durable service is a service that
persist the information of client sessions in some permanent stores like databases so that even in case
of service class instance goes off, it could regain the state information once a new instance for the same client
comes up. This is very useful when we need a stateful service running for a long time.
A service can be marked as DurableService
to make it a durable service.
The functions should also be marked as DurableOperation
to make
it a durable operation. The DurableOperation
supports the similar kind of properties to maintain order of
operations. For a durable operation, setting the CanCreateInstance
to true will make the operation as the
first operation in the sequence and setting the CompleteInstance
to true will make the operation as terminating
operation in in sequence.
Note: This is a very high level overview of durable services. Durable service creation requires some
more configuration to be done at service end.
Point of interest
In this article we saw how we can enforce an order/sequence of operations in a stateful WCF service. This
has been written from a beginner's perspective(but some knowledge of WCF service and instance modes is required).
I hope this has been informative.
History
-
25 March 2013: First version.