Course 2 / Lecture 10

How to build a VoIP PBX in C#

How to create call queue with routing strategies

With the help of this example, you can add other functionalities to your already created advanced call queue. The incoming calls will be distributed among the agents who are constantly becoming available on the basis of various strategies. You can find more details on these strategies in the Introduction to Automatic call distribution / call routing article.

What knowledges would you need?

To fully understand this guide, you might need to study the following chapters first:

  • Advanced Call queue: you can learn how to start to develop an advanced call queue.
    Learn more...

Call queue with routing strategies, source code analysis in C#

Now you have more than one agent and more than one incoming call. In case you have more than one agent available, Round Robin, the Most Idle Agent or the Less Utilized Agent strategy will provide the basis for deciding to which agent will the next call be trasferred. (You can find more information on these strategies here.)

Compared to the previous advanced call queue example, in this extension there is a new enum: the CallQueueStrategy class. This new enum contains the three previously mentioned strategies.

namespace MyPBX.CallQueue
{
    enum CallQueueStrategy
    {
        RoundRobin,
        MostIdleAgent,
        LeastUtilizedAgent
    }
}

The parameter list of the constructor of the CallQueueCallHandler class should be expanded with a strategy. This way, you can select which strategy should be applied. You should use this in MyPBX class in the following manner:

var callQueueMembers = new List<string>();
callQueueMembers.Add("855");
var callQueueExtension = new CallQueueExtension("800", callManager, callQueueMembers, CallQueueStrategy.LeastUtilizedAgent);
extensionContainer.TryAddExtension(callQueueExtension);

In this example, there are three agents, and the one to whom the call will be transferred will be determined with the help of the Least Utilized Agent strategy.

In the timer_Elapsed method, the call will be transferred to that agent who is chosen based on the current strategy. This agent will be provided by the GetAgent method and this agent will be the SelectedAgent, i.e. the selected agent at the given moment.

private string GetAgent(List<string> memb)
{
	switch (strategy)
	{
		case CallQueueStrategy.MostIdleAgent:
			return GetMostIdleAgent(memb);
			break;
		case CallQueueStrategy.LeastUtilizedAgent:
			return GetLeastUtilizedAgent(memb);
			break;
		case CallQueueStrategy.RoundRobin:
			return GetRoundRobin(memb);
			break;
	}
	return null;
}

This method calls the following methods on the basis of the chosen strategy: GetMostIdleAgent, GetLeastUtilizedAgent, GetRoundRobin

  • The GetRoundRobin method selects the agent in accordance with the Round Robin strategy.

    The call will be transferred to the agent who is next in line. In order for this system to work properly, and for you to be able to keep track of who was the last in call, the Start method has a parameter, the lastAgent. With the help of this, it is easy to tell which agent was the last to answer a call.

    	private string GetRoundRobin(List<string> memb)
    	        {
    	            string selectedMember = null;
    	            for (int i = 0; i < memb.Count; i++)
    	            {
    	                if (lastAgent == null)
    	                {
    	                    selectedMember = memb[0];
    	                }
    	                else if (memb[i] == lastAgent)
    	                {
    	                    if (memb[i] == memb.LastOrDefault())
    	                    {
    	                        selectedMember = memb[0];
    	                    }
    	                    else
    	                    {
    	                        selectedMember = memb[i + 1];
    	                    }
    	                }
    	            }
    	            return selectedMember;
    	        }
    	
  • The GetMostIdleAgent selects using the Most Idle Agent strategy.

    The call will be transferred to the agent who has been idle for the longest since the last call.

    	 private string GetMostIdleAgent(List<string> memb)
    	        {
    	            double tmp = 0;
    	            double idleTime = 0;
    	            string selectedMember = null;
    	
    	            foreach (var m in memb)
    	            {
    	                List<ISession> historyResult = CallHistory.GetCallHistory(m);
    	                var lastHistoryItem = historyResult.LastOrDefault();
    	
    	                if (lastHistoryItem == null)
    	                {
    	                    return m;
    	                }
    	
    	                idleTime = (DateTime.Now.Hour * 3600 + DateTime.Now.Minute * 60 + DateTime.Now.Second) -
    	                           (lastHistoryItem.StartTime.Hour * 3600 + lastHistoryItem.StartTime.Minute * 60 + lastHistoryItem.StartTime.Second +
    	                            lastHistoryItem.TalkDuration.TotalSeconds);
    	
    	                if (idleTime != 0 && idleTime > tmp)
    	                {
    	                    tmp = idleTime;
    	                    selectedMember = m;
    	                }
    	            }
    	            return selectedMember;
    	        }
    	
  • The GetLeastUtilizedAgent uses the Least Utilized Agent strategy for selection.

    This means that it will calculate who has been the most idle on the given day, i.e. the call will be transferred to the agent for whom the following ratio is the smallest: time spent in call / online spent time.

    	private string GetLeastUtilizedAgent(List<string> memb)
    	        {
    	            double inCallTime = 0;
    	            double idleTime = 0;
    	            double currentUtilized = 0;
    	            string selectedMember = null;
    	
    	            for (int i = 0; i < memb.Count; i++)
    	            {
    	                List<ISession> historyResult = CallHistory.GetCallHistory(memb[i]);
    	                var currentTime = DateTime.Now;
    	                var firstHistoryItem = historyResult.FirstOrDefault(item => item.StartTime.Year == currentTime.Year 
    	                                                                    && item.StartTime.Month == currentTime.Month 
    	                                                                    && item.StartTime.Day == currentTime.Day);
    	
    	                TimeSpan diff = (currentTime - new DateTime(currentTime.Year, currentTime.Month, currentTime.Day));
    	                var spendTime = diff.TotalSeconds;
    	
    	                if (firstHistoryItem == null)
    	                {
    	                    return memb[i];
    	                }
    	
    	                foreach (var session in historyResult)
    	                {
    	                    inCallTime = inCallTime + session.TalkDuration.TotalSeconds;
    	                }
    	
    	                idleTime = spendTime - inCallTime;
    	                double utilized = inCallTime / idleTime;
    	
    	                if (i == 0)
    	                {
    	                    currentUtilized = utilized;
    	                    selectedMember = memb[0];
    	                }
    	                else if (utilized < currentUtilized)
    	                {
    	                    currentUtilized = utilized;
    	                    selectedMember = memb[i];
    	                }
    	            }
    	            return selectedMember;
    	        }
    	

You can select a suitable Ozeki VoIP SIP SDK license for PBX development on Pricing and licensing information page

Related Pages

More information