Search code examples
c#vb.netringcentral

How to convert RingCentral SMS console app to a Forms app in Visual Studio?


I need to send SMS messages from my VB.net Windows Forms app. The RingCentral documentation provides a console app example in C#, which is fairly simple to convert to VB.net using InstantVB. The code below works as it's a direct translation from their C# example, but it's still a console application. (Oh, and their example has a bug I had to fix. RINGCENTRAL_PRODUCTION should be boolean False, not a string "False" as their example has it.)

Anyway... I've been trying to run similar code in a Windows Forms app. But of course, I don't have a Sub Main for a Forms app. So I tried putting that code into the Form.Load event. But when done that way, it always just hangs on the Authorize line with no error message -- It just hangs until I click the "pause" (break) button in the IDE.

I've been working on this all day, searching for answers online, and would greatly appreciate any guidance.

Imports System
Imports System.Threading.Tasks
Imports RingCentral

Namespace Send_SMS
    Friend Class Program
        Private Const RECIPIENT As String = "9165551212"
        Private Const RINGCENTRAL_CLIENTID As String = "_not_my_real_client_ID"
        Private Const RINGCENTRAL_CLIENTSECRET As String = "_not_my_real_client_secret_"
        Private Const RINGCENTRAL_PRODUCTION As Boolean = False

        Private Const RINGCENTRAL_USERNAME As String = "+14245551212"
        Private Const RINGCENTRAL_PASSWORD As String = "notarealpassword"
        Private Const RINGCENTRAL_EXTENSION As String = "101"

        Private Shared restClient As RestClient

        Shared Sub Main(ByVal args() As String)
            restClient = New RestClient(RINGCENTRAL_CLIENTID, RINGCENTRAL_CLIENTSECRET, RINGCENTRAL_PRODUCTION)

            restClient.Authorize(RINGCENTRAL_USERNAME, RINGCENTRAL_EXTENSION, RINGCENTRAL_PASSWORD).Wait()
            send_sms().Wait()
        End Sub

        Private Shared Async Function send_sms() As Task
            Dim parameters = New CreateSMSMessage()
            parameters.from = New MessageStoreCallerInfoRequest With {.phoneNumber = RINGCENTRAL_USERNAME}
            parameters.to = New MessageStoreCallerInfoRequest() {
                New MessageStoreCallerInfoRequest With {.phoneNumber = RECIPIENT}
            }
            parameters.text = "Hello World from VB.net" '< I changed that part.
            Dim resp = Await restClient.Restapi().Account().Extension().Sms().Post(parameters)
            Console.WriteLine("SMS sent. Message status: " & resp.messageStatus)
        End Function


    End Class
End Namespace

Just for reference, below is the original in C# (with the bug fixed):

using System;
using System.Threading.Tasks;
using RingCentral;

namespace Send_SMS
{
    class Program
    {
        const string RECIPIENT = "9165551212";
        const string RINGCENTRAL_CLIENTID = "_not_my_real_client_ID";
        const string RINGCENTRAL_CLIENTSECRET = "_not_my_real_client_secret_";
        const Boolean RINGCENTRAL_PRODUCTION = false;

        const string RINGCENTRAL_USERNAME = "+14245551212";
        const string RINGCENTRAL_PASSWORD = "notarealpassword";
        const string RINGCENTRAL_EXTENSION = "101";

        static RestClient restClient;

        static void Main(string[] args)
        {
            restClient = new RestClient(RINGCENTRAL_CLIENTID, RINGCENTRAL_CLIENTSECRET, RINGCENTRAL_PRODUCTION);

            restClient.Authorize(RINGCENTRAL_USERNAME, RINGCENTRAL_EXTENSION, RINGCENTRAL_PASSWORD).Wait();
            send_sms().Wait();
        }
        static private async Task send_sms()
        {
            var parameters = new CreateSMSMessage();
            parameters.from = new MessageStoreCallerInfoRequest { phoneNumber = RINGCENTRAL_USERNAME };
            parameters.to = new MessageStoreCallerInfoRequest[] { new MessageStoreCallerInfoRequest { phoneNumber = RECIPIENT } };
            parameters.text = "Hello World from C#";

            var resp = await restClient.Restapi().Account().Extension().Sms().Post(parameters);
            Console.WriteLine("SMS sent. Message status: " + resp.messageStatus);
        }
    }
}

