Rashim's Blog

Posts Tagged ‘Callback


As a series of article, this time, I am going to introduce you with Event-based asynchronous communication. I have used here same service operation of IAsyncResult asynchronous operation that I have mentioned earlier. Service implementation and hosting are also same with IAsyncResult asynchronous. So, in this case I will not go to explain service section again as well as will not go for in details all parts as i have discussed it clearly in other two posts. I would suggest you to have a look on the given links first.

To develop a client is just straight forward. Here I have shown you a console app as a client,

public class Program
 {
 static void Main(string[] args)
 {
 var proxy = new MessageServiceProxy("MessageServiceEndpoint");
 proxy.GetMessageCompleted += GetMessageCompleted;
 proxy.GetMessageAsync("HI");
 Console.WriteLine("waiting");
 Console.ReadLine();
 }
 static void GetMessageCompleted(object sender, GetMessageCompletedEventArgs e)
 {
 Console.WriteLine(e.Result.ToString(CultureInfo.InvariantCulture));
 }
 }

And its corresponding configuration has been given below,

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <system.serviceModel>
 <bindings />
 <client>
 <endpoint address="http://localhost/Rashim.Rnd.WCF.Asynchronous.Services/"
 binding="basicHttpBinding" contract="Rashim.Rnd.WCF.Asynchronous.Services.IMessageService"
 name="MessageServiceEndpoint">
 <identity>
 <dns value="ServerCertificate" />
 </identity>
 </endpoint>
 </client>
 </system.serviceModel>
</configuration>

Now the important thing is how we develop our ServiceProxy which in our case is MessageServiceProxy .

public class MessageServiceProxy : ClientBase<IMessageService>, IMessageService
 {
 public event EventHandler<GetMessageCompletedEventArgs> GetMessageCompleted;
 private BeginOperationDelegate _onBeginGetMessage;

private EndOperationDelegate _onEndGetMessage;

private SendOrPostCallback _onGetMessageCompleted;

public MessageServiceProxy()
 {
 }

public MessageServiceProxy(string endpointConfigurationName) :
 base(endpointConfigurationName)
 {
 }

public IAsyncResult BeginGetMessage(string message, AsyncCallback callback, object asyncState)
 {
 return Channel.BeginGetMessage(message, callback, asyncState);
 }

public string EndGetMessage(IAsyncResult result)
 {
 return Channel.EndGetMessage(result);
 }

private IAsyncResult OnBeginGetMessage(object[] inValues, AsyncCallback callback, object asyncState)
 {
 var message = ((string)(inValues[0]));
 return BeginGetMessage(message, callback, asyncState);
 }

private object[] OnEndGetMessage(IAsyncResult result)
 {
 string retVal = Channel.EndGetMessage(result);
 return new object[] {
 retVal};
 }

private void OnGetMessageCompleted(object state)
 {
 if ((GetMessageCompleted != null))
 {
 var e = ((InvokeAsyncCompletedEventArgs)(state));
 GetMessageCompleted(this, new GetMessageCompletedEventArgs(e.Results, e.Error, e.Cancelled, e.UserState));
 }
 }

public void GetMessageAsync(string message)
 {
 GetMessageAsync(message, null);
 }

private void GetMessageAsync(string message, object userState)
 {
 if ((_onBeginGetMessage == null))
 {
 _onBeginGetMessage = OnBeginGetMessage;
 }
 if ((_onEndGetMessage == null))
 {
 _onEndGetMessage = OnEndGetMessage;
 }
 if ((_onGetMessageCompleted == null))
 {
 _onGetMessageCompleted = OnGetMessageCompleted;
 }
 InvokeAsync(_onBeginGetMessage, new object[] {
 message}, _onEndGetMessage, _onGetMessageCompleted, userState);
 }
 }

Here I have used three delegates namely onBeginGetMessage, onEndGetMessage and onGetMessageCompleted and these three delegates will register when we first call GetMessageAsync() method. onGetMessageCompleted  is a SendOrPostCallback which represents a callback method that we want to execute when a message is to be dispatched to a synchronization context.

Finally the GetMessageCompletedEventArgs,

public class GetMessageCompletedEventArgs : AsyncCompletedEventArgs
 {
 private readonly object[] _results;

public GetMessageCompletedEventArgs(object[] results, Exception exception, bool cancelled, object userState) :
 base(exception, cancelled, userState)
 {
 _results = results;
 }

public string Result
 {
 get
 {
 RaiseExceptionIfNecessary();
 return ((string)(_results[0]));
 }
 }
 }

Download Source code

Advertisements

In WCF, a service can call back to its clients. That is to say that, at the time of call back, the service behaves as a client as well as the client becomes the service, and the client must assist hosting the call back object. To support call back, the underlying transport of the binding must support bidirectional. So, for the nature of connectionless, we cannot use all bindings for the call back operation. For instance, BasicHttpBinding or WsHttpBinding does not support callback while WsDualHttpBinding supports it, because this binding establishes two HTTP channel to do that; one for client to service another for service to client. Also, NetTcpBinding and NetNamedPipeBinding supports callback since their underlying transport is bidirectional.

