Howto:SOAP Api VisualBasic.Net Sample Code

From innovaphone wiki
Jump to navigation Jump to search

Applies To

This information applies to

  • any innovaphone PBX platform
  • Visual Studio 2005 Visual Basic Programming Environment

innovaphone PBX web services can be used from VB.Net. Here is how


More Information

Configuration

For this example, you will need Visual Studio 2005. Other versions however will work too with minor modifications.

The innovaphone PBX wsdl file is required. See Reference:SOAP API for information how to obtain this file.

The sample code requires a working PBX at 172.16.10.5. There must be a PBX user called _TAPI_ which should be active member of a group where all monitored users are member in too. This user must have a password access. There must be a user ckl-2, which is monitored by _TAPI_.

Of course you can change all these properties in the code.

Running Sample Code

To demonstrate SOAP usage with a stand-alone visual basic script, a simple class is presented that will allow you to monitor your PBX and note all events in a trivial form.

To create the solution, follow these steps:

  • Start Visual Studio
  • Click File / New / Project
  • Select Visual Basic
  • Select Windows Application
  • Create the solution
  • Copy the file pbx501.wsdl into your project
  • Right-click on the project and select Add Service Reference
  • Click Advanced
  • Click Add Web Reference
  • Type the path to the wsdl file into the URL field
  • Click on Go and you should see a 1 Service found: pbx501 message
  • As Web reference name use pbx_wsdl
  • Open the code window for Form1
  • Replace the content with the following code within the editor window

' ' this form will merely show a window with cumulating PBX events ' its purpose is to demonstrate the use of async events in VB.Net '

Public Class Form1

   ' the forms PBX link
   Dim pbx As myPBX
   ' PBX access data
   ' assuming the controling user is "_TAPI_" which has a user-password "access"
   Const httpUser As String = "_TAPI_"
   Const httpPw As String = "access"
   Const pbxUser As String = "_TAPI_"
   Const pbxMonitor As String = "ckl-2"
   Const pbxUrl As String = "http://172.16.10.5/PBX0/user.soap"
   ' PBX runtime data
   Public pbxKey As Integer
   Public pbxSession As Integer
   Public pbxUserId As Integer
   ' initalize pbx link on load
   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       Try
           ' create the link
           pbx = New myPBX(Me)
           ' set the URL
           pbx.Url = pbxUrl
           ' set the HTTP level credentials
           pbx.Credentials = New Net.NetworkCredential(httpUser, httpPw, "")
           ' initialize the session, remember the session-id and -key
           pbxSession = pbx.Initialize(pbxUser, "VBNet SOAP Test", True, True, pbxKey)
           ' monitor a user
           pbxUserId = pbx.UserInitialize(pbxSession, pbxMonitor, False)
           ' start async retrieval of events from pbx
           pbx.startPolling()
       Catch err As Exception
           Dim msg1 As String
           Dim msg2 As String
           msg1 = err.Message
           If Not err.InnerException Is Nothing Then
               msg2 = err.InnerException.Message
           Else
               msg2 = ""
           End If
           MsgBox("pbx link initialization failed: " + msg1 + " / " + msg2)
           Exit Sub
       End Try
   End Sub

End Class


' the PBX link ' we need a derived class to be able to handle the async events Public Class myPBX

   ' derive from the auto-generated soap class
   Inherits pbx_wsdl.pbx
   ' link to form
   Dim form As Form1
   ' constructor to save the form link
   Public Sub New(ByRef form As Form1)
       Me.form = form
   End Sub
   ' setup an async Poll
   Public Sub startPolling()
       Try
           Me.PollAsync(form.pbxSession)
       Catch e As Exception
           MsgBox("start poll failed: " + e.Message + " / " + e.InnerException.ToString)
       End Try
   End Sub
   Public Sub addEvent(ByVal ev As String)
       Me.form.events.Items.Add(ev)
   End Sub
   Delegate Sub addEventDelegate(ByVal ev As String)
   ' handle an async Poll result
   Private Sub pollCB(ByVal sender As Object, ByVal e As pbx_wsdl.PollCompletedEventArgs) _
       Handles MyBase.PollCompleted
       Dim p As addEventDelegate = AddressOf addEvent
       ' scan user and call events
       For Each ui As pbx_wsdl.UserInfo In e.Result.user
           ' Me.form.events.Items.Add("user " + ui.cn) -- dangerous
           Me.form.Invoke(p, New Object() {"user " + ui.cn})
       Next
       For Each ci As pbx_wsdl.CallInfo In e.Result.call
           ' Me.form.events.Items.Add("call -> " + ci.msg) -- dangerous
           Me.form.Invoke(p, New Object() {"call -> " + ci.msg})
       Next
       ' schedule next async Poll
       Me.startPolling()
   End Sub

End Class

  • Right click on Form1.vb and select View in Designer
  • Select View from the Visual Studio Menubar and select Toolbox
  • Create a ListView control in the form, resize it so that it fills the whole area
  • Open the controls properties and rename it to events
  • Select Build from Visual Studios menu bar. The solution should build OK
  • Open Form1.vb's code window and check paragraph titled ' PBX access data
  • If need be (most likely, you will have to at least change the setting of Const pbxUrl), change to your needs

To run the code, press F5. You should see a window showing events in your PBX.

How the code works

The pbx_wsdl Class

This class is generated automatically by the Visual Studio WSDL importer in file Reference.vb. It defines stubs for all PBX SOAP functions described in Reference:SOAP API.

Here is one example:

       <System.Web.Services.Protocols.SoapRpcMethodAttribute("http://innovaphone.com/pbx#Poll", RequestNamespace:="http://innovaphone.com/pbx", ResponseNamespace:="http://innovaphone.com/pbx")>  _
       Public Function Poll(ByVal session As Integer) As <System.Xml.Serialization.SoapElementAttribute("return")> AnyInfo
           Dim results() As Object = Me.Invoke("Poll", New Object() {session})
           Return CType(results(0),AnyInfo)
       End Function


This stub function lets you conveniently call the SOAP methods as if they were local class methods. Also, it deals with the SOAP way of passing parameters and returning results. It is good for synchronous invocation of SOAP (that is, pbx-) methods.

However, this class also creates helper code for asynchronous invocation:

       Public Overloads Sub PollAsync(ByVal session As Integer)
           Me.PollAsync(session, Nothing)
       End Sub
       
       Public Overloads Sub PollAsync(ByVal session As Integer, ByVal userState As Object)
           If (Me.PollOperationCompleted Is Nothing) Then
               Me.PollOperationCompleted = AddressOf Me.OnPollOperationCompleted
           End If
           Me.InvokeAsync("Poll", New Object() {session}, Me.PollOperationCompleted, userState)
       End Sub
       
       Private Sub OnPollOperationCompleted(ByVal arg As Object)
           If (Not (Me.PollCompletedEvent) Is Nothing) Then
               Dim invokeArgs As System.Web.Services.Protocols.InvokeCompletedEventArgs = CType(arg,System.Web.Services.Protocols.InvokeCompletedEventArgs)
               RaiseEvent PollCompleted(Me, New PollCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState))
           End If
       End Sub

This ultimately ends up in raising a class defined event (InitializeCompleted in this example). This event can be handled by the application. In the sample code, this is done by deriving a new class

Public Class myPBX

   ' derive from the auto-generated soap class
   Inherits pbx_wsdl.pbx

This new class can handle events from its parent class

   ' handle an async Poll result
   Private Sub pollCB(ByVal sender As Object, ByVal e As pbx_wsdl.PollCompletedEventArgs) _
       Handles MyBase.PollCompleted

The sample application

This section will discuss the sample code. It is not a decent description of the PBX SOAP API. For this, see the related articles.

The form startup code in Form1_Load needs to initialize the pbx SOAP link. It first creates an instance of the derived class

           ' create the link
           pbx = New myPBX(Me)

then sets the basic (HTTP) access parameters

           ' set the URL
           pbx.Url = pbxUrl
           ' set the HTTP level credentials
           pbx.Credentials = New Net.NetworkCredential(httpUser, httpPw, "")

initializes the session and creates a monitor on one of the users

           ' initialize the session, remember the session-id and -key
           pbxSession = pbx.Initialize(pbxUser, "VBNet SOAP Test", True, True, pbxKey)
           ' monitor a user
           pbxUserId = pbx.UserInitialize(pbxSession, pbxMonitor, False)

and finally kicks off the first asynchronous Poll() to get at the PBX events.

           ' start async retrieval of events from pbx
           pbx.startPolling()

The derived class myPBX handles events of type pbx.PollCompleted:

   Private Sub pollCB(ByVal sender As Object, ByVal e As pbx_wsdl.PollCompletedEventArgs) _
       Handles MyBase.PollCompleted

It iterates over the user and call members in the Result member of the PollCompletedEventArgs class passed to the PollCompleted event handler.

       ' scan user and call events
       For Each ui As pbx_wsdl.UserInfo In e.Result.user
           ' Me.form.events.Items.Add("user " + ui.cn) -- dangerous
           Me.form.Invoke(p, New Object() {"user " + ui.cn})
       Next
       For Each ci As pbx_wsdl.CallInfo In e.Result.call
           ' Me.form.events.Items.Add("call -> " + ci.msg) -- dangerous
           Me.form.Invoke(p, New Object() {"call -> " + ci.msg})
       Next

(See below for a discussion of Me.form.Invoke) It then schedules the next asynchronous Poll()

       ' schedule next async Poll
       Me.startPolling()


Known Problems

Asynchronous activities are quite tricky to handle in Visual Basic.Net (actually, this is true for all .Net-based languages, such as e.g. C#). This is because .Net tries to shield developers from the complexity of asynchronous operations. Unfortunately, this doesn't work really.

Parallel SOAP Requests

While a SOAP client object (such as pbx_wsdl.pbx in our code example) are not multi-threaded. That is, you must not have 2 requests active on the same object. A typical mistake thus is to do an asynchronous Poll() request on such an object and to use the same object to do another call (such as Echo() or any User*()) meanwhile. You need to instantiate one separate object per parallel request.

Unfortunately, it looks as if .Net tries to optimize the use of underlying HTTP client objects by re-using such an object when it is available and bound to the same SOAP server. That is, even when you instantiate separate pbx_wsdl.pbx objects for parallel requests, .Net may still use the same HTTP client object under the hood, again resulting in synchronization problems. This can be worked around by setting the ConnectionGroupName property differently for each pbx_wsdl.pbx object.

Windows Forms and Asynchronous Events

We have seen .Net programs failing when Windows Forms objects are used in asynchronous event handlers such as pollCB in our sample application. You can work around this problem by using the Invoke method of the form. This will schedule a function within the form main threads execution context and allows you to use the form object safely.

Loosing the PBX connection

When the PBX restarts or the link is lost due to a network issue, the connection and all handles are invalidated. It is thus important to detect such a situation and to handle it gracefully. While this is beyond the scope of this article, here are a few hints.

You cannot be sure to receive an error notification from the SOAP client object (pbx_wsdl.pbx). Especially for asynchronous operations, you may or may not receive a completion event for a failed request. Moreover, as HTTP is stateless in nature, you might not encounter a failed request at all if the PBX restarts in between two requests.

To deal with that, you should use the Echo() request to verify the link, as when the PBX restarted meanwhile, the Echo's key parameter will be invalid and the Echo fail. You may consider calling Echo() just before any call to Poll(), or you may want to call Echo regularly using the Windows Forms Timer object. If you decide for the latter, be sure to use separate pbx_wsdl.pbx instances for this.

Once the PBX is lost, you must not use any pbx_wsdl.pbx object any further that has an asynchronous request active at that moment. This is because you cannot safely determine when the (presumably failing) asynchronous request is actually finished. If you use the object for the next request, you may run into synchronization problems in underlying .Net layers. Allocate a new object for subsequent requests and trust in .Net's garbage collector to eventually reclaim the old objects resources.

Download

  • Download the complete file package of scripts and files described in this article.

Related Articles

Reference:SOAP API

Howto:PBX SOAP Api C-sharp sample code

Howto:SOAP with PHP5