Creating a WCF ACD (Automatic Call Distributor) Server Part I

Download Code Sample

In March of last year I started a CodePlex project which didn't get very far and I kind of left it. At that time Speech Server (2007) was still in Beta and I wanted to build an ACD for it, which I somewhat accomplished. If you aren't sure what an ACD is here is the Wiki link: https://en.wikipedia.org/wiki/Automatic_call_distributor

The idea is simple, when a call comes in on Speech Server (2007) and the caller asks to be transferred to an agent, we need some logic to decide which agent to transfer it to. Typically an ACD uses a common protocol called CSTA, this application doesn't. Since I'm only supporting Speech Server and no other IVR Platform I don't have a real need to use CSTA.

Creating a WCF application is the most logical solution as it supports duplex messaging, which I will need, and you can do alot with very little code. So a caller asks to be transferred to an agent, using WCF in a Speech Server activity we pass a message to the WCF Service asking for an available agent. The WCF Service performs logic to find an available agent. In Part II I'll show you how to make this a presence based routing using the UC AJAX API. (You aren't limited to just presence based routing, you could also easily add skills based routing by adding lookups to a database of agents and what skills you the caller needs, for now this is out of scope.)

First we need to define the duplex contract by setting up a new "Class Library Project" and reference the System.ServiceModel assembly.
Next we need to define two Interfaces with one method each. One is used by the server, FindAvailableAgent. The other is used by the Client, TransferTo.

using

System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;

namespace

ACDFunctions.Library
{

[ServiceContract(SessionMode = SessionMode.Required)]
public interface IACDServerDuplexCallback
{

[OperationContract(IsOneWay = true)]
void TransferTo(string agentAddress);

}

[ServiceContract(CallbackContract = typeof(IACDServerDuplexCallback))]
public interface IACDServerDuplex
{

[OperationContract(IsOneWay = true)]
void FindAvailableAgent(string sessionId, string callingFrom);

}

}

 

The Speech Server application will call the FindAvailableAgent method, when the service finds an available agent the service will call the TransferTo method on the Speech Server application.
Now let's implement these interfaces. First we need to create a ClientProxy, we could use the SvcUtil to generate the proxy, but I'm not publishing any metadata and the service isn't running, it's easier to just build it.

using

System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace

ACDFunctions.Library
{

public class ACDClientProxy : DuplexClientBase<IACDServerDuplex>, IACDServerDuplex
{

public ACDClientProxy(InstanceContext callbackInstance, Binding binding, EndpointAddress remoteAddress): base(callbackInstance, binding, remoteAddress)
{

}

public void FindAvailableAgent(string sessionId, string callingFrom)
{

base.CreateChannel().FindAvailableAgent(sessionId, callingFrom);

}

}

}

Next we need to build the actual implementation of the FindAvailableAgent method, which processes the logic to find agents. In Part II I'll show you how to add presence look up using the UC AJAX API, but for now, we will do some simple logic based on the day of the week. Notice the [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] attribute, if you don't know already, I'll get into that in Part II.

using

System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.ServiceModel;

namespace

ACDFunctions.Library
{

[

ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]

public class ACDService : IACDServerDuplex
{

private OperationContext _context;

public void FindAvailableAgent(string sessionId, string callingFrom)
{

//TODO: Lookup to see if customer exist in our CRM DB based on the number the user is calling from.
// this information then could be passed to the agent via a CTI using the UC Client API!

//TODO: Adding Rules for finding an agent. This could be presence based and/or skills based.
//For now we have a simple date based rule.

string sipAddress = string.Empty;
_context = OperationContext.Current;

switch (DateTime.Now.DayOfWeek)
{

case DayOfWeek.Saturday:
sipAddress = "sip:mark@contoso.com";
break;

case DayOfWeek.Sunday:
sipAddress = "sip:john@contoso.com";
break;

default:
sipAddress = "sip:kelly@contoso.com";
break;

}

//Simulate Wait Time
Thread.Sleep(5000);

//Notify the application who to transfer the call to.
_context.GetCallbackChannel<IACDServerDuplexCallback>().TransferTo(sipAddress);

}

}

}

Next we need to build the actual server component. This should be a Windows Service, but for a simple demo a Console application will do.

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using ACDFunctions.Library;

namespace

ACDServiceConsole
{

class Program
{

static void Main(string[] args)
{

Console.WriteLine("Setting up ACD Service ... ");

NetNamedPipeBinding binding = new NetNamedPipeBinding();
binding.Security.Mode = NetNamedPipeSecurityMode.Transport;

Uri address = new Uri("net.pipe://localhost/ACDService");
ServiceHost serviceHost = new ServiceHost(typeof(ACDService), new Uri[] { address });

serviceHost.AddServiceEndpoint(typeof(IACDServerDuplex), binding, address);

Console.WriteLine("Opening ACD Service ...");

serviceHost.Open();

Console.WriteLine("ACD Service Started!");
Console.WriteLine("Press any key to exit ...");
Console.ReadLine();

serviceHost.Close();

}

}

}

For the last peice of code we need to create the client peice, in this case it will be a Voice Response Activity Library. This is where we build the interface to Speech Server (2007). In our Voice Response Activity Library we need to added the following:

  • Statement Activity
  • Code Activity
  • While Activity
    • Statement Activity
  • Blind Transfer Activity

using

System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using Microsoft.SpeechServer;
using Microsoft.SpeechServer.Dialog;
using ACDFunctions.Library;
using System.ServiceModel;

namespace

FindAgentActivity
{

public partial class FindAgentActivity: SpeechSequenceActivity, IACDServerDuplexCallback
{

private bool _onHold = true;
private ACDClientProxy _client;

[NonSerialized]
private ITelephonySession _telephonySession;

public FindAgentActivity()
{

InitializeComponent();

}

private void FindAvailableAgent_ExecuteCode(object sender, EventArgs e)
{

_telephonySession =

base.Workflow.TelephonySession;

NetNamedPipeBinding binding = new NetNamedPipeBinding();
binding.Security.Mode = NetNamedPipeSecurityMode.Transport;

string uri = "net.pipe://localhost/ACDService";

EndpointAddress remoteAddress = new EndpointAddress(uri);

this._client = new ACDClientProxy(new InstanceContext(this), binding, remoteAddress);
this._client.FindAvailableAgent(this._telephonySession.Id, this._telephonySession.CallInfo.CallingParty.Uri.ToString());

}

private void IsOnHold(object sender, ConditionalEventArgs args)
{

args.Result =

this._onHold;

}

public void TransferTo(string agentAddress)
{

SipUriTelephonyAddress agentURI = new SipUriTelephonyAddress(new SipUri(agentAddress));

this._telephonySession.Synthesizer.SpeakAsyncCancel();
this.blindTransferToAgent.CalledParty = agentURI;
this._onHold = false;

_client.Close();

}

private void playMusicInfo_TurnStarting(object sender, TurnStartingEventArgs e)
{

playMusicInfo.MainPrompt.SetText(

"You can put repeating music or information here.");

}

}

}

And that's it! So Run the Console Server first, then run you can debug your Speech Server application which uses the FindAgent activity we created.

THIS CODE IS PROVIDED ON AN “AS-IS” BASIS WITH NO WARRANTIES, NO SUPPORT AND CONFERS NO RIGHTS

Speech Server ACD Server.zip