Sunday, March 10, 2013

WCF - Duplex contract


A duplex contract allows clients and servers to communicate with each other independently so that either can initiate calls to the other.
The primary interface is used to send messages from client to service.
The callback interface is used to send messages from service back to client.
 
All Binding does not support duplex service. wsDualHttpBinding supports duplex communication over HTTP binding

 Duplex communication is possible over netTcpBinding and netNamedPipeBinding

Example :
Database Table
GO
/****** Object:  Table [dbo].[Emp]    Script Date: 03/08/2013 15:06:31 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Emp](
      [AutoID] [int] IDENTITY(1,1) NOT NULL,
      [Name] [varchar](50) NULL,
      [Designation] [varchar](50) NULL,
 CONSTRAINT [PK_Emp] PRIMARY KEY CLUSTERED
(
      [AutoID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF
 Project Structure :
 
Service Interface
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace WCF_DuplexCallBack
{
    // IN service contract we define Callback contract Interface eg:  INotifyUser
    [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(INotifyUser))]
    public interface IDuplexCallBack
    {
        [OperationContract]     
        int AddEmp(string Name, string Designation); // method to add employee
        [OperationContract]
        int DeleteEmp(Int32 EmpID);  // method to delete employee
    }
       

Declare the method signatures in the callback interface.

    // Call Back Interface
    public interface INotifyUser
    {
        //Create an operation contract in the Interface
        //Make sure return type is Void. 
        //Make sure IsOneWayProperty is set to true. 
        //There is no need to mark call back interface as ServiceContract.


        [OperationContract(IsOneWay = true)]
        void Initilizing(string Value);
       
        [OperationContract(IsOneWay = true)]
        void NotifyCollection(string message);

         [OperationContract(IsOneWay = true)]
        void NotifyUserDeleted(string message);
    }
    
   }


Service :
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace WCF_DuplexCallBack
{
    // Service Implementation
    // set the instance mode of the service to percall
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class DuplexCallBack : IDuplexCallBack
    {
         INotifyUser callback = null;
        public int AddEmp(string EmpName, string EmpDesignation)
        {
            //Create a call back channel (OperationConetxt to get current call back channel)
            INotifyUser callback= OperationContext.Current.GetCallbackChannel<INotifyUser>();

//instance of INotifyUser can be used to make call functions in call back interface contract at the client side
 
callback.Initilizing("Process Started for adding data for employee  : " + EmpName + "  Designation :" + EmpDesignation); 

            // Save data using entity framework
            int retval = 0;
            Emp em = new Emp();
            em.Name = EmpName;
            em.Designation = EmpDesignation;
            using (LinqTestEntities ctx = new LinqTestEntities())
            {
                ctx.Emps.AddObject(em);
                retval = ctx.SaveChanges();
                var empdata = from total in ctx.Emps
                              where total.Designation == EmpDesignation
                              select total;

                // calling call back function
                callback.NotifyCollection("Total for designation " + EmpDesignation + " " + empdata.Count());
            }                       
            return retval;                 
        }


      // Delete employee function
        public int DeleteEmp(Int32 EmpID)
        {
            //Create a call back channel (OperationConetxt to get current call back channel)
            INotifyUser callback = OperationContext.Current.GetCallbackChannel<INotifyUser>();
            int retval = 0;
          
            Emp em = new Emp();
            using (LinqTestEntities ctx = new LinqTestEntities())
            {
                var empdata = (from e in ctx.Emps
                               where e.AutoID == EmpID
                               select e).FirstOrDefault();
                 
                if (empdata != null)
                {

                    // calling call back function
                    callback.Initilizing("Process Started for deleteing employee having designation  " + empdata.Designation);

                    ctx.Emps.DeleteObject(empdata);
                    retval = ctx.SaveChanges();

                    var emp = from total in ctx.Emps
                              where total.Designation == empdata.Designation
                              select total;
                   
                    // calling call back function
                    callback.NotifyCollection("Total remains for designation " + empdata.Designation + " " + emp.Count());
                }
                else
                {
                    // calling call back function
                    callback.Initilizing("Unable to delete because Employee doesn't exist for ID : " + EmpID);
                }

            }
            return retval;
        }

    }
}

 
Webconfig  :
 <?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      </assemblies>
    </compilation>
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true" />
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>

      <serviceHostingEnvironment multipleSiteBindingsEnabled="false" />
    <services>
    <service name="WCF_DuplexCallBack.DuplexCallBack">
     
      <endpoint  address="http://localhost:54500/DuplexCallBack.svc"  binding="wsDualHttpBinding" contract="WCF_DuplexCallBack.IDuplexCallBack" />
    </service>

    </services>
 
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
  </system.webServer>

  <connectionStrings>
    <add name="LinqTestEntities" connectionString="metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=VAMDEV;initial catalog=LinqTest;user id=dev;password=c0nNd3v;multipleactiveresultsets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
  </connectionStrings>
</configuration>


Client  :
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DuplexClient.DuplexServiceClient;
using System.ServiceModel;
namespace DuplexClient
{

    // Callback class MyCallBack implimenting interface IDuplexCallBackCallback
    //Note: Since name of the service contract Interface is (IDuplexCallBack),
    //so the call back interface name will be (IDuplexCallBackCallback).
   // It is name of service contract suffixes by keyword CallBack.

    class MyCallBack : IDuplexCallBackCallback
    {
        //callback functions
        public void Initilizing(string value)
        {         
            Console.WriteLine("\t " + value);
        }

        public void NotifyCollection(string message)
        {
          Console.WriteLine("\t " + message);
        }

        public void NotifyUserDeleted(string message)
        {
          
            Console.WriteLine("\t " + message);
            Console.WriteLine("\n");
        }

 
    }



    class Program
    {
        static void Main(string[] args)
        {
         
            //Create an instance of InstanceContext and pass the reference of current class in the constructor
            //Instance Context represents context information of a service.
            // service reference or proxy is used to send messages to server and Instance Context is used to accept incoming messages
            InstanceContext instanceContext = new InstanceContext(new MyCallBack());

            //create proxy object

            DuplexCallBackClient client = new DuplexCallBackClient(instanceContext);
            client.AddEmp("Emp1", "Manager");
            Console.WriteLine("");

            client.AddEmp("Emp2", "Manager");
            Console.WriteLine("");

            client.AddEmp("Emp3", "Director");
            Console.WriteLine("");

            client.AddEmp("Emp4", "Director");
            Console.WriteLine("");

            client.DeleteEmp(3);
            Console.WriteLine("");

      
            Console.ReadLine();

            
        }
    }


}


OutPut  :
Output Explanation:

In client side we add 4 employees with two roles Manager and Directors .
In service Addemp method  implementation we call two methods Initilizing and NotifyCollection to notify client about what is happening on server side. Method Initilizing notify client that process of adding client started and method NotifyCollection notify client about total number of users of Current role

Note : In duplex contact server and client using same communication channel to communicate so some time we face error like port 80 is used by other application .

Solution of current problem is just to change client base address in web config file and add service reference again

            <?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      </assemblies>
    </compilation>
  </system.web>
  <system.serviceModel>
   
   
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true" />
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
   
    <serviceHostingEnvironment multipleSiteBindingsEnabled="false" />



    <bindings>

      <wsDualHttpBinding>

        <binding clientBaseAddress="http://locahost:6666/" name="WSDualHttpBinding_IPingService" closeTimeout="00:01:00"

        openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"

        bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"

        maxBufferPoolSize="524288" maxReceivedMessageSize="65536"

        messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true">

        

          <security mode="Message">
            <message clientCredentialType="Windows" negotiateServiceCredential="true"

            algorithmSuite="Default" />

            </security>

            </binding>

          </wsDualHttpBinding>

    </bindings>



    <services>
    
      <service name="WCF_DuplexCallBack.DuplexCallBack">
        <endpoint address="http://localhost:54500/DuplexCallBack.svc" binding="wsDualHttpBinding" contract="WCF_DuplexCallBack.IDuplexCallBack" />
      </service>
    </services>
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
  </system.webServer>
  <connectionStrings>
   <add name="LinqtestEntities" connectionString="metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=VIKAS-PC;Initial Catalog=Linqtest;User ID=sa;Password=c0nNd3v;MultipleActiveResultSets=True&quot;" providerName="System.Data.EntityClient" />
  </connectionStrings>
</configuration>

No comments :

Post a Comment