Click here to Skip to main content
15,867,895 members
Articles / WCF

A Beginner's Guide to Duplex WCF

Rate me:
Please Sign up or sign in to vote.
4.74/5 (31 votes)
8 May 2013CPOL21 min read 193.8K   8.8K   107   22
The purpose of this article is to create a service which accepts messages from any client and redistributes those messages to all subscribed clients, and a client that can subscribe to the service, send messages to it and receive unrelated messages from it regardless of how many it sends.

Objective

The purpose of this article is to create a service which accepts messages from any client and redistributes those messages to all subscribed clients, and a client that can subscribe to the service, send messages to it and receive unrelated messages from it regardless of how many it sends. The result is a client/service combination using WCF (Windows Communications Foundation).

Example Origin

The example is driven by a system I am working on, where purchase and consumption data needs to be shared in real time between different installations.

This is actually my first ever C# application, my system being written in C++/CLI. By I opted for C# in for this feature because it offers neater communications protocols in the WCF.

Introducing the Sample Application - and its Heritage

I have chosen to use the example of a simple messaging service to learn, and now illustrate how I learned duplex WCF correspondence. The service maitains a list of subscribed clients, and when any one of them sends in a message, it distributes it to all subscribed clients. Each client in turn, subscribes to the service with a 'Join' request, sends message to the service, accepts any messages the service throws at it and eventually may opt to leave the service.

Initially I wanted to use webservices in C++/CLI but there was no proper dublex communication protocol available, the best being implementing both client and server functionality on every installation. So off I went to research C# and the WCF.

My entire offering here has its origins in two excellent sources of inspiration, both of which were essential in achieving my objective.

  • [TROELSEN] - Chapter 25 of "Pro C# 2010 and the .NET 4 Platform (Fifth Edition)" By Andrew Troelsen
  • [BARNES] - "WCF: Duplex Operations and UI Threads" by Jeff Barnes, right here on CodeProject

There are times in the text where I may appear critical of one or the other. But the reality is that both have very different target audiences and both are excellent in how they address those audiences.

[TROELSEN] is for beginners, and without it I would not have been able to get started with WCF, but doesn't do much besides make reference to the existence of duplex activity. On the other hand [BARNES] provides an excellent article on duplex communication, but I was completely lost when I tried to use it as an introduction to WCF.

WCF - Easy as ABC

Before we go any further I recommend you read one of the many introductory excellent articles on WCF here at Codeproject, or an author such as [TROELSEN]

The only theory I will emphasize ie the importance of ABC:

  • Address - the address of your new service
  • Binding - the transport protocol e.g. HTTP / TCP used to carry the messages
  • Contract - a description of the set of method that will handle the message exchange

The Service

The Service is the central hub of all the communications that will take place between the clients. To keep things clear, I have implemented it and the other two aspects, the host and the client each in their own solution

Create a new C# Class Library project named GPH_QuickMessageServicelib.lib (xxxxlib.lib could be considered name overkill but the host and service that follow will be similarly named so having lib at the end helps preserve uniqueness in the namespace titles).

Select a class library as the service project type

Click OK

Close Class1.cs. Use the Solution Explorer to rename Class1.cs as GPH_QuickMessageService.cs, and answer Yes when prompted to extend the rename to all references as seen below.

Confirm the Renaming

Reopen GPH_QuickMessageService.cs now. Add a reference to the System.ServiceModel.dll assembly using the Solution Explorer again, right clicking on GPH_QuickMessageServiceLib seen here highlighted.

Click to Add Service

Choose Add Reference from the menu (Add Service Reference will be used later when you need to add the working service to a client)

Include a using statement in GPH_QuickMessageService.cs so that it now looks like this:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

// Reference added to make WCF happen
using System.ServiceModel;

namespace GPH_QuickMessageServiceLib
{
    public class GPH_QuickMessageService
    {
    }
}

The Contract

I am going to use a single contract in this example represented by a pair of interfaces called IMessageServiceInbound and IMessageServiceCallback. The objective of the interfaces is to allow clients to register with the service on a Publish and Subscribe basis, where users Identify themselves to the system. The interface will

  • Accept messages from the user
  • Route those messages to the named registered users
  • Notify the user as other users Register with and Resign from the service

Warning

The system will allow you to omit [OperationContract] from your methods, but if you make that omission, those methods will not be exposed through the WCF Runtime [TROELSEN].

We are defining them in GPH_QuickMessageService.cs. The [ServiceContract] attribute on the IMessageServiceInbound interface notifies WCF that a service is to be operated. Adding CallbackContract property attaches the call back interface (IMessageServiceCallback) to it. This forms an association between the two interfaces, and in order to consume the service operations, the client must implement the callback contract and host the object for invocation by the service [BARNES].

IMessageServiceCallback does not have to be given a ServiceContract attribute because WCF considers it implied due to it being listed as the CallBackContract. You may add it for completeness if you wish.

This is the current state of GPH_QuickMessageService.cs:

C#
/// <summary>
/// GPH Quick Message Service Operations
/// </summary>

[ServiceContract(
    Name = "GPH_QuickMessageService",
    Namespace = "GPH_QuickMessageServiceLib",
SessionMode = SessionMode.Required,
    CallbackContract = typeof(IMessageServiceCallback))]

public interface IMessageServiceInbound
{
    [OperationContract]
    int JoinTheConversation(string userName);
    [OperationContract(IsOneWay = true)]
    void ReceiveMessage(string userName,List<string> addressList, string userMessage);
    [OperationContract]
    int LeaveTheConversation(string userName);

}

