Creating an IIS hosted web service that uses callbacks and publishes metadata.

For callbacks to work, WCF requires the use of a network protocol that is session based, ruling out http as a protocol. Instead, we’ll use net.tcp as the protocol.

Service Contract

In our example, we’ll create a simple IM chat system. The service contract is very simple:

[ServiceContract(SessionMode = SessionMode.Required, 
                 CallbackContract = typeof(IChatServicesCallback))]
public interface IChatServices
{
    [OperationContract(IsOneWay = false, 
                       IsInitiating = true)]
    void Subscribe();

    [OperationContract(IsOneWay = false, 
                       IsInitiating = false, 
                       IsTerminating = true)]
    void Unsubscribe();

    [OperationContract(IsOneWay = true)]
    void SayMessage(ChatMessage message);
}
  • Marking the interface as SessionMode.Required ensures that a session based protocol is used, allowing callbacks to be specified.

  • The callback interface is specified by CallbackContract.

On the server side, the callback contract can have any name - but when the client proxies are generated by svcutil, the name used will be the name of the service suffixed with “Callback”. For consistency between client and server code, I suggest following this convention on the server as well.

Our callback contract looks like this:

public interface IChatServicesCallback
{
    [OperationContract]
    void MessagePublished(ChatMessage message);
}

Service Implementation

Implementation of the service is straightforward. The class declaration needs some WCF magic:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, 
                 ConcurrencyMode = ConcurrencyMode.Multiple)]
public class ChatServices : IChatServices
{
    ...
}
  • Specifying InstanceContextMode.PerSession ensures that we have a separate context for each session.

  • ConcurrencyMode.Multiple is necessary to allow the callbacks to be used.

The implementations of Subscribe() is simple - we just keep a reference to the OperationContext for later use.

public void Subscribe()
{
    var context = OperationContext.Current;
    lock (mPadLock)
    {
        if (!mClients.Contains(context))
        {
            mClients.Add(context);
        }
    }
}

Note that I’ve chosen to retain references to the OperationContext, instead of the callback interface itself. Doing this allows us to check the state of the connection to see if it’s still open before we use the callback.

Using the callback is relatively simple, as can be seen from the implementation of SayMessage().

public void SayMessage(ChatMessage message)
{
    List<OperationContext> clients;
    lock (mPadLock)
    {
        mClients.RemoveAll(
            c => c.Channel.State == CommunicationState.Closed
            || c.Channel.State == CommunicationState.Faulted);
        clients = mClients.ToList();
    }

    foreach (var chatClient in clients)
    {
        var chatServicesCallback 
            = chatClient.GetCallbackChannel<IChatServicesCallback>();
        chatServicesCallback.MessagePublished(message);
    }
}
  • The method begins by discarding any channels that are unusable, because the client’s closed the connection or because of an error.

  • For each remaining connection, the callback is obtained and the message published.

  • For clarity, exception handling isn’t shown, but in a real system you’d need to capture any exceptions thrown by the MessagePublished() call and handle them appropriately (logging would be a good start).

Client Implementation

Calling the service from the client follows pretty much the regular WCF style, but with the minor complication that you need to supply an object implementing the callback interface to handle the servers callbacks.

var messageReceiver = new MessageReceiver();
var context = new InstanceContext(messageReceiver);
mClient = new ChatServicesClient(context);
  • MessageReceiver is a class implementing IChatServicesCallback that we provide to the InstanceContext for WCF to callback.

IIS Configuration

Configuration of IIS to host the web service is mostly simple, but has a couple of traps for the unwary or uninformed.

Within the IIS Manager, select your web site (possibly Default Web Site) and choose Edit Bindingsfrom the context menu. Check that both http and net.tcp are shown correctly.

Why do we need to include http? While the service itself will be accessed through net.tcp, we still need to support http for publishing of metadata.

Select the virtual directory for your web application, and choose Advanced Settingsfrom the Actions sidebar.

Make sure that both http and net.tcp are listed under Enabled Protocols.

Web.Config

Make sure your service behavior includes a <serviceMetadata> element:

<behaviors>
  <serviceBehaviors>
    <behavior name="defaultBehavior">
      <serviceMetadata httpGetEnabled="false"/>
      <serviceDebug includeExceptionDetailInFaults="true" />
    </behavior>
  </serviceBehaviors>
</behaviors>

In my testing, it doesn’t seem to matter whether you have httpGetEnabled turned on (true) or off (false), but it does matter that you have a configuration setting at all.

Now, move on to the declaration of the service itself:

<services>
  <service name="Chat.Services.ChatServices"
           behaviorConfiguration="defaultBehavior">
    ...
  </service>
</services>

The first endpoint we need is for the service itself:

<endpoint address=""
          binding="netTcpBinding"
          name="NetTcpBindingEndpoint"
          contract="Chat.Services.IChatServices"/>

The second end point is to publish metadata about the service.

<endpoint address="mex"
          binding="mexHttpBinding"
          name="MexTcpBindingEndpoint"
          contract="IMetadataExchange"/>

Note that we have specified a relative url for the metadata endpoint address attribute. With this endpoint, metadata about the service will be available through http. If you are using svcutil to generate your service proxies, the commandline will look something like this:

svcutil http://server/Chat.Services/ChatServices.svc/mex 
        /enableDataBinding 
        /async 
        /serializer:DataContractSerializer 
        /namespace:*,Chat.Client

Comments

blog comments powered by Disqus