Callback service can be supported by using CallbackContract property in the ServiceContract attribute. Only one callback contract is allowed for a service contract. After defining a callback contract, we require to set up the client for up keeping the callback and also to offer the callback endpoint to the service for each and every call.

In the example given below, the callback contract defines the operations to which the service can call on the client endpoint

public interface INotificationServiceCallBack
{
    [OperationContract(IsOneWay = true)]
    void OnNotificationSend(string message);
}

The implementation of this Service Contract,

[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
public class NotificationServiceCallBack : INotificationServiceCallBack
{
    public void OnNotificationSend(string message)
    {
        Console.WriteLine(message);
        Console.ReadLine();
    }
}

Here CallbackBehavior refers how the callback of this operation will act which is similar to ServiceBehavior.

And the service contract has been defined like the way given below,

[ServiceContract(CallbackContract = typeof(INotificationServiceCallBack))]
public interface INotificationServices
{
    [OperationContract]
    void SendNotification(string message);
}

To make a way around for the service to call, clients must expose an endpoint to the service. This has been done by the following way,

public class Program
{
    public static INotificationServices Proxy
    {
        get
        {
            var ctx = new InstanceContext(new NotificationServiceCallBack());
            return new DuplexChannelFactory<INotificationServices>(ctx, "WSDualHttpBinding_INotificationServices").CreateChannel();
        }
    }
    static void Main(string[] args)
    {
        Proxy.SendNotification("Are u ready?");
        Console.ReadLine();
    }
}

Here InstanceContext is the execution scope of inner most service instance. It provides a constructor that takes the service instance to the host.

And now service implementation,

public class NotificationService : INotificationServices
{
    public INotificationServiceCallBack Proxy
    {
        get
        {
            return OperationContext.Current.GetCallbackChannel<INotificationServiceCallBack>();
        }
    }
    public void SendNotification(string message)
    {
        Console.WriteLine("\nClient says :" + message);
        Proxy.OnNotificationSend("Yes");
    }
}

In the above example, we see that the service has the proxy of INotificationServiceCallBack and call its operation which the clients will handle.

That’s it. Now the total code portion that i have used here has been given,

Services and Implementations,

public interface INotificationServiceCallBack
{
    [OperationContract(IsOneWay = true)]
    void OnNotificationSend(string message);
}

 

[ServiceContract(CallbackContract = typeof(INotificationServiceCallBack))]
public interface INotificationServices
{
    [OperationContract]
    void SendNotification(string message);
}
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
public class NotificationServiceCallBack : INotificationServiceCallBack
{
    public void OnNotificationSend(string message)
    {
        Console.WriteLine(message);
        Console.ReadLine();
    }
}
public class NotificationService : INotificationServices
{
    public INotificationServiceCallBack Proxy
    {
        get
        {
            return OperationContext.Current.GetCallbackChannel<INotificationServiceCallBack>();
        }
    }

    public void SendNotification(string message)
    {
        Console.WriteLine("\nClient says :" + message);
        Proxy.OnNotificationSend("Yes");
    }
}

Server and its configuration,

class Program
{
    static void Main(string[] args)
    {
        var svcHost = new ServiceHost(typeof(NotificationService));
        svcHost.Open();
        Console.WriteLine("Available Endpoints :\n");
        svcHost.Description.Endpoints.ToList().ForEach(endpoint => Console.WriteLine(endpoint.Address.ToString()));
        Console.ReadLine();
        svcHost.Close();
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="Rashim.RND.WCF.CallBack.Services.NotificationService">
        <host>
          <baseAddresses>
            <add baseAddress = "net.Tcp://localhost:8732/"/>
            <add baseAddress="http://localhost:8888"/>
          </baseAddresses>
        </host>
        <endpoint address="CallbackService" binding="netTcpBinding" contract="Rashim.RND.WCF.CallBack.Interfaces.INotificationServices"/>
        <endpoint address="CallbackService" binding="wsDualHttpBinding" contract="Rashim.RND.WCF.CallBack.Interfaces.INotificationServices"/>
        <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
        <endpoint address="http://localhost:8888/mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata />
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Client and its Configuration,

public class Program
{
    public static INotificationServices Proxy
    {
        get
        {
            var ctx = new InstanceContext(new NotificationServiceCallBack());
            return new DuplexChannelFactory<INotificationServices>(ctx, "WSDualHttpBinding_INotificationServices").CreateChannel();
        }
    }
    static void Main(string[] args)
    {
        Proxy.SendNotification("Are u ready?");
        Console.ReadLine();
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint address="http://localhost:8888/CallbackService" binding="wsDualHttpBinding" contract="Rashim.RND.WCF.CallBack.Interfaces.INotificationServices" name="WSDualHttpBinding_INotificationServices">
      </endpoint>
      <endpoint address="net.tcp://localhost:8732/CallbackService" binding="netTcpBinding"
 contract="Rashim.RND.WCF.CallBack.Interfaces.INotificationServices" name="NetTcpBinding_INotificationServices">
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>

%d bloggers like this: