A Simple WCF Service Callback Example

I’ve done a lot with WCF services over the past few years, but I haven’t done much with callbacks. I wanted to write a simple application to understand and demonstrate how a callback service works in WCF. The example described below was implemented in a single solution with two console application projects, one client and one server.

Create the server

The first thing we’ll do is create and host our WCF service. This will be done in five short steps:

  1. Create the service contract
  2. Create the service callback contract
  3. Create the service implementation
  4. Modify the endpoint configuration
  5. Host the service

To create the service contract, service implementation, and default endpoint configuration, you can use Visual Studio’s menu to choose Add New Item > Visual C# Items > WCF Service. I named my service MyService, and so my service contract is called IMyService. Visual Studio creates the service contract with a single method, DoWork. I decided that I wanted my service to have two methods: OpenSession and InvokeCallbackOpenSession will be used by the server to store a copy of the callback instance, and InvokeCallback will be used to trigger a callback call to the client from the client.

Here’s my complete IMyService contract:

using System.ServiceModel;

namespace CallbackService.Server
{
    [ServiceContract(CallbackContract = typeof(IMyServiceCallback))]
    public interface IMyService
    {
        [OperationContract]
        void OpenSession();
    }
}

Note that the service contract indicates the callback contract in its attribute. The callback contract, IMyServiceCallback, will have a single method, OnCallback. Here is the complete IMyServiceCallback interface:

using System.ServiceModel;

namespace CallbackService.Server
{
    public interface IMyServiceCallback
    {
        [OperationContract]
        void OnCallback();
    }
}

The third step requires us to implement our service. When OpenSession is called, I create a timer that will invoke the callback once per second. Note the ServiceBehavior attribute that sets the ConcurrencyMode to Reentrant. If you do not change the ConcurrencyMode to Multiple or Reentrant, the channel will be locked and the callback will not be able to be invoked. Here is the complete MyService implementation:

using System;
using System.ServiceModel;
using System.Timers;

namespace CallbackService.Server
{
    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
    public class MyService : IMyService
    {
        public static IMyServiceCallback Callback;
        public static Timer Timer;

        public void OpenSession()
        {
            Console.WriteLine("> Session opened at {0}", DateTime.Now);
            Callback = OperationContext.Current.GetCallbackChannel<IMyServiceCallback>();

            Timer = new Timer(1000);
            Timer.Elapsed += OnTimerElapsed;
            Timer.Enabled = true;
        }

        void OnTimerElapsed(object sender, ElapsedEventArgs e)
        {
            Callback.OnCallback();
        }
    }
}

When we added the new WCF service to the project, Visual Studio added the default endpoint configuration to the app.config. By default, wsHttpBinding is used as the binding. We want to change it to use wsDualHttpBinding which supports two-way communication. I also changed the URL to be friendlier, but that is not necessary. Here’s my final app.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="CallbackService.Server.MyService">
        <endpoint address="" binding="wsDualHttpBinding" contract="CallbackService.Server.IMyService">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8090/CallbackService.Server/MyService/" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

The final step is to simply host the service. Since my server is a console application, I do this in the Program.Main method. Here is the complete class:

using System;
using System.ServiceModel;

namespace CallbackService.Server
{
    class Program
    {
        static void Main(string[] args)
        {
            var host = new ServiceHost(typeof(MyService));
            host.Open();
            Console.WriteLine("Service started at {0}", DateTime.Now);
            Console.WriteLine("Press key to stop the service.");
            Console.ReadLine();
            host.Close();
        }
    }
}

Create the client

With the server complete, creating the client is a breeze. We’ll complete the client in just three steps:

  1. Add a service reference
  2. Implement the callback class
  3. Create the client proxy

To add the service reference, you must have the service running. I did this by simply running the server executable outside of Visual Studio. You can copy the URL from the server’s app.config. Right-click References in your client project, choose Add Service Reference, and paste the URL. I named my service reference MyServiceReference.

The service reference pulls in IMyServiceCallback, which will allow us to create our callback class. To do this, just add a new, empty class to the project. I named my class MyServiceCallback, and when the callback is invoked, it just writes a line to the console. Here is the complete implementation:

using System;
using CallbackService.Client.MyServiceReference;

namespace CallbackService.Client
{
    public class MyServiceCallback : IMyServiceCallback
    {
        public void OnCallback()
        {
            Console.WriteLine("> Received callback at {0}", DateTime.Now);
        }
    }
}

The client application is also a console application, so creating and using the proxy client will occur in its Program.Main. I added a pause to allow the server time to host the service, and then I invoke the remote procedure OpenSession. This will trigger the service to begin executing callbacks to the client application every second, resulting in a line written to the console. Here’s is the contents of my Program.cs:

using System;
using System.ServiceModel;

namespace CallbackService.Client
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Press enter to continue once service is hosted.");
            Console.ReadLine();

            var callback = new MyServiceCallback();
            var instanceContext = new InstanceContext(callback);
            var client = new MyServiceReference.MyServiceClient(instanceContext);
            client.OpenSession();

            Console.ReadLine();
        }
    }
}

Conclusion

