Testing & Debugging applications built with 3CX Call Flow Designer
On this topic
The 3CX Call Flow Designer (formerly VAD) is a powerful tool for creating voice applications quickly and easily, without needing to have great programming or telephony skills. These applications are deployed to the server where 3CX Phone System is installed, and are implemented as voice apps inside Call Queues. Through the management interface it is possible to define the circumstances under which each of these applications is activated, eg for a specific PSTN line.
While creating these applications using the CFD is significantly easier than doing it programmatically, you can always make mistakes, and in these cases it is necessary to make a correct diagnosis of the problem in order to resolve it.
First step: fix build project errors
When your CFD project is ready, you need to build it. The build procedure consists in the following steps:
- The CFD creates a set of C# files that conform a .NET Core project.
- Then it compiles the project to a .NET Core DLL.
- Finally the CFD makes a ZIP including the DLL and the required WAV audio files, and encrypts it.
You might find errors in any of the steps above. Let’s see examples of each case.
An error occurs when the CFD tries to create the .NET Core project
This might happen for example if some component is not properly configured, a component property has not been set, etc. For example, if we use a Transfer component but we don’t set the Destination property, we’ll get an error.
When we build this project we’ll see the following error in the Error List window.
Double click on the error, and the designer will be shown with the component with problems selected.
An error occurs compiling the project to a .NET Core DLL
When all the components are configured, the CFD will be able to generate a C# .NET Core project, and then it will try to build this project to a DLL. The compilation procedure might fail because of different reasons, like errors in the expressions used when configuring components, or missing references.
Fixing errors in the expressions used when configuring components
One of the most common errors is using an invalid expression when configuring a component. For example, if you have a Transfer component and set the Destination to an invalid expression like this:
...you will get an error during build. The error will look like this:
As you can see, in this case the errors shown are the errors reported by the C# compiler. In the case that the compiler error description is not very clear to you, this is what you can do:
- Find the project sources created by the CFD. When the build fails, the sources created are not removed, so you can take a look in case of need. They’re located in the folder “Output\Release\ProjectName\Sources” in your project folder. For example, in the case of this sample the project name is “Test01”, so the sources are here:
- In that folder you will find many files with extension “cs”, and a file named “project.csproj”. If you have Visual Studio installed, you can open this project, try to build it, and see the errors you get. You will see the same errors reported by the CFD, but Visual Studio will let you easily see the line in which the error is, so you can understand what you need to fix.
- If you don’t have Visual Studio or don’t want to use it, no problem. The problem is always in one of the following files, which you can open using a text editor:
- “Callflow.cs”: this is the file that defines the behavior of the callflow in your project. If the problem is in the callflow, then you need to check this file.
- “Dialer.cs”: this is the file that defines the behavior of the dialer in your project. If the problem is in the outbound dialer, then you need to check this file.
- “YourComponentName.cs”: this is the file that defines the behavior of your custom component. When you add a custom component with name “MyCustomComponent.comp” you will see a file with name “MyCustomComponent.cs” which contains this component behavior.
- In our case, the error was reported in line 133 (column 70). If we open the file “Callflow.cs” using a text editor and go to line 133, we’ll see the following.
- As you can see, the problem is that the Destination property is being set to InvalidExpression, which is not a valid string or variable name.
Fixing errors caused by missing references
When you use a Launch External Script component, you will provide your C# code to execute. This C# code might need external DLL references, not included by default in the 3CX CFD. In this case, when you build your CFD application you will see an error like “Error compiling source code to .NET library: (9,7): error CS0246: The type or namespace name 'XmlDocument' could not be found (are you missing a using directive or an assembly reference?)”:
In order to fix this, we need to:
- Add the missing reference to the CompilerDependencies folder, so compilation succeeds.
- Add the missing reference and its dependencies to the Queue Manager service folder, so it can be loaded in runtime.
Let’s cover these tasks in more detail. We’ll show the step by step procedure for the example missing reference “System.Xml.XmlDocument.dll”.
Adding the missing reference to the CompilerDependencies folder
When the CFD compiles the generated C# .NET Core project, it uses all the DLLs in the CompilerDependencies folder from your installation path, usually “C:\Program Files\3CX Call Flow Designer\CompilerDependencies”, as references. If you need additional references, just copy the DLLs to this folder and the CFD will consider them in the next build.
As you’re writing the C# code, you must know which DLLs you need to include. If you’re using Visual Studio to test your code before including it in the CFD project, when you add the reference, it will be automatically downloaded by Visual Studio from nuget, and placed in your local drive in folder “C:\Users\UserName\.nuget\packages”. You can also manually download the package from https://www.nuget.org, and unzip it to get the DLL from inside.
When you have the nuget package in your local drive, you need to get the right version of the DLL. The application generated by the CFD will run as a .NET Core application, so you can’t use the DLL for the .NET Framework for example. In the case of the “System.Xml.XmlDocument.dll” we need to take it from here:
Just copying that DLL to the CompilerDependencies folder will fix the compilation issue. But when you upload this app to 3CX, the DLL will not be found in the server, and we’ll experience a runtime error. So let’s tackle that issue as well.
Adding the missing reference and its dependencies to the Queue Manager service folder
The app generated with the 3CX CFD will be executed by the 3CX Queue Manager service. If your code is trying to use a missing DLL, the log file “3CXQueueManager.log” will give you that information:
17/05/24 13:00:45.186|100030| Err|10|0032|: PlugIn[TestApp - UserComponent 'validateDataXml' - MainFlow - CallID VICXIAXSCWGX] ERROR: Error executing last component: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.IO.FileNotFoundException: “Could not load file or assembly 'System.Xml.XmlDocument, Version=220.127.116.11, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies.” The system cannot find the file specified. ---> System.IO.FileNotFoundException: Could not load file or assembly 'file:///C:\Program Files\3CX Phone System\Instance1\Bin\System.Xml.XmlDocument.dll' or one of its dependencies. The system cannot find the file specified.
In order to fix this, we need to copy the DLL, and all its dependencies, to the 3CX Queue Manager service folder, that is “C:\Program Files\3CX Phone System\Instance1\Bin” in Windows and “/usr/lib/3cxpbx” in Linux. In this case, just copying the file “System.Xml.XmlDocument.dll” is not enough, the same error will continue happening, because there are more dependant DLLs to add. In order to find out which are the dependencies, we need to go to the package specification, for example in our case to this page. As you can see, under the title “.NETStandard 1.3” we have the following dependencies:
- System.Collections (>= 4.0.11)
- System.Diagnostics.Debug (>= 4.0.11)
- System.Globalization (>= 4.0.11)
- System.IO (>= 4.1.0)
- System.Resources.ResourceManager (>= 4.0.1)
- System.Runtime (>= 4.1.0)
- System.Runtime.Extensions (>= 4.1.0)
- System.Text.Encoding (>= 4.0.11)
- System.Threading (>= 4.0.11)
- System.Xml.ReaderWriter (>= 4.0.11)
If you were using Visual Studio to test your C# code before including it in the CFD project, then all these packages should be already downloaded to your local drive. For each dependency, you need to get the nuget package, copy the DLL from the “lib” or “ref” folder to the 3CX Queue Manager service folder as you did in the previous step, and then check for additional dependencies for each dependency added. Please note that some dependency might be already present in the 3CX Queue Manager service folder, in that case keep the DLL provided by 3CX with the installation.
In our example, after iterating over all the dependencies and nested dependencies, we’ll need to copy the following DLLs to the 3CX Queue Manager service folder (file paths are relative to C:\Users\UserName\.nuget\packages):
After copying the DLLs mentioned to the 3CX Queue Manager service folder, the issue is solved and the application starts running as expected.
Second step: analyze problems in runtime
Once you have built your project, you will get an output file with extension “tcxvoiceapp”. This file contains the DLL and the audio WAV files of your project. Using the 3CX Management Console you upload this file to a Call Queue, the Queue Manager service is restarted, and the voice app is ready to be used.
So, what happens now if the app is not doing what we want? We need to check the logs to understand what the app is doing. Voice apps write logs to the Queue Manager log file. The file name is “3CXQueueManager.log” and it’s located at “C:\ProgramData\3CX\Instance1\Data\Logs” in Windows, or “/var/lib/3cxpbx/Instance1/Data/Logs” in Linux.
We’ll work with a very simple sample application, which plays a file “Hello.wav”, then tries to execute some C# code (which just throws an exception), and finally transfers the call to the operator (this will not happen because of the error in the previous component). The designer will look like this:
We deploy this app to 3CX, to the Call Queue with extension 801. Then we call to the extension 801, and we’ll see the following in the logs:
- 17/05/08 14:32:09.742|100023| Inf|30|0006|: “PlugIn[Test01]” INFO: Starting...
- 17/05/08 14:32:09.742|100046| Trc|75|0006|: DBG: VAD C:\ProgramData\3CX\Instance1\Data\Voiceapps\Test01\Test01.qmext.dll was loaded successfully
- 17/05/08 14:32:09.744|100049| Trc|75|0014|: PlugIn[Test01] Trace: Start looking for queues assigned to this app...
- 17/05/08 14:32:09.745|100049| Trc|75|0014|: PlugIn[Test01] Trace: This app is assigned to queue number '801', getting queue...
- 17/05/08 14:32:09.751|100049| Trc|75|0014|: PlugIn[Test01 - Main] Trace: Caching the following audio files: C:\ProgramData\3CX\Instance1\Data\Ivr\Prompts\..\..\Voiceapps\Test01\Audio\beep.wav, C:\ProgramData\3CX\Instance1\Data\Ivr\Prompts\..\..\Voiceapps\Test01\Audio\Hello.wav
- 17/05/08 14:32:09.753|100049| Trc|75|0014|: PlugIn[Test01] Trace: Main instance created for queue number '801', start processing calls...
- 17/05/08 14:32:17.014|100007| Inf|30|0012|: Call(NPICEAMLUKCX) has been started
- 17/05/08 14:32:17.076|100005| Inf|10|0006|: New incoming call to Q:801 from 100; qcid=NPICEAMLUKCX
- 17/05/08 14:32:17.082|100049| Trc|75|0006|: PlugIn[Test01 - Main - CallID NPICEAMLUKCX] Trace: OnNewQueueCall - taking call out of the queue and creating CallHandler for this call...
- 17/05/08 14:32:17.087|100046| Trc|75|0006|: DBG: Call(NPICEAMLUKCX) has been taken out of the queue Q:801
- 17/05/08 14:32:17.092|100046| Trc|75|0006|: DBG: QCall NPICEAMLUKCX state changed: Init -> PluginHandlesIt
- 17/05/08 14:32:17.093|100049| Trc|75|0006|: PlugIn[Test01 - CallHandler - CallID NPICEAMLUKCX] Trace: OnStateChanged: obj='PluginHandlesIt'
- 17/05/08 14:32:17.131|100033| Trc|75|0011|: Call update(Ins): cid=2, dn=801, internal=<empty>, external=100, ac_stat=Ringing, leg=203; att=''
- 17/05/08 14:32:17.131|100046| Trc|75|0011|: DBG: : [202,203]
- 17/05/08 14:32:17.394|100033| Trc|75|0011|: Call update(Upd): cid=2, dn=Ext.100 John Doe, internal=<empty>, external=801, ac_stat=Connected, leg=202; att='8aa5115d854643c4a4a56157ad32c9ec'
- 17/05/08 14:32:17.400|100046| Trc|75|0011|: DBG: : [202,203]
- 17/05/08 14:32:17.404|100033| Trc|75|0011|: Call update(Upd): cid=2, dn=801, internal=<empty>, external=100, ac_stat=Connected, leg=203; att=''
- 17/05/08 14:32:17.404|100046| Trc|75|0011|: DBG: : [202,203]
- 17/05/08 14:32:17.565|100049| Trc|75|0006|: PlugIn[Test01 - CallHandler - CallID NPICEAMLUKCX] Trace: OnCallEstablished - Taking call out of the queue...
- 17/05/08 14:32:17.672|100033| Trc|75|0011|: Call update(Upd): cid=2, dn=801, internal=<empty>, external=100, ac_stat=Connected, leg=203; att='NPICEAMLUKCX'
- 17/05/08 14:32:17.677|100046| Trc|75|0011|: DBG: NPICEAMLUKCX: [202,203]
- 17/05/08 14:32:17.988|100046| Trc|75|0006|: DBG: CMNotify(NPICEAMLUKCX): NewQCall, legId 203
- 17/05/08 14:32:17.988|100046| Trc|75|0006|: DBG: CMNotify(NPICEAMLUKCX): LegStateChanged, legId 203
- 17/05/08 14:32:18.028|100049| Trc|75|0020|: PlugIn[Test01 - Callflow - MainFlow - CallID NPICEAMLUKCX] Trace: “Start executing component 'playHello'”
- 17/05/08 14:32:18.030|100049| Trc|75|0020|: PlugIn[Test01 - Callflow - MainFlow - CallID NPICEAMLUKCX] Trace: “Start executing component 'executeSomeCode'”
- 17/05/08 14:32:18.541|100049| Trc|75|0020|: PlugIn[Test01 - ExternalCodeExecutionComponent - CallID NPICEAMLUKCX] Trace: Start executing component with objectType='SomeCodeNamespace.MyClass' - methodName='DoSomething'
- 17/05/08 14:32:18.582|100030| Err|10|0020|: PlugIn[Test01 - Callflow - MainFlow - CallID NPICEAMLUKCX] ERROR: “Error executing last component”: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Exception: “This is an exception thrown from my code”
- at SomeCodeNamespace.MyClass.DoSomething()
- --- End of inner exception stack trace ---
- at System.RuntimeMethodHandle.InvokeMethod(Object target, Object arguments, Signature sig, Boolean constructor)
- at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object parameters, Object arguments)
- at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object parameters, CultureInfo culture)
- at System.Reflection.MethodBase.Invoke(Object obj, Object parameters)
- at Test01.ExternalCodeExecutionComponent.executeStart(QMExtendAPI iface, String id)
- at Test01.ExternalCodeExecutionComponent.Start(QMExtendAPI iface, CallQueue callQueue, QueueCall queueCall, ActiveConnection activeConnection, TimerManager timerManager, Dictionary`2 variableMap, TempWavFileManager tempWavFileManager, PromptQueue promptQueue)
- at Test01.Callflow.ProcessStart()
- 17/05/08 14:32:18.583|100049| Trc|75|0020|: PlugIn[Test01 - Callflow - ErrorFlow - CallID NPICEAMLUKCX] Trace: “Start executing component 'errorHandlerAutoAddedFinalExitCallflow'”
- 17/05/08 14:32:18.585|100049| Trc|75|0020|: PlugIn[Test01 - PromptQueue - CallID NPICEAMLUKCX] Trace: “Start playing file: C:\ProgramData\3CX\Instance1\Data\Ivr\Prompts\..\..\Voiceapps\Test01\Audio\Hello.wav”
- 17/05/08 14:32:18.590|100046| Trc|75|0020|: DBG: Using prompts path 'F1BAA317-E130-44ff-B467-63AE7F9EC061'
- 17/05/08 14:32:18.594|100046| Trc|75|0020|: DBG: Playing: [[Hello.wav]] to NPICEAMLUKCX
- 17/05/08 14:32:23.797|100045| Dbg|90|0021|: Audio (id=1) has been played for call QC:NPICEAMLUKCX[100->801]
- 17/05/08 14:32:23.810|100049| Trc|75|0006|: PlugIn[Test01 - CallHandler - CallID NPICEAMLUKCX] Trace: OnPromptPlayed: obj='1'
- 17/05/08 14:32:23.823|100049| Trc|75|0020|: PlugIn[Test01 - Callflow - ErrorFlow - CallID NPICEAMLUKCX] Trace: OnPromptPlayed for component 'errorHandlerAutoAddedFinalExitCallflow'
- 17/05/08 14:32:23.824|100049| Trc|75|0020|: PlugIn[Test01 - Callflow - ErrorFlow - CallID NPICEAMLUKCX] Trace: Callflow finished, disconnecting call...
- 17/05/08 14:32:23.866|100008| Inf|30|0006|: Call(NPICEAMLUKCX) has been terminated (reason: Unknown)
- 17/05/08 14:32:23.868|100049| Trc|75|0006|: PlugIn[Test01 - CallHandler - CallID NPICEAMLUKCX] Trace: OnInboundCallTerminated: obj='487'
We need to note the following:
- Every log line from a CFD app starts with “PlugIn[ProjectName”, in our case “PlugIn[Test01”, as highlighted in bold in line 1.
- In line 24, we see that the component “playHello” is being executed.
- In line 25, we see that the component “executeSomeCode” is being executed.
- In line 27, we see that an error occurs while executing component “executeSomeCode”.
- Then in line 37, we see that the error handler flow is executed, but as it is empty, no user component is executed there.
- In line 38, we see that the file “Hello.wav” is played. When the property AllowDtmfInput is set to true, it is normal that the file is played after the error, because audio files are queued in that case, to be played when the voice app is waiting for a DTMF digit, or when the app is ending.
As we can see, in this sample application the error occurs when we try to execute the component “executeSomeCode”, and therefore we need to check what that component is doing.
- Install the 3CX Call Flow Designer
- Accessing a database from a CFD voice app
- Sending e-mails from a CFD voice app
- How to play a sequence of digits from a 3CX Call Flow Designer voice app
- Creating a Phone Support Portal with the 3CX Call Flow Designer – Part 1, 2, 3, 4
- Routing Calls Based on the Time of Day
- Creating an outbound dialer
- Time-Based Routing without Programming
- Using the Authentication component to validate customers
- Using the Credit Card Component
- Text to Speech with the 3CX Call Flow Designer
- Creating a predictive dialer with the 3CX CFD