Solution

  • There isn't any magical difference between a forms app and a console app..

    Create a new Forms app (that has a single form called Form1, drop a single button on the form and leave the default name of Button1 [but promise me you'll change it later]), right click the project node in solution explorer and choose Manage Nuget Packages, Browse for the RingCentral.Net package and install it

    Paste this code over the top of everything in the code of Form1.vb:

    Imports RingCentral
    
    Public Class Form1
        Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    
            restClient = New RestClient(RINGCENTRAL_CLIENTID, RINGCENTRAL_CLIENTSECRET, RINGCENTRAL_PRODUCTION)
    
            Await restClient.Authorize(RINGCENTRAL_USERNAME, RINGCENTRAL_EXTENSION, RINGCENTRAL_PASSWORD)
            Await send_sms()
    
        End Sub
    
        Private Const RECIPIENT As String = "9165551212"
        Private Const RINGCENTRAL_CLIENTID As String = "_not_my_real_client_ID"
        Private Const RINGCENTRAL_CLIENTSECRET As String = "_not_my_real_client_secret_"
        Private Const RINGCENTRAL_PRODUCTION As Boolean = False
    
        Private Const RINGCENTRAL_USERNAME As String = "+14245551212"
        Private Const RINGCENTRAL_PASSWORD As String = "notarealpassword"
        Private Const RINGCENTRAL_EXTENSION As String = "101"
    
        Private Shared restClient As RestClient
    
    
    
        Private Shared Async Function send_sms() As Task
            Dim parameters = New CreateSMSMessage()
            parameters.from = New MessageStoreCallerInfoRequest With {.phoneNumber = RINGCENTRAL_USERNAME}
            parameters.to = New MessageStoreCallerInfoRequest() {
                    New MessageStoreCallerInfoRequest With {.phoneNumber = RECIPIENT}
                }
            parameters.text = "Hello World from VB.net" '< I changed that part.
            Dim resp = Await restClient.Restapi().Account().Extension().Sms().Post(parameters)
            Console.WriteLine("SMS sent. Message status: " & resp.messageStatus)
        End Function
    
    End Class
    

    Naturally, I get an exception because the details in the code are bogus, but it's a response from the service at least!

    enter image description here


    Bonus: Await and Task 101:

    Methods (functions/subs) that return Tasks can be awaited. Methods that contain await keyword need to be marked async within the body of their code. If you run a method that returns a Task(Of X) you should quickly get a Task(Of X) back, while the runtime starts working on the task. You could conceive that right now the Task is an empty container that will be filled with a X when the Task is complete. If you want the X inside it, await will wait for the Task to finish and then pull the X out of it for you; "await turns a Task(Of X) into an X, when the X is ready". While your program's thread is awaiting that to happen it goes back to whatever it was doing before (drawing the UI probably) which is good because it makes it look like your app hasn't hung.

    You should note that it's a bit like saving your computer game and turning off the computer when a friend comes round for coffee (the task that will take a while) - friend goes, you go back to the game, reload the saved state and and everything is as it was. Your program hit the await, saved its game and went off to do something else. When the task was complete it came back, reloaded the game, and carried on from where it left off

    You'll note that a Task(Of X) has other things like a Result property. If you access that instead of awaiting, then your program will jam up while the task completes, and then you get your X. In a console app that probably doesn't matter cos the console app probably wasn't going to be doing anything else in the interim, but a UI app needs to draw its UI otherwise windows will think it's Not Responding.

    I lead with Task(Of X) because it's probably easier to understand "a task that does something that will take a while and then gives me something I want". For methods that return a Task (i.e. no output object from the task) you just Await them and then they finish. Await doesn't dig any result object out because there isn't one, but your program was still able to draw its UI. Calling Wait() on the Task will also wait for it to finish, but it'll jam up your UI while it does so