And that’s all there is to it! This is clearly a simple, unorganized example with few complexities, but it demonstrates the core functionality provided by callbacks in WCF services. This capability allows for push updates and other real-time communication between client and server, and it really opens a world of possibilities. This is certainly a welcome option in my ever-growing development toolbox!

Advertisements

24 thoughts on “A Simple WCF Service Callback Example”

  1. It’s still a mystery what happens in the line:
    var client = new MyServiceReference.MyServiceClient(instanceContext);
    so I’m going to beg to differ on the simplicity of the 3 simple steps

    1. I don’t think it’s quite as complicated as you’re making it out to be. MyServiceReference.MyServiceClient is a class generated from adding the service reference. The instanceContext “represents the context information for a service instance” (from MSDN) where you can store data items such as a callback.

      So, all this line is doing is instantiating a service client proxy while providing it with a callback to provide two-way capability.

      1. I didn’t really make it out to be anything; I explicitly referred to how simple it *wasn’t*, and a low count of 3 steps. It turned out to be a ClientBase which has a DuplexChannelFactory. As for your class generated by a service reference, this corresponds to a lot of code-behind and a .config file. All of which depend on IDE help and additional knowhow as to invoking said help. If I were just happening on either your reply or my reply to yours I still wouldn’t be able to decipher the simplicity from the complexity. I’ve found my solution but still, good luck to others, and thanks to you for pointing me in the right direction because you definitely did lead me there.

  2. Hello Adam,
    I am new to WCF and tried your example.
    When I run it I get the error in the client that the CallbackContractType Çlient.ServiceReference1.IMyServiceCallback is not implemented.
    Do you have an idea what I might have forgot?

  3. What If i need a service, which startsup and does things even when no client is connected? The constuctor of the wcf is not called when I start the hosting app, but only when the first client connets to it. What if i need somthing to e.g. monitor a value on its own and raises an event in some cases, and when that event occurs does some serverside things (like writing that to a log) and if there is a client conneted raises a callback to inform it?
    I dont see how to implement that with wcf.

    1. You would do this by putting your monitoring logic outside of the WCF service. Start this monitor at the same time you host the WCF service. Then, when clients connect, the WCF service can register the client’s callback to handle events from the monitor.

  4. Thanks for the post. It was exactly what I was looking at this moment. One question though. Wouldn’t be better if you have used named pipe? I will try myself.

      1. hi
        Sure. I mentioned named pipes,as in your example the console apps were run on the same computer. Named Pipe or NetTcp binding would possibly better suited for performance and security purposes, if inter process communications or intranet is aimed. Regards.

  5. Hi,
    I followed your tutorial ut I keep having the same error when I run my server : “Failed to add a service. Service metadata may not be accessible. Make sure your service is running and exposing metadata.”
    I couldn’t find anything on the internet.
    Do you have any clue about this ?

    1. you shouldn’t add this as a service reference.
      if you do so, you’ll need to create an endpoint for the client to consume

  6. This is really a nice example. I am trying to figure out how to pass parameters to the callback function and have it return a value.
    Ideas?

  7. I copied this and getting the following

    Service ‘Service.MyService’ has zero application (non-infrastructure) endpoints. This might be because no configuration file was found for your application, or because no service element matching the service name could be found in the configuration file, or because no endpoints were defined in the service element.

    at line

    host.Open();

      1. In the xml

        service name=”Service.MyService”

        endpoint address=”” binding=”wsDualHttpBinding” contract=”Service.IMyService”

        add baseAddress=”http://localhost:19919/”

        Those are the only lines different from your example because my namespace is different.

    1. using System;
      using System.Timers;
      using System.ServiceModel;

      namespace Service
      {
      [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
      public class MyService : IMyService
      {
      public static IMyServiceCallback Callback;
      public static Timer Timer;

      public void OpenSession()
      {
      Console.WriteLine(“> Session opened at {0}”, DateTime.Now);
      Callback = OperationContext.Current.GetCallbackChannel();

      Timer = new Timer(1000);
      Timer.Elapsed += OnTimerElapsed;
      Timer.Enabled = true;
      }

      void OnTimerElapsed(object sender, ElapsedEventArgs e)
      {
      Callback.OnCallback();
      }
      }
      }

      1. using System;
        using System.ServiceModel;

        namespace Service
        {
        [ServiceContract(CallbackContract = typeof(IMyServiceCallback))]
        public interface IMyService
        {
        [OperationContract]
        void OpenSession();
        }

        public interface IMyServiceCallback
        {
        [OperationContract]
        void OnCallback();
        }
        }

    2. using System;
      using System.ServiceModel;
      using System.ServiceModel.Description;
      using Service;

      namespace TheServiceHost
      {
      class Program
      {
      static void Main(string[] args)
      {
      var host = new ServiceHost(typeof(Service.MyService));
      host.Open();
      Console.WriteLine(“Service started at {0}”, DateTime.Now);
      Console.WriteLine(“Press key to stop the service.”);
      Console.ReadLine();
      host.Close();
      }

      }
      }

  8. Very helpful example. It explains two things:
    1- Create WCF service step-by-step.
    2- Create the Callback method.

    Thanks for sharing.

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s