public interface IMessageServiceCallback
{
    [OperationContract(IsOneWay = true)]
    void NotifyUserJoinedTheConversation(string userName);

    [OperationContract(IsOneWay = true)]
    void NotifyUserOfMessage(string userName, String userMessage);

    [OperationContract(IsOneWay = true)]
    void NotifyUserLeftTheConversation(string userName);
}

The functions you see here in the contract handle receive Join requests, Messages, Leave Requests and use three corresponding Notify functions to let the other clients know what is happening

Adding a Behavior to the Contract

A behavior in the words of [TROELSEN] allows you to qualify further the functionality of a host, service or client. The example I am using is from [BARNES] where he is using the behavior to determine concurrency: "Single is the default concurrency mode, but it never hurts to be explicit Note the single threading model still functions properly with use of one way methods for callbacks since they are marked as one way on the contract."

C#
[ServiceBehavior(
        ConcurrencyMode = ConcurrencyMode.Single,
        InstanceContextMode = InstanceContextMode.PerCall)]

Interfacing the Service Class with the Contract

The inbound contract is implemented as an interface on the service class:

C#
public class GPH_QuickMessageService : IMessageServiceInbound
{
}

This is conventional webservice communication, but the outbound contract has not been forgotten. The CallbackContract attribute on the ServiceContract definition ties it to the inbound contract.

Service Implementation

I will follow the [TROELSEN] example here and continue coding in GPH_QuickMessageService.cs, the [BARNES] example puts the implementation of the callback in its own .cs – but while I learn the practice of using WCF I what to see as much of the mechanics in close proximity as possible. I can modularize them to follow best practice after I become comfortable with the constructs. While [BARNES] is clearly laid out in a modular format, as a beginner I am finding it difficult to make the mental connection between the components.

[TROELSEN] only has one function to create to satisfy his contract with which his service is complete and, but my example here following the [BARNES] model has six, and critically three of them are callbacks. There is still work to do, but the [BARNES] narrative is difficult for the beginner to see what comes next.

First up, is the definition of a list to hold the call back channels:

C#
private static List<IMessageServiceCallback> _callbackList = new List<IMessageServiceCallback>();

Of course, not forgetting a default constructor:

C#
public GPH_QuickMessageService() { }

The service has six methods or functions three of which handle inbound messages (JoinTheConversation,ReceiveMessage and LeaveTheConversation). These will be mirrored by three others on the client side (NotifyUserJoinedTheConversation, NotifyUserOfMessage, and NotifyUserLeftTheConversation) who recieve those messages for each subscribed client. You have already met these at a high level when you defined your contract above. Now we are implementing that contract

JoinTheConversation

This method receives requests from clients to join the conversation. It creates a user variable of type IMessageServiceCallback, checks to see if this user is already on the callback list (adding it if not). It finishes up by notifying all users that this new user has joined the conversation.

C#
public int JoinTheConversation(string userName)
{
    // Subscribe the user to the conversation
    IMessageServiceCallback registeredUser = 
      OperationContext.Current.GetCallbackChannel<IMessageServiceCallback>();

    if (!_callbackList.Contains(registeredUser))
    {
        _callbackList.Add(registeredUser);
    }            

    _callbackList.ForEach(
        delegate(IMessageServiceCallback callback)
        { 
            callback.NotifyUserJoinedTheConversation(userName);
            _registeredUsers++;
        });

    return _registeredUsers;
}

ReceiveMessage

Currently a very simple method, it broadcasts a message and its author to the list of registered users. It contains an address list variable that is unused. It has been declared for future use when the functionality is tailored to broadcast to specific users.

C#
public void ReceiveMessage(string userName,List<string> addressList, string userMessage)
{
 
    // Notify the users of a message.
    // Use an anonymous delegate and generics to do our dirty work.
    _callbackList.ForEach(
        delegate(IMessageServiceCallback callback)
            { callback.NotifyUserOfMessage(userName, userMessage); });
}

LeaveTheConversation

This method removes a user from the callback list and notifies the remaining users that the removed users has left the conversation.

C#
public int LeaveTheConversation(string userName)
{
    // Unsubscribe the user from the conversation.      
    IMessageServiceCallback registeredUser = 
      OperationContext.Current.GetCallbackChannel<IMessageServiceCallback>();

    if (_callbackList.Contains(registeredUser))
    {
        _callbackList.Remove(registeredUser);
        _registeredUsers--;
    }

    // Notify everyone that user has arrived.
    // Use an anonymous delegate and generics to do our dirty work.
    _callbackList.ForEach(
        delegate(IMessageServiceCallback callback)
            { callback.NotifyUserLeftTheConversation(userName); });            

    return _registeredUsers;
}

Compiling

Your service library is now ready to compile. Remember again, the three notify methods referenced above will be implemented on the client side to receive any notifications distributed by the service.

Concurrency

A quick note on concurrency before moving on. The IsOneWay property on the OperationContract attribute looks out of place on a duplex example. This is a single threaded piece of code, so the thread is locked while each message is being handled. When a function returns a reply, there is a risk of causing deadlock or a timeout. The IsOneWay property means that there are no replies sent to the originator. Any responses posted by other users are in the form of new messages. My preferred approach is a multithreaded application but I have a while to go before putting one of those in place. All OneWay functions must be declared void. My Join and Leave methods do return a response, but the client I will implement chooses to ignore them. If the client was to wait on those responses it would lock up until they arrived without putting some form of threading in place.

[BARNES]. The use of one way service operations allows the callback to occur while still using the single concurrency mode rather than Reentrant or Multiple. See the [BARNES] example for more on the Concurrency Mode including how it is used in the service to prevent locking.

Hosting the service

I will follow [TROELSEN] on this and use a new solution to host the new message service. In time I will use a windows service for this, but at the moment I am sticking with a console application as per the text I am following. This new console application will be called GPH_QuickMessageServiceHost.

Select a console application as the servicehost project type

To begin with, it needs the System.ServiceModel and GPH_MessageServiceLib. Do this using the Solution Explorer as above. Use the Browse tab to hunt for the GPH_MessageServiceLib.dll.

These also have to be imported into the code file:

C#
using System.ServiceModel;
using GPH_QuickMessageServiceLib;

Endpoints (The term used to describe the ABC of WCF or Address / Binding /Contract rolled into one) etc can be defined in the source code itself, but I prefer to use an App.Config for them. Add one now using Project->Add New Item:

Adding App.config

Click Add to complete.

Add this XML Snippet between the configuration tags in the App.Config file:

XML
 <system.serviceModel>
    <services>
      <service name="GPH_QuickMessageServicelib.GPH_QuickMessageService">
        <endpoint address ="http://localhost:8080/GPH_QuickMessageService"
        binding="basicHttpBinding"
        contract="GPH_QuickMessageService.IMessageServiceInbound"/>
      </service>
    </services>
</system.serviceModel>

service name has specified the DLL name followed by the implementing class name.

endpoint address is a URL style reference to the local machine including the implementing class name. This can be any URL anywhere on the web where the service is running.

binding Is one of the standards in this case basicHttpBinding, dictated by the use of an http address.

contract specifies the implementing class name and its interface to the Service Contract.

All the key details are wrapped in the <system.serviceModel> </system.serviceModel> tags.

Code to Host the Service

This is the piece of magic in the host main method that gets the service up and running:

C#
using (ServiceHost serviceHost = new ServiceHost(typeof(GPH_QuickMessageService)))
{
 // Open the host and start listening for incoming messages.
 serviceHost.Open();
 // Keep the service running until the Enter key is pressed.
 Console.WriteLine("The service is ready.");
 Console.WriteLine("Press the Enter key to terminate service.");
 Console.ReadLine();
}

This distills down to defining a variable of type ServiceHost, tied to the WCF service defined earlier and invoking its open method.

This can now be compiled and run to launch a working service. All it needs now is a client. But it doesn’t launch. A quick spin on the debugger suggested:

Runtime error on host

Clearly there is work to do before moving on to the Client.

Change the binding in App.Config to wsDualHTTPbinding.

binding="wsDualHttpBinding"
C#
service name="GPH_QuickMessageServicelib.GPH_QuickMessageService"	  

and

C#
contract="GPH_QuickMessageService.IMessageServiceInbound"

Need to become:

C#
service name="GPH_QuickMessageServiceLib.GPH_QuickMessageService"

and

C#
contract="GPH_QuickMessageServiceLib.IMessageServiceInbound"

This in turn may kick out another issue:

Host Access Error

The Exe needs to run with administrator privileges. Locate it using explorer, right click on it and choose Run as administrator. Answer Yes when asked to confirm the action. This is the Console window now:

Running Service

Alternatively, launch Visual Studio in administrator mode to begin with.

Enabling MEX

MEX or Metadata Exchange is used to define runtime behaviours to further tune how the service will behave. I have followed the [TROELSEN] example in implementing them here – they also feature in the [BARNES] model. In this example I am adding a new Endpoint for MEX, a WCF behaviour to allow HTTP GET access, and the behaviourConfiguration attribute will be used match the behaviour up to the service. A Host element will define the base address for the benefit of MEX.

Rework the App.config file as follows:

XML
<system.serviceModel>
    <services>
      <service name="GPH_QuickMessageServiceLib.GPH_QuickMessageService"
        behaviorConfiguration = "QuickMessageServiceMEXBehavior">
        <endpoint address ="service"
        binding="wsDualHttpBinding"
         contract="GPH_QuickMessageServiceLib.IMessageServiceInbound"/>
        <!-- Enable the MEX endpoint -->
        <endpoint address="mex"
        binding="mexHttpBinding"
        contract="IMetadataExchange" />

        <!-- Need to add this so MEX knows the address of our service -->
        <host>
          <baseAddresses>
            <add baseAddress ="http://localhost:8080/GPH_QuickMessageService"/>
          </baseAddresses>
        </host>

      </service>
    </services>
    <!-- A behavior definition for MEX -->
    <behaviors>
      <serviceBehaviors>
        <behavior name="QuickMessageServiceMEXBehavior" >

          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
</system.serviceModel>

Compile the host again, and look at the service in a web browser: http://localhost:8080/GPH_QuickMessageService.

Service in a window

Building a Client

Building a client using C# is a new departure for me because this is my first time using the language.

I am going to begin by creating a new Windows Forms Application called GPH_QuickMessageServiceClient.

Create Client

Click OK. Then use the solution explorer to rename From1.cs to MessageForm.cs, clicking Yes when asked:

Confirm Rename

You will need to have your service running for the next step to succeed. Staying with Solution Explorer, right click on the solution, but this time choose Add Service Reference. Paste in the URL http://localhost:8080/GPH_QuickMessageService into the address box on the Add Service Reference dialog:

Add Service Reference

Click ‘Go’, and the IDE will attempt to download the service details. This is the expected outcome:

Service Reference Added

I was happy to leave the name at ServiceReference1, but you are free to change it if you wish. According to [TROELSEN] this adds the proxy for the service and any references the WCF assemblies automatically. In practice it will create an App.config if your project does not have one, or add to an existing one if it is already there, it also creates the proxy called Reference.cs. The proxy is the representative of the service at the client site. Expand the Reference entry on the Solution Explorer to see them. Next right-click on MessageForm.cs and select View Code from the popup. There are some of Using statement needed:

C#
using System.ServiceModel;
using System.Threading; 
using System.ServiceModel;
// Location of the proxy.
using GPH_QuickMessageServiceClient.ServiceReference1;

You will also need to add the reference to system.ServiceModel as you did for the serivce library. We has already added its using statement.

Return to the form designer and modify MessageForm until it looks something like:

The finished client in use

It includes four command buttons, brnJoin, btnSend, btnLeave, and btnExit along with three text boxes, txtName, txtMessageOutbound, and txtMessageLog which will need the Scrollbars property set to Vertical. txtName will need a Text_Changed event on it.

We will begin with Join, Leave and Send disabled. Join will become available when a name is entered. If that name is accepted, then the name box will become readonly, Join is disabled and Send enabled.

Add form load, form closing and a text changed (on Name) events, also add click events on each of the buttons. MessageForm.cs also needs some communication information.

