Saturday, 15 October 2011

Events

Events allow the client or clients to be notified about something that has occurred on the service side. An event may result from a direct client call, or it may be the result of something the service monitors. The service firing the event is called the publisher, and the client receiving the event is called the subscriber.
  • Publisher will not care about order of invocation of subscriber. Subscriber can be executed in any manner.
  • Implementation of subscriber side should be short duration. Let us consider the scenario in which you what to publish large volume of event. Publisher will be blocked, when subscriber is queued on previous subscription of the event. These make publishers to put in wait state. It may lead Publisher event not to reach other subscriber.
  • Large number of subscribers to the event makes the accumulated processing time of each subscriber could exceed the publisher's timeout
  • Managing the list of subscribers and their preferences is a completely service-side implementation. It will not affect the client; publisher can even use .Net delegates to manage the list of subscribers.
  • Event should always one-Way operation and it should not return any value

Definition

    public interface IMyEvents
    {
        [OperationContract(IsOneWay = true)]
        void Event1();
    }
Let us understand more on Event operation by creating sample service
Step 1 : Create ClassLibrary project in the Visual Studio 2008 and name it as WCFEventService as shown below.
Step 2: Add reference System.ServiceModel to the project
Create the Event operation at the service and set IsOnwWay property to true. This operation should not return any value. Since service has to communicate to the client, we need to use CallbackContract for duplex communication. Here we are using one operation to subscribe the event and another for firing the event.
public interface IMyEvents
    {
        [OperationContract(IsOneWay = true)]
        void Event1();
    }

   [ServiceContract(CallbackContract = typeof(IMyEvents))]
   public interface IMyContract
   {
       [OperationContract]
       void DoSomethingAndFireEvent();

       [OperationContract]
       void SubscribeEvent();

   }
