How to dial phone from a .NET website? Very old solution works, need to upgrade to server control

1

Currently, we have a website that relies on a Microsoft TAPI interface to dial a phone from within a .NET web site. It uses VBScript and tags, and it is bound to a master page. What we are looking for is a server control that would encompass all of this code and only be ran when it is included on a webpage.

The old page does the following:

<object classid="clsid:21D6D48E-A88B-11D0-83DD-00AA003CCABD" id="TAPIOBJ"></object>
<object classid="clsid:E9225296-C759-11d1-A02B-00C04FB6809F" id="MAPPER"></object>

After these lines of code are tags that contains VBScript to initialize the Microsoft TAPI 3.0 library and a few functions to dial. A Dialer control creates a call to one of the functions to dial in an onclick event.

Essentially, we want to create the same type of control without having tags embedded into the HTML of a page directly. We also do not want VBScript in there. Ideally, we would like a server control that works with the TAPI 3.0 API and gains access to the client's phone. Is this possible? Since we are talking about a "server" control, I'm skeptical. I could just as easily create a user control within the project, but we'd like to have this in a controls framework for use elsewhere instead of copying it.

I've been looking at this article on how to create a server control for injection of Client ActiveX controls, but is this down the right path?

UPDATE: Here's the VBScript:

This is what sits in the tag:

<script type="text/vbscript"  LANGUAGE="VbScript">

'Constants section
'These constants are copied from tapi3if.idl
Const TAPIMEDIATYPE_AUDIO  = &H08&
Const TAPIMEDIATYPE_VIDEO = &H8000&
Const S_MEDIA_AUDIOVIDEO = &H8008&
Const TD_CAPTURE  = 0
Const TD_RENDER = 1
Const QSL_NEEDED = 1
Const AS_INSERVICE = 0
Const DC_NORMAL = 0
Const TE_CALLSTATE = 8
Const TE_CALLNOTIFICATION = 4
Const CS_DISCONNECTED = 3
Const CS_IDLE = 0
Const CS_OFFERING = 4
Const CS_CONNECTED = 2
Const CNE_OWNER =  0
Const CIS_CALLERIDNAME = 0
Const CIS_CALLERIDNUMBER = 1
'Interface IDs for casting 
'Note: you can find the following IID-s in tapi3.h, tapi3if.idl or rend.idl

Const IID_String_ITMediaSupport = "{B1EFC384-9355-11D0-835C-00AA003CCABD}"
Const IID_String_ITTerminalSupport="{B1EFC385-9355-11D0-835C-00AA003CCABD}"
Const IID_String_ITBasicCallControl = "{B1EFC389-9355-11D0-835C-00AA003CCABD}"

'Const IID_String_ITCallInfo = "{B1EFC390-9355-11d0-835C-00AA003CCABD}"
'New interface
Const IID_String_ITCallInfo = "{350F85D1-1227-11D3-83D4-00C04FB6809F}"
Const IID_String_ITStreamControl= "{EE3BD604-3868-11D2-A045-00C04FB6809F}"
Const IID_String_ITDirectoryObjectConference= "{F1029E5D-CB5B-11D0-8D59-00C04FD91AC0}"
Const IID_String_ITCallStateEvent = "{62F47097-95C9-11d0-835D-00AA003CCABD}"
Const IID_String_ITCallNotificationEvent = "{895801DF-3DD6-11d1-8F30-00C04FB6809F}"

' IID of IVideoWindow 
' Note: you can find this IID defined in control.h (from your sdk\inc directory), 
' which contains the interface to type library QuartzTypeLib for quartz.dll;
' (search for the interface IVideoWindow)
Const IID_String_IVideoWindow = "{56A868B4-0AD4-11CE-B03A-0020AF0BA770}"

' The following CLSID is defined in tapi3.h 
'(and it's used for creating a terminal of class "video window terminal")
Const CLSID_String_VideoWindowTerm = "{F7438990-D6EB-11d0-82A6-00AA00B5CA1B}"


'****************************************************************************
'Global variable section
'****************************************************************************
Dim CallStatus
Dim pICallState
pICallState = 0

'Set on True when we are unable to complete the connecting phase, to skip rest of processing
DIM sUnableToComplete
DIM sbNeedToExit
sUnableToComplete = False
sbNeedToExit = False

' If we want to receive incoming calls, we have to register on the corresponding addresses.
'We don't really use the values returned by registration (they are supposed to be used 
'for unregistration), because Unregistration is performed automatically when we shutdown the TAPI object

'The variable pRegisteredCallNotification is an array that contains cookies returned by RegisterCallNotifications;
'these would normally be used to call UnregisterNotifications.

'The variable pRegisteredName holds correspondent AddressName 

DIM pRegisteredCallNotification(50)
DIM pRegisteredName(50)
DIM iQtaRegistered
DIM callFrom

iQtaRegistered = 0


'Set by radio button "Select Address Type" 
DIM sCurrentAddressType
'sCurrentAddressType = -1
sCurrentAddressType = 1

' This variable will hold a reference to the currently established call
DIM spITCall 
spITCall = Empty


DIm pVideoWindow1
DIm pVideoWindow2

'Simplest error processing
Sub CheckError(strMsg)
if not Err.number = 0 Then
    MsgBox strMsg & ":" & Err.number & ";"&Err.description
    sbNeedToExit = True
    Err.Clear
End If

End Sub

Function IsComponentInstalled(ProgId)
Dim tmpObject

On Error Resume Next

    Set tmpObject = Server.CreateObject(ProgId)

    If Err.Number = 0 Then
        IsComponentInstalled = True
    Else
        IsComponentInstalled = False
    End If

    Set tmpObject = Nothing

End Function

</script>

Below the end body tag after the tags is:

<script type="text/vbscript"  LANGUAGE="vbscript">

' Be sure that you call TAPIOBJ.Initialize before window_onload, otherwise you'll
' never receive events from tapi... 
On Error Resume Next

call TAPIOBJ.Initialize
sUnableToComplete = False
TAPIOBJ.EventFilter = &H1FFFF&


if Not Err.number = 0  Then
   MsgBox "TAPI software has not been installed on your workstation.",0,"Init"
   sUnableToComplete = True
End If  


For Each  pITAddress in TAPIOBJ.Addresses

    if left(pITAddress.AddressName,4) <> "Line" and left(pITAddress.AddressName,29) <> "Shoreline Multi-Line Monitor:" _
    and pITAddress.MediaTypes = 8 then
        callFrom = pITAddress.AddressName
    end if

next

'This section shows how to override Application Priority:
'after the execution of the following lines, our app will always receive incoming calls
'even if there are other running tapi apps that had registered for receiving calls before our app.

call TAPIOBJ.SetApplicationPriority("IEXPLORE.EXE",TAPIMEDIATYPE_AUDIO,TRUE)
call TAPIOBJ.SetApplicationPriority("IEXPLORE.EXE",TAPIMEDIATYPE_VIDEO,TRUE)



' Check parameters of a call before connecting it
Sub PressConnect(pNumber,Status)
On Error Resume Next
'MsgBox (pNumber & "," & Status)
DIM iAddressType
 DIM pConnectTo

DIM addressFrom
DIM selStr

'If not IsEmpty(spITCall) Then
'  MsgBox "You are currently in call. Disconnect first",0,"connect"
'End If


pConnectTo = pNumber
set CallStatus=Status

addressFrom = callFrom 

If addressFrom = "" Then
    callStatus.innerHTML = "Feature Unavailable"
    MsgBox "The TAPI Feature has not been setup on your phone line.",0,"COnnect"
else   
    sUnableToComplete = False

    callStatus.innerHTML = "Connecting to " & pConnectTo & " ...."

    'Create new internal call representation 
     For Each  pITAddress in TAPIOBJ.Addresses 
        if pITAddress.AddressName = addressFrom Then
         'Obtain ITMediaSupport
         Set pITAddress_Connect = pITAddress
         Exit For
      End If    
     Next

    Set pITAddress = Nothing

     'Create a Call
     DIM MediaTypes

     MediaTypes = TAPIMEDIATYPE_AUDIO

     Set pCall = pITAddress_Connect.CreateCall(pConnectTo,1,MediaTypes)

     Set spITCall = pCall

     if sUnableToComplete Then
       Call DisconnectCall(1)
       callStatus.innerHTML = "Call to "& pConnectTo & " failed."
     End If

     Call pCall.Connect(false)

     ' Check for error "invalid address" (see in tapi3err.h TAPI_E_INVALADDRESS=(HRESULT)0x8004000C)
     if Err.Number = &H8004000C Then
      Err.Clear
      Call DisconnectCall(1)
      callStatus.innerHTML = "Call to "& pConnectTo & " failed: Address is invalid"
      Set pCall = Nothing

    Else
      if not Err.Number = 0 Then 
        Err.Clear
        Call DisconnectCall(1)
        callStatus.innerHTML = "Call to "& pConnectTo & " failed: error " & Hex(Err.number)
        Set pCall = Nothing
      Else
        Set spITCall = pCall
      End if

    End If
end if  

Set pCall = Nothing

end sub

' Disconnect current call
Sub HangUp(callDisc)
'On Error resume Next

    if not IsEmpty(spITCall) Then

        if not callDisc = 8 and not callDisc = 0 Then 
          ' We need some kind of message pump here. The following call to MsgBox does exactly this:
          'MsgBox "A call is disconnected",0,"Disconnect"  
        End If  

        Set pVideoWindow1 = Nothing
        Set pVideoWindow2 = Nothing

    '   ConnANN.innerHTML =  "Disconnected"  

        if callDisc=0  Then
          spITCall.Disconnect(DC_NORMAL)
        End If

        Set spITCall = Nothing 
        spITCall = Empty  
        callStatus.innerHTML = "Disconnected"

        'btnDisconnect.disabled = true   
        'source.visible = false
    End If
End Sub


'*****************************************************************************
' Tapi events processing: 
' - call state events ("connected", "disconnected") 
' - and call notification events (these calls will be in "offering" state)
Sub TAPIOBJ_Event(event_type, tapi_event)
'On Error Resume Next  

'Check For disconnected call
if event_type = TE_CALLSTATE Then
  DIM pITCallStateEvent

  Set pITCallStateEvent = MAPPER.QueryDispatchInterface(_
  IID_String_ITCallStateEvent,tapi_event)

  iCallState = pITCallStateEvent.State 

  if iCallState= CS_DISCONNECTED or iCallState= CS_IDLE Then

   cause = pITCallStateEvent.Cause
   'pICallState=iCallState
   strinnerHTML = ""
   Select Case cause
     Case 1 '  CEC_DISCONNECT_NORMAL - Normal disconnect
       strinnerHTML =  "Disconnected"
     Case 2 '  CEC_DISCONNECT_BUSY
       strinnerHTML =  "Your Party is busy.Try Later."
     Case 3 '  CEC_DISCONNECT_BADADDRESS
       strinnerHTML =  "Address is invalid"
     case 4 '  CEC_DISCONNECT_NOANSWER
       strinnerHTML =  "No answer from your party."
     case 0 'CEC_NONE
       strinnerHTML =  "No answer from your party."
     Case Else
       strinnerHTML =  "Your call is cancelled, rejected or failed"       
   End Select

    'Call DisconnectCall(1)
    'btnDisconnect.disabled = true             
  End If  'Call is disconnected

  if iCallState = CS_CONNECTED Then 'Call is connected
   callStatus.innerHTML =  "Call is connected."
   'btnDisconnect.disabled =  False
  End If 'Call is connected
End If ' event: call state


'Check only for incoming calls
if event_type = TE_CALLNOTIFICATION Then ' We have an incoming call (an "offering" call)

  DIM pITCallNotificationEvent
  Set pITCallNotificationEvent = MAPPER.QueryDispatchInterface(_
  IID_String_ITCallNotificationEvent,tapi_event)

  Call CheckError("TAPIOBJ_Event:query for pITDirectoryObjectUser")   

  CallOwnership = pITCallNotificationEvent.Event

  DIM pITCallInfo

  Set pITCallInfo = pITCallNotificationEvent.Call

  Call CheckError("TAPIOBJ_Event:get pITCallInfo")   

  if not blnShowOnlyOnce and  pITCallInfo.CallState  = CS_OFFERING and not ( CallOwnership = CNE_OWNER) Then
    MsgBox "Unable to accept incoming calls: is other instance of this app running?",0,"Info"
     blnShowOnlyOnce  = True
    Exit Sub
  End IF

  if CallOwnership = CNE_OWNER Then  'We are the owner!

    if not IsEmpty(spITCall) Then
      MsgBox "Already in call, disconnect first",0,"Incoming Call"
      Exit Sub
    End if

    if  pITCallInfo.CallState  = CS_OFFERING Then 'Offering

      '-- CIS_CALLERIDNAME Wasn't working so I switched to NUMBER
      sCalleeName = pITCallInfo.CallInfoString(CIS_CALLERIDNAME)
      if not Err.number = 0 then ' Caller ID name is not supported 
        sCalleeName =  "Unknown Name"
        Err.Clear
      End if

      sCalleeNumber = pITCallInfo.CallInfoString(CIS_CALLERIDNUMBER)
      if not Err.number = 0 then ' Caller ID name is not supported 
        sCalleeNumber =  "Unknown Number"
        Err.Clear
      End if

      DIM pITCallOffer
      Set pITCallOffer = MAPPER.QueryDispatchInterface( _ 
        IID_String_ITBasicCallControl, pITCallInfo)

      Call CheckError("TAPIOBJ_Event:query for pITCall")   

      response = MsgBox("A call from '" & sCalleeNumber & " " & sCalleeName & "' has arrived. Do you want to accept it?",4,"Incoming call")

      if not response = 7    Then       'the did not press "NO", so he pressed "YES"
        Call AcceptIncomingCall(pITCallOffer, pITCallInfo)
      End If  
    End If 'Call is offering
   End If 'We are owner   

End If 'Call Notification has arrived  

End Sub 

</script>

Is it possible to use the ITAPI3 Managed Library to get rid of this and do this in the code-behind?

c#
.net
vb.net
telephony
tapi
asked on Stack Overflow Feb 12, 2013 by jlrolin • edited Feb 12, 2013 by jlrolin

1 Answer

0

Hopefully, someone knows of a cleaner, more modern way of doing this, but here's what I did.

I embedded the VBScript into a script server-side, and registered it as a client script. Before I registered it, I added the tags into Literal controls and added them to the Page header. The server control itself was nothing but a link around a phone icon image.

Since I could call VBScript functions inside javascript, I created a GetPhoneNumber function that accepted the controlID of the control where I would get my phone number. This was set on the ControlID property of the Dialer control. In this instance, it was a textbox. The function parses the number for any bad data, then called PressConnect... done. It works, but I'm not really all that pleased with using VBScript in this manner.

If anyone has any idea how to interact with the client (local) TAPI of a user (Actual examples would be appreciated), post them here.

answered on Stack Overflow Feb 12, 2013 by jlrolin

User contributions licensed under CC BY-SA 3.0