C#
public partial class MessageForm : Form
{
   public MessageForm()

is extended to become:

C#
// Specify for the callback to NOT use the current synchronization context
[CallbackBehavior(
    ConcurrencyMode = ConcurrencyMode.Single,
    UseSynchronizationContext = false)]
public partial class MessageForm : Form, ServiceReference1.GPH_QuickMessageServiceCallback
{
    private SynchronizationContext _uiSyncContext = null;
    private ServiceReference1.GPH_QuickMessageServiceClient _GPH_QuickMessageService = null;
    public MessageForm()

form_load

The form load event needs to signal its presence to the service:

C#
// Capture the UI synchronization context
_uiSyncContext = SynchronizationContext.Current;

// The client callback interface must be hosted for the server to invoke the callback
// Open a connection to the message service via
// the proxy (qualifier ServiceReference1 needed due to name clash)
_GPH_QuickMessageService = 
    new ServiceReference1.GPH_QuickMessageServiceClient(new InstanceContext(this), 
    "WSDualHttpBinding_GPH_QuickMessageService");
_GPH_QuickMessageService.Open();

The name WSDualHttpBinding_GPH_QuickMessageService comes from the auto generated app.config.

Set the initial states of the fields and buttons on the form:

C#
this.btnJoin.Enabled = false;
this.btnSend.Enabled = false;
this.btnLeave.Enabled = false;
this.btnExit.Enabled = true;
this.txtMessageOutbound.Enabled = false;

Two event handlers need to be declared:

C#
this.txtName.TextChanged += new EventHandler(txtName_TextChanged);
this.FormClosing += new FormClosingEventHandler(MessageForm_FormClosing);

form_closing

This event fulfills the important task of terminating the relationship with the service when the form is closing:

C#
_GPH_QuickMessageService.Close();

btnJoin

The join click event must contact the service to indicate that it has a user about to join the conversation:

C#
_GPH_QuickMessageService.JoinTheConversation(this.txtName.Text); 

It will also change some button states:

C#
this.btnJoin.Enabled = false;
this.btnSend.Enabled = true;
this.btnLeave.Enabled = true;
this.txtMessageOutbound.Enabled = true;

btnLeave

Let the service know that this user is leaving:

C#
_GPH_QuickMessageService.LeaveTheConversation(this.txtName.Text);

Update the button/field states:

C#
this.btnJoin.Enabled = true;
this.btnSend.Enabled = false;
this.btnLeave.Enabled = false;
this.txtMessageOutbound.Enabled = false;

btnSend

Very little to see here, just the message being forwarded to the service:

C#
_GPH_QuickMessageService.ReceiveMessage(this.txtName.Text, null, this.txtMessageOutbound.Text);

btnExit

btnExit also fires a message at the service indicating that the user is leaving the conversation, but in addition it triggers the event to close the form

C#
_GPH_QuickMessageService.LeaveTheConversation(this.txtName.Text);
this.Close();

txtName_TextChanged

This event will only change the state of the Join button. It has no WCF aspect whatsoever.

C#
if (this.txtName.Text != String.Empty)
{
    this.btnJoin.Enabled = true;
}

WriteMessage

WriteMessage is used to format the messages that will be displayed in txtMessageLog. Its a personal choice with no critical WCF role.

C#
private void WriteMessage(string message)
{
    string format = this.txtMessageLog.Text.Length > 0 ? "{0}\r\n{1} {2}" : "{0}{1} {2}";
    this.txtMessageLog.Text = String.Format(format, this.txtMessageLog.Text, 
            DateTime.Now.ToShortTimeString(), message);
    this.txtMessageLog.SelectionStart = this.txtMessageLog.Text.Length - 1;
    this.txtMessageLog.ScrollToCaret();
}

Inbound traffic on the client

So far all the client has done is pass messages to the service in a very standard way. Now its time for the Notify methods that we first met way back when we defined the contract in the service library. These will take messages from the service and display them in the txtMessageLog box.

[BARNES] placed the callback methods in their own region within the form class. I have followed that practice, but based on my tests it appears to be no more than a question of style.

C#
#region GPH_QuickMessageServiceCallback Methods

NotifyUserJoinedTheConversation

NotifyUserJoinedTheConversation takes arg_Name as a parameter. It receives the name of each new client joining the conversation and relays it to the current client. I defer to [BARNES] for a techincal explanation of what is going on in this method:

The UI thread won't be handling the callback, but it is the only one allowed to update the controls. So, we will dispatch the UI update back to the UI sync context.

C#
SendOrPostCallback callback =
                delegate(object state)
    {
        string msg_user = state.ToString();
        msg_user = msg_user.ToUpper();
        this.WriteMessage(String.Format("[{0}] has joined the conversation.", msg_user)); 
    };

_uiSyncContext.Post(callback, arg_Name);

NotifyUserOfMessage

This function takes the author and content of messages from the service.

C#
SendOrPostCallback callback =
    delegate(object state)
    { 
        this.WriteMessage(String.Format("[{0}]: {1}", arg_Name.ToUpper(), arg_Message));
    };

_uiSyncContext.Post(callback,  arg_Name);

NotifyUserLeftTheConversation

The final method in the client example, its role is to inform the current user when it receives word from the service that other users have left the conversation.

C#
SendOrPostCallback callback =
    delegate(object state)
    {
        string msg_user = state.ToString();
        msg_user = msg_user.ToUpper();
        this.WriteMessage(String.Format("[{0}] has left the conversation.", msg_user));
    };

_uiSyncContext.Post(callback, arg_Name);

Compile Errors

After you compile everything up, there will be three or four compile errors on the auto-generated proxy in Reference.cs. I don't have an explanation for why this is occurring but there are four instances over three lines where the client namespace title GPH_QuickMessageServiceClient has to be removed from before the reference ServiceReference1

The earliest instance reported is on line 15 at column 208

C:\SBSB\Training - C#\GPH_QuickMessageServiceClient\GPH_QuickMessageServiceClient\
   Service References\ServiceReference1\Reference.cs(15,208): error CS0426: 
   The type name 'ServiceReference1' does not exist in the type 
   'GPH_QuickMessageServiceClient.ServiceReference1.GPH_QuickMessageServiceClient'
C#
[System.ServiceModel.ServiceContractAttribute(
   Namespace="GPH_QuickMessageServiceLib", 
   ConfigurationName="ServiceReference1.GPH_QuickMessageService", 
   CallbackContract=typeof(
     GPH_QuickMessageServiceClient.ServiceReference1.GPH_QuickMessageServiceCallback), 
   SessionMode=System.ServiceModel.SessionMode.Required)]

becomes

C#
[System.ServiceModel.ServiceContractAttribute(
  Namespace="GPH_QuickMessageServiceLib", 
  ConfigurationName="ServiceReference1.GPH_QuickMessageService", 
  CallbackContract=typeof(ServiceReference1.GPH_QuickMessageServiceCallback), 
  SessionMode=System.ServiceModel.SessionMode.Required)]

On line 45 at column 85

C:\SBSB\Training - C#\GPH_QuickMessageServiceClient\GPH_QuickMessageServiceClient\
  Service References\ServiceReference1\Reference.cs(43,85): error CS0426: 
  The type name 'ServiceReference1' does not exist in the type 
  'GPH_QuickMessageServiceClient.ServiceReference1.GPH_QuickMessageServiceClient' 
C#
public interface GPH_QuickMessageServiceChannel : 
  GPH_QuickMessageServiceClient.ServiceReference1.GPH_QuickMessageService, 
  System.ServiceModel.IClientChannel {

becomes

C#
public interface GPH_QuickMessageServiceChannel : 
  ServiceReference1.GPH_QuickMessageService, System.ServiceModel.IClientChannel {

On line 48 there are two, at column 125 and at column 199

C:\SBSB\Training - C#\GPH_QuickMessageServiceClient\GPH_QuickMessageServiceClient\
  Service References\ServiceReference1\Reference.cs(48,125): error CS0426: The type name 
  'ServiceReference1' does not exist in the type 
  'GPH_QuickMessageServiceClient.ServiceReference1.GPH_QuickMessageServiceClient'

C:\SBSB\Training - C#\GPH_QuickMessageServiceClient\GPH_QuickMessageServiceClient\
  Service References\ServiceReference1\Reference.cs(48,199): error CS0426: 
  The type name 'ServiceReference1' does not exist in the type 
  'GPH_QuickMessageServiceClient.ServiceReference1.GPH_QuickMessageServiceClient' 

Change

C#
public partial class GPH_QuickMessageServiceClient : 
  System.ServiceModel.DuplexClientBase<
    GPH_QuickMessageServiceClient.ServiceReference1.GPH_QuickMessageService>, 
  GPH_QuickMessageServiceClient.ServiceReference1.GPH_QuickMessageService {

to become

C#
public partial class GPH_QuickMessageServiceClient : 
  System.ServiceModel.DuplexClientBase<servicereference1.gph_quickmessageservice>, 
  ServiceReference1.GPH_QuickMessageService {</servicereference1.gph_quickmessageservice>

Other Potential Errors

So far you have seen me make a deliberate error only to correct it with the binding, and clear a compile error that as I write this, I have yet to eliminate without editing the auto-generated code. Here are a few others that I met along the way:

The first attempt to run the host produced this beauty in the console window:

***** Console Based WCF Host for GPH_QuickMessageService *****
Unhandled Exception: System.InvalidOperationException: Operations marked 
      with IsOneWay=true must not declare output parameters, 
      by-reference parameters or return values.
at System.ServiceModel.Description.TypeLoader.CreateOperationDescription(
      ContractDescription contractDescription, MethodInfo methodInfo, 
      MessageDirection direction, ContractReflectionInfo reflectionInfo, 
      ContractDescription declaringContract)
at System.ServiceModel.Description.TypeLoader.CreateOperationDescriptions(
      ContractDescription contractDescription, ContractReflectionInfo reflectionInfo, 
      Type contractToGetMethodsFrom, ContractDescription declaringContract, 
      MessageDirection direction)
at System.ServiceModel.Description.TypeLoader.CreateContractDescription(
      ServiceContractAttribute contractAttr, Type contractType, Type serviceType, 
      ContractReflectionInfo& reflectionInfo, Object serviceImplementation)
at System.ServiceModel.Description.TypeLoader.LoadContractDescriptionHelper(
      Type contractType, Type serviceType, Object serviceImplementation)
at System.ServiceModel.Description.ContractDescription.GetContract(
      Type contractType, Type serviceType)
at System.ServiceModel.ServiceHost.CreateDescription(IDictionary`2& implementedContracts)
at System.ServiceModel.ServiceHostBase.InitializeDescription(
      UriSchemeKeyedCollection baseAddresses)
at System.ServiceModel.ServiceHost.InitializeDescription(Type serviceType, 
      UriSchemeKeyedCollection baseAddresses)
at System.ServiceModel.ServiceHost..ctor(Type serviceType, Uri[] baseAddresses)
at GPH_QuickMessageServiceHost.Program.Main(String[] args) in C:\SBSB\Training - 
         C#\GPH_QuickMessageServiceHost\GPH_QuickMessageServiceHost\Program.cs:line 16 
Press any key to continue . . .

A run on the debugger highlights a problem with the OneWay attribute (its shown above too but not as clearly):

The One-Way error

This was caused by my use of the IsOneWay on all three service methods - I removed it from Join and Leave to resolve the error. It was a clash with the return codes.

Remember the advice back at the host about running it with admin privileges. This is what you will see if you forget to do that:

The Admin error

This occurred during my first attempt (but not subsequently) because the auto-genterated app.config had /service at the end of the address. On removing it, communication was established:

The Service error

If something such as above occurs and results in a timeout, here is how it is reported:

The Timeout error

Launch the debugging aid, WcfTestClient, from the visual studio command prompt. Include the URL to the service in the command:

wcftestclient http://localhost:8080/GPH_QuickMessageService

The WCFTestClient

The operations are inaccessible. The [BARNES] service is similarly restricted. As I write, I have not learned why.

You will also find that you cannot run the service library on the debugger without having the calling host project in the same solution. I like a 1:1 relationship between projects and solutions for clarity, but I am going to have to set that aside if I need to run a service library on the debugger.

An Enhancement - Targeted Messaging

The code discussed in this section was deliberately kept out of the attached example in the interests of simplicity. However, if you fully understand what was discussed above then adding the next few lines should prove doable and interesting. Here I will show you a means of deploying a directory of subscribers which can be used to decide who gets messages. Here's how the interface would look:

The client With Directory Listing

There is now a check list labeled Subscribers to the right of the message box, and as soon as our third subscriber joins, that list is populated with the current subscriber list. In the example above each subscriber is only aware of subscribers joining after them. This is the code used:

GPH_QuickMessageServiceLib

These are the changes to the service library. The callback contract was changed so that NotifyUserJoinedTheConversation and NotifyUserLeftTheConversation now include a subscriber list.

C#
public interface IMessageServiceCallback
{
    [OperationContract(IsOneWay = true)]
    void NotifyUserJoinedTheConversation(string userName, List<string> SubscriberList);

    [OperationContract(IsOneWay = true)]
    void NotifyUserOfMessage(string userName, String userMessage);

    [OperationContract(IsOneWay = true)]
    void NotifyUserLeftTheConversation(string userName,List<string> SubscriberList);
}

The service class gets two new attributes - a simple list, SubscriberList, that will hold the subscriber names to be transmitted back to all subscribers as others come and go, and a dictionary, NotifyList, this time holding the list of subscribers and their associated callback ID's. I could have explored passing back just the subscriber id from the NotifyList to eliminate the need for the SubscriberList, but I leave that for another day.

C#
private static List<string> SubscriberList = new List<string>();
private static Dictionary<string,IMessageServiceCallback> NotifyList =
new Dictionary<string,IMessageServiceCallback>();// Default Constructor

JoinTheConversation needs two new lines in its 'if' statement:

C#
SubscriberList.Add(userName);//Note the callback list is just a list of channels.
NotifyList.Add(userName,registeredUser);//Bind the username to the callback channel ID

Change the callback statement to include SubscriberList in the parameters passed back.

C#
callback.NotifyUserJoinedTheConversation(userName, SubscriberList);

LeaveTheConversation needs two corresponding new lines in its 'if' statement:

C#
NotifyList.Remove(userName);
SubscriberList.Remove(userName);

Change the callback statement to include SubscriberList in the parameters passed back.

C#
{ callback.NotifyUserLeftTheConversation(userName, SubscriberList); });

ReceiveMessage is completely reworked, dispensing with the anonymous delegate technique. The address list from the client is used to look up the NotifyList, and the callbacks stored there are used to target the message. This is the new body for the method:

C#
foreach (string tmpAddr in addressList)
{
    IMessageServiceCallback tmpCallback = NotifyList[tmpAddr];
    tmpCallback.NotifyUserOfMessage(userName, userMessage);
}

GPH_QuickMessageServiceHost

GPH_QuickMessageServiceHost does not require any amendment.

GPH_QuickMessageServiceClient

First off, we need to reload the proxy, so start the service. That done return to the client, right click on ServiceReference1 in the solution explorer and choose Update Service Reference. Add a checked list to the form as illustrated above. I called mine clstSubscriber.

MessageForm.cs needs some work. It gets a new method to look after refreshing the checked list:

C#
private void ShowUserList(string[] _SubscriberList)
{
    clstSubscriber.Items.Clear();
    clstSubscriber.Items.Clear();

    foreach (string _subscriber in _SubscriberList)
        clstSubscriber.Items.Add(_subscriber);
}

btnSend_Click is completely reworked to read the checked list and pass any checked subscribers to the service.

C#
string[] addressList = new string[clstSubscriber.CheckedItems.Count];
int i = 0;
for (int j = 0; j < clstSubscriber.CheckedItems.Count; j++ )
{
    addressList[i++] = (string)clstSubscriber.CheckedItems[j];
}
_GPH_QuickMessageService.ReceiveMessage(this.txtName.Text, 
           addressList, this.txtMessageOutbound.Text);

NotifyUserJoinedTheConversation and NotifyUserLeftTheConversation both get a new If statement to populate the subscriber checked list as their first action.

C#
if (SubscriberList.Count() > 0)
    ShowUserList(SubscriberList);

That's it, compile it all up - and don't forget that you will need to remove the compile errors from Reference.cs because you updated the service reference. Your subscribers will now be able to target where their messages go.

Multi Machine Deployment

While it is all well and good sending messages between clients on the same machine, the real value of WCF comes when you have two machines exchanging messages over the internet / intranet. If you wish to use the internet, assign a static IP address to the machine that will run the service - but be very careful because this could compromise your online security.

I chose to use my domestic intranet, and used the maintenance software that came with my router to identify the IP address of two of the machines connected to it. I replaced localhost:8080 in the base address of the host App.Config as follows:

C#
baseAddress ="http://123.456.1.6/GPH_QuickMessageService"

I made the same change to the endpoint address of the client App.Config. I put a copy of the client on another machine then started the service and a client session on the machine with IP ending .6, then ran the other copy of the client on a second machine. I got a message telling me port 80 is in use by another application, Online research suggests IIS as a likely suspect and advises checking the binding.

I added :8001 to the end of the IP address in the client's app.config and redeployed it.

But despite my best efforts I continued to get a message from the client telling me that the target machine was actively refusing a connection. I could see the service from the second machine via a browser, but just could not get my client to hook in.

This is due to security implications on http bindings. I created the same windows account (username/password) on each machine, added the host and client to the firewall exceptions and eventually even dropped the firewall. Still refused.

I read online that net.tcp binding is less strict, so I added net.tcp bindings to both host and client config files as follows:

Host

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>

    <services>
      <service name="GPH_QuickMessageServiceLib.GPH_QuickMessageService"
        behaviorConfiguration = "QuickMessageServiceMEXBehavior">
      <endpoint
          address ="service"
          binding="netTcpBinding"
          contract="GPH_QuickMessageServiceLib.IMessageServiceInbound"/>
          <endpoint
          address ="service"
          binding="wsDualHttpBinding"
          contract="GPH_QuickMessageServiceLib.IMessageServiceInbound"/>
        <!-- Enable the MEX endpoint -->
        <endpoint address="mex"
        binding="mexHttpBinding"
        contract="IMetadataExchange" />

        <!-- Need to add this so MEX knows the address of our service -->
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:8002/GPH_QuickMessageService/" />
            <add baseAddress ="http://localhost:8080/GPH_QuickMessageService"/>
          </baseAddresses>

        </host>
      </service>
    </services>
    <!-- A behavior definition for MEX -->
    <behaviors>
      <serviceBehaviors>

        <behavior name="QuickMessageServiceMEXBehavior" >
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

</configuration>

Client

This was a big departure from the auto-generated config to follow the [BARNES] model whoose example I had deployed over two machines in late summer.

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <system.serviceModel>
    <client>
      <endpoint address="net.tcp://192.168.1.4:8002/GPH_QuickMessageService/service"
         binding="netTcpBinding"
         contract="ServiceReference1.GPH_QuickMessageService"
         name="TcpBinding" >
      </endpoint>
      <endpoint address="http://192.168.1.4:8080/GPH_QuickMessageService/service"
        binding="wsDualHttpBinding" 
        contract="ServiceReference1.GPH_QuickMessageService"
        name="WSDualHttpBinding_GPH_QuickMessageService" >
      </endpoint>

    </client>
  </system.serviceModel>
</configuration>

The advice is that this should be sufficient alone, but as a failsafe, I also re-complied my Host and Service.

Remember to make sure your net services are running:

Net services

That done, I started the service and a client instance on one machine, then a second client instance on another and they were able to correspond using my message form.

Conclusion

Next up, I want to explore a disconnected correspondence using queues so that the service and its clients to not have to be online simultaneously. I will also have to look at threading and how it could improve performance and to explore fault handling both in terms of how errors are relayed / error recovery.

But the main purpose of this exercise was to learn WCF to allow installations of my ticketing system to communicate effectively. It has succeeded nicely in that. I find it difficult to imagine now that when I started making these notes that I had no practical working knowledge of WCF.

History

  • 2012-11-06 - V1.0 - Initial submission.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Ireland Ireland
My first program was written in Basic on a Sinclair Spectrum ZX 16K in the summer of '85. Having studied Computer Systems I attempted to break into the world of C but took a wrong turn and got immersed in COBOL!

I looked a C again in 1994 but didnt follow up on it. In 2001 I introduced myself to Visual C++ 6.0 courtesy of Ivor Hortons book, but found the going difficult. I tipped my toe in the .NET water in '05 but the first example I tried in VC++ 2005 express didnt work and allied with the absence of MFC in the express package, I parked that up.

Along the way my career got shunted into software testing

A personal machine change force me to migrate to VS2008 in 2008. The new edition of Ivor Hortons book for VC++ in VS2008 reintroduced me to .NET and I got curious whereupon I went out and acquired Stephen Fraser's "Pro Visual C++/CLI and
the .NET 3.5 Platform". I was hooked!

After 20 years I think I finally found my destination.

But it would take a further 8 years of exile before I was reappointed to a developer role. In that time I migrated to C# and used selenium wedriver (courtesy of Arun Motoori's Selenium By Arun) as the catalyst to finally grab the opportunity.

Comments and Discussions

 
QuestionTwo weird errors Pin
The Steve Mol12-Mar-17 11:19
The Steve Mol12-Mar-17 11:19 
AnswerRe: Two weird errors Pin
Ger Hayden12-Apr-17 5:38
Ger Hayden12-Apr-17 5:38 
GeneralRe: Two weird errors Pin
The Steve Mol12-Apr-17 6:22
The Steve Mol12-Apr-17 6:22 
GeneralRe: Two weird errors Pin
Avdesh Kataria26-Jul-17 2:30
Avdesh Kataria26-Jul-17 2:30 
GeneralRe: Two weird errors Pin
Member 1327274229-Aug-18 14:43
Member 1327274229-Aug-18 14:43 
BugTimeouts Pin
mr_squall21-Sep-15 2:14
mr_squall21-Sep-15 2:14 
QuestionGreat article - In VS2015 error is tripped - Unhandled Exception: System.InvalidOperationException: The contract name 'IMetadataExchange' could not be found in the list of contracts implemented by the service GPH_QuickMessageService. Add a ServiceMe Pin
Member 119662628-Sep-15 3:38
Member 119662628-Sep-15 3:38 
QuestionNot easy to configure and execute Pin
Juan TF26-Jun-15 3:38
Juan TF26-Jun-15 3:38 
AnswerRe: Not easy to configure and execute Pin
Ger Hayden3-Jul-15 9:41
Ger Hayden3-Jul-15 9:41 
QuestionWPF client Pin
sweet pain21-Apr-15 1:54
sweet pain21-Apr-15 1:54 
Questionwhen i host service in my windows hosting it generates error Pin
Member 1133771331-Jan-15 1:48
Member 1133771331-Jan-15 1:48 
QuestionExcellent Article...!! Pin
UttamPrasad9-Nov-14 20:01
UttamPrasad9-Nov-14 20:01 
QuestionNotification of Client Disconnect Pin
Joel Palmer20-Oct-14 5:19
Joel Palmer20-Oct-14 5:19 
QuestionRe: Notification of Client Disconnect Pin
Uday Manvar4-Nov-15 20:13
professionalUday Manvar4-Nov-15 20:13 
QuestionGreat Article. For threading support, see the article below. Pin
Jean-Pierre Fouche31-Aug-14 23:49
Jean-Pierre Fouche31-Aug-14 23:49 
QuestionGreat Article - What about the internet? Pin
Lawrence Thurman1-Jul-14 3:42
Lawrence Thurman1-Jul-14 3:42 
GeneralMy vote of 5 Pin
Jeroen Coussement27-Jun-14 2:36
Jeroen Coussement27-Jun-14 2:36 
GeneralGreat article. Pin
Kavir Bheeroo1-Jul-13 20:13
Kavir Bheeroo1-Jul-13 20:13 
QuestionGreat Piece of work. Pin
mafaz32115-May-13 0:53
professionalmafaz32115-May-13 0:53 
Hi There, Thumbs Up | :thumbsup:
All instructions well laid out. Just at the right moment I needed. I followed all your instructions line by line and encountered more or less the similar issues. I too couldn't figure out why the Reference.cs in the client application gives those 4 compile errors. Will update if I figure it out.

Any way the WCF Test Client does not support Duplex contract as explained on MSDN here: http://msdn.microsoft.com/en-us/library/bb552364.aspx[^] That is why you are getting those red warnings.

Though this is your first experience with C#, you rocked man Cool | :cool: . Thank you very much.

Kind Regards,
Mafaz
AnswerRe: Great Piece of work. Pin
mafaz32115-May-13 0:56
professionalmafaz32115-May-13 0:56 
GeneralMy vote of 5 Pin
waqasw shah12-Mar-13 3:19
waqasw shah12-Mar-13 3:19 
GeneralMy vote of 5 Pin
ThangDo6-Mar-13 19:08
ThangDo6-Mar-13 19:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.