Step 3: Implementation of the Service Contract is shown below.
In the Subscription operation, I am using Operationcontext to get the reference to the client instance and Subscription method is added as event handler to the service event. DoSomethingAndFireEvent operation will fire the event as shown.
MyPublisher.cs
   [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
   public  class MyPublisher : IMyContract
    {
        static Action m_Event1 = delegate { };

        public void SubscribeEvent()
        {
            IMyEvents subscriber = OperationContext.Current.GetCallbackChannel();
            m_Event1 += subscriber.Event1;
        }

        public static void FireEvent()
        {
            m_Event1();
        }

        public void DoSomethingAndFireEvent()
        {
            MyPublisher.FireEvent();           
        }
    }
Step 4: Create the Console application using Visual Studio 2008 and name it as WcfEventServiceHost. This application will be used to self-host the service.
Step 5: Add System.ServiceModel and WcfEventService as reference to the project.
static void Main(string[] args)
        {
            Uri httpUrl = new Uri("http://localhost:8090/MyPublisher/");
            ServiceHost host = new ServiceHost(typeof(WcfEventService.MyPublisher), httpUrl);
            host.Open();
            Console.WriteLine("Service is Hosted at {0}", DateTime.Now.ToString());
            Console.WriteLine("Host is running...Press  key to stop the service.");
            Console.ReadLine();
            host.Close();
        }
  
Step 6: Use Duplex binding to support Callback operation.
Web.Config
<system.serviceModel>
    <services >
      <service behaviorConfiguration="ServiceBehavior"  
      name="WcfEventService.MyPublisher">
        <endpoint address="http://localhost:8090/MyPublisher" 
        binding="wsDualHttpBinding" contract="WcfEventService.IMyContract">
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding"
         contract="IMetadataExchange"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="ServiceBehavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true "/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
Step7: Run the host application as shown below.
Step 8: Create the console application using visual studio and name it as WcfEventServiceClient as shown below. This application will act a client which is used to subscribe the event from service.
Step 9: Create the proxy class as shown below. Use DuplexClientBase to create the proxy, because it will support bidirectional communication. Create the contractor which will accept InstanceContext as parameter.
EventServiceClient.cs
 class EventServiceClient:DuplexClientBase<IMyContract>,IMyContract 
    {
        public EventServiceClient(InstanceContext eventCntx)
            : base(eventCntx)
        {
            
        }

        public void  DoSomethingAndFireEvent()
        {
            base.Channel.DoSomethingAndFireEvent();
        }

        public void SubscribeEvent()
        {
            base.Channel.SubscribeEvent();
        }
      
    }
Step 10: Implementation of IMyEvents at client side is shown below. This method will be called when service publish the event.
class MySubscriber : IMyEvents
    {
       public void Event1()
        {
            Console.WriteLine("Event is subscribed from the 
            service at {0}",DateTime.Now.ToString() );
        }
  
    }
Step 11: Main method of the client side you can find the creating Subscription instance and it passed to service using InstanceContext
 static void Main(string[] args)
        {
            IMyEvents evnt = new MySubscriber();
            InstanceContext evntCntx = new InstanceContext(evnt);

            EventServiceClient proxy = new EventServiceClient(evntCntx);
            Console.WriteLine("Client subscribe the event
             from the service at {0}",DateTime.Now.ToString());
            proxy.SubscribeEvent();
            Console.WriteLine("Client call operation which will fire the event");
            proxy.DoSomethingAndFireEvent();
            Console.ReadLine();
        }
Step 12: Run the client application and you see the when event is fired from the service. Subscriber got notification.

Callback Service

Till now we have seen that the all clients will call the service to get the things done. But WCF also provides the service to call the client. In which, service will act as client and client will act as service.
  • HTTP protocols are connectionless nature, so it is not supported for callback operation. So BasicHttpBinding and WSHttpBinding cannot be used for this operation.
  • WCF support WSDualHttpBinding for call back operation.
  • All TCP and IPC protocols support Duplex communication. So all these binding will be used for callback operation.

Defining and configuring a callback contract

Callback service can be enabled by using CallbackContract property in the ServiceContract attribute. In the below example you can find the decalration of the callback contract and it is configured in the ServiceContract attribute.
    public interface IMyContractCallback
    {
        [OperationContract]
        void OnCallback();
    }
    [ServiceContract(CallbackContract = typeof(IMyContractCallback))]
    public interface IMyContract
    {
        [OperationContract()]
        void MyMethod();
    }

Client Callback Setup

As I said earlier, in callback operation client will act as service and service will act as client. So client has to expose a callback endpoint to the service to call. In the earlier part of the tutorial I have mention that InstanceContext is the execution scope of inner most service instance. It provides a constructor that takes the service instance to the host.
 IMyContractCallback callback=new MyCallback();
        InstanceContext cntx=new InstanceContext(callback);

        MyServiceClient proxy = new MyServiceClient(cntx);
        proxy.MyMethod();   
The client must use a proxy that will set up the bidirectional communication and pass the callback endpoint reference to the service. This can be achieved by creating the proxy using DuplexClientBase
   class MyServiceClient:DuplexClientBase,IMyContract
    {
        public MyServiceClient(InstanceContext callbackCntx)
            : base(callbackCntx)
        {            
        }
        public void MyMethod()
        {
             base.Channel.MyMethod();
        }
    }

Service-Side Callback Invocation

The client-side callback endpoint reference is passed along with every call the client makes to the service, and it is part of the incoming message. The OperationContext class provides the service with easy access to the callback reference via the generic method GetCallbackChannel<T>( ). Service can call the client side callback method using reference e to the client side callback instance. The following code shows the callback method invocation.
IMyContractCallback
 callbackInstance=OperationContext.Current.GetCallbackChannel();
            callbackInstance.OnCallback();

One-Way

In One-Way operation mode, client will send a request to the server and does not care whether it is success or failure of service execution. There is no return from the server side, it is one-way communication.
Client will be blocked only for a moment till it dispatches its call to service. If any exception thrown by service will not reach the server.
Client can continue to execute its statement, after making one-way call to server. There is no need to wait, till server execute. Sometime when one-way calls reach the service, they may not be dispatched all at once but may instead be queued up on the service side to be dispatched one at a time, according to the service's configured concurrency mode behavior. If the number of queued messages has exceeded the queue's capacity, the client will be blocked even if it's issued a one-way call. However, once the call is queued, the client will be unblocked and can continue executing, while the service processes the operation in the background.

Definition :

One-way operation can be enabled by setting IsOneWay property to true in Operation contract attribute.
[ServiceContract]
public interface IMyService
{
    [OperationContract(IsOneWay=true)]
    void MyMethod(EmployeeDetails emp);
}

One-Way Operations and Sessionful Services

Let us see the example, what will happen when you use the one-way communication with Sessionful service.
    [ServiceContract(SessionMode = SessionMode.Required)]
    interface IMyContract
    {
        [OperationContract(IsOneWay = true)]
        void MyMethod();
    }
As per above configuration, when client makes one-way call using MyMethod() operation and if it close the proxy. Client will be blocked until operation completes. It will be good practice, that one-way operation should be applied on per-call and singleton service.
Suppose If you want to make use of One-way operation in Sessionful service, use in the last operation of the service which will terminate the session. This operation should not return any value.
    [ServiceContract(SessionMode = SessionMode.Required)]
    interface IMyContract
    {
        [OperationContract]
        void MyMethod1();

        [OperationContract]
        string MyMethod2();

        [OperationContract(IsOneWay = true, IsInitiating = false,
                                           IsTerminating = true)]
        string CloseSessionService(int id);
       
    }

One-Way Operations and Exceptions

Suppose when we are using BasicHttpBinding or WSHttpBinding, i.e. no transport session is used, if any exception throw by service will not affect the client. Client can make a call to the service using same proxy
[ServiceContract]
interface IMyContract
{
   [OperationContract(IsOneWay = true)]
   void MethodWithError( );

   [OperationContract]
   void MethodWithoutError( );
}
//Client side without transport session
MyContractClient proxy = new MyContractClient( );
proxy.MethodWithError( ); //No exception is thrown from serivce
proxy.MethodWithoutError( ); //Operation will execute properly
proxy.Close( );
In the presence of transport session, any exception thrown by service will fault the client channel. Client will not be able to make new call using same proxy instance.
//Client side transport session
MyContractClient proxy = new MyContractClient( );
proxy.MethodWithError( ); 
proxy.MethodWithoutError( ); //Can not executre because channel is faulted
proxy.Close( );

Oprations =Request-Reply

By default all WCF will operated in the Request-Replay mode. It means that, when client make a request to the WCF service and client will wait to get response from service (till receiveTimeout). After getting the response it will start executing the rest of the statement. If service doesn't respond to the service within receiveTimeout, client will receive TimeOutException.
Apart from NetPeerTcpBinding and the NetMsmqBinding all other bindings will support request-reply operations.

Throttling

WCF throttling provides some properties that you can use to limit how many instances or sessions are created at the application level. Performance of the WCF service can be improved by creating proper instance.
Attribute Description
maxConcurrentCalls Limits the total number of calls that can currently be in progress across all service instances. The default is 16.
maxConcurrentInstances The number of InstanceContext objects that execute at one time across a ServiceHost. The default is Int32.MaxValue.
maxConcurrentSessions A positive integer that limits the number of sessions a ServiceHost object can accept. The default is 10.
Service Throttling can be configured either Adminstractive or Programatically

Administrative(configuration file)

Using <serviceThrottling> tag of the Service Behavior, you can configure the maxConcurrentCalls, maxConcurrentInstances , maxConcurrentSessions property as shown below.
<system.serviceModel>
    <services >
      <service behaviorConfiguration="ServiceBehavior"  name="MyService">
        <endpoint address="" binding="wsHttpBinding" contract="IMyService">
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="ServiceBehavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true "/>
          <serviceThrottling maxConcurrentCalls="500"
 maxConcurrentInstances ="100" 
maxConcurrentSessions ="200"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

Programming Model

Use ServiceThrottlingBehavior object to set concurrent calls, session and instance property.
           ServiceHost host = new ServiceHost(typeof(MyService));
           ServiceThrottlingBehavior throttle
 = host.Description.Behaviors.Find();
            if (throttle == null)
            {
                throttle = new ServiceThrottlingBehavior();
                throttle.MaxConcurrentCalls = 500;
                throttle.MaxConcurrentSessions = 200;
                throttle.MaxConcurrentInstances = 100;
                host.Description.Behaviors.Add(throttle);
            }

            host.Open();
   

How to Create Durable Service step 9&10

Step 9: Create the Helper class called it as Helper.cs. This helper class is used to Store, Retrieve and set the context at the client side. Context information will be saved in 'token_context.bin' file. Copy and paste the below code to your helper file.
Helper.cs
using System.ServiceModel.Channels;
using System.ServiceModel;
using System.Net;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

    public class Helper
    {
        static readonly String TokenContextFileName = "token_context.bin";

        public static IDictionary<String, String> LoadContext()
        {
            IDictionary<String, String> ctx = null;

            try
            {
                using (FileStream fs = new 
                FileStream(TokenContextFileName, FileMode.Open, FileAccess.Read))
                {
                    BinaryFormatter bf = new BinaryFormatter();

                    ctx = bf.Deserialize(fs) as IDictionary<String, String>;

                    fs.Close();
                }
            }
            catch (Exception ex)
            {

            }
            return ctx;
        }

        public static void SaveContext(IClientChannel channel)
        {
            IDictionary<String, String> ctx = null;
            IContextManager cm = channel.GetProperty<IContextManager>();
            if (cm != null)
            {
                ctx = cm.GetContext() as IDictionary<String, String>;
                try
                {
                    using (FileStream fs 
                    = new FileStream(TokenContextFileName, FileMode.CreateNew))
                    {
                        BinaryFormatter bf = new BinaryFormatter();

                        bf.Serialize(fs, ctx);

                        fs.Close();
                    }
                }
                catch (Exception ex)
                {

                }
            }
        }

        public static void DeleteContext()
        {
            try
            {
                File.Delete(TokenContextFileName);
            }
            catch (Exception ex)
            {
            }
        }

        public static void SetContext(IClientChannel channel,
         IDictionary<String, String> ctx)
        {
            IContextManager cm = channel.GetProperty<IContextManager>();
            if (cm != null)
            {
                cm.SetContext(ctx);
            }
        }
    }
Step 10: In the main method, I was creating the proxy for the service and calling the Add operation. Call to this method will add instance state to the database. Now I have closed the proxy and creating new proxy instance. When I call the Subtract and Multiply operation, it will operate on the previously saved value (instance state).
static void Main(string[] args)
        {
           
             //Create the proxy for the service
            SimpleCalculatorService.SimpleCalculatorClient client 
            = new SimpleCalculatorService.SimpleCalculatorClient
            "WSHttpContextBinding_ISimpleCalculator");
            int currentValue = 0;
            //Call the Add method from the service
            currentValue = client.Add(10000);     
            Console.WriteLine("The current value is {0}", currentValue);
            //Save the Context from the service to the client
           Helper.SaveContext(client.InnerChannel); 
            //Close the proxy
            client.Close();
            
            //Create new Instance of the proxy for the service
            client = new SimpleCalculatorService.SimpleCalculatorClient
            ("WSHttpContextBinding_ISimpleCalculator");
            //Load the context from the client to start from saved state
            IDictionary<string,string> cntx=Helper.LoadContext();
            //Set Context to context manager
            Helper.SetContext(client.InnerChannel, cntx);
            //Call the Subtract and Multiply method from service
            currentValue = client.Subtract(2);
            Console.WriteLine("The current value is {0}", currentValue);
            currentValue = client.Multiply(5);
            Console.WriteLine("The current value is {0}", currentValue);
            //Delete the context from the client
            Helper.DeleteContext();
            //Remove persistance state from the server
            client.EndPersistence();
            Console.WriteLine("Press <ENTER> to shut down the client.");
            Console.ReadLine();
            client.Close();

        }
End of the proxy 1, service instance saved in the database as shown below.
Serialized XML instance state save in the database is shown below.
Output of the client application.

step 8

Step 8: Add WCF service as Service Reference to the project and name it as SimpleCalculatorService