Process flow for RingOut on ASP.NET WebForms

  • 1
  • Question
  • Updated 2 weeks ago

Hi,

I have an ASP.NET Web Forms site, where staff members can view all the details of a customer including phone number, and I want those staff members to be able to make RingOut calls to customers by clicking a button next to their phone number.

Can anybody help me out on the process of how this would work?  There are plenty of non-real-life demos of the technical side, and I can reproduce all the steps of getting authenticated and making the call, I am just struggling to see how the process would work in real life? 

From a customer details page, when the staff member clicked the button, the first thing I’d have to do is get the auth URL, right?  But that then sends an auth code back to a different page – the callback page.  So now I’ve lost the details about who I’m trying to call?  Am I supposed to use the state parameter to remember those details or something?  (I presume not from the description of it).  Or add the number I’m trying to call as a parameter on the end of the callback page?

How often am I supposed to have to get the auth URL and code – just once and then store it in a database?  Or I store the URL in a database and get the code each time I need to make a call?  Or get both each time I need to place a call?

It may be a blind spot on my part but I just cannot see at all how to map the technical examples available to a real life example.  It's not at all clear which bits are one off actions and which need to be repeated for each RingOut call.

I appreciate JavaScript may be a better option for me but my JS isn’t too strong and I can’t get the example (https://ringcentral.github.io/cti-demo) working for me.  Nothing happens when I click “Link Account”.

Would appreciate any help from anyone who has done this stuff.

Thanks in advance.

 
Photo of Dave Harrison 301

Dave Harrison 301

  • 152 Points 100 badge 2x thumb

Posted 1 month ago

  • 1
Photo of Brandon Hein

Brandon Hein

  • 388 Points 250 badge 2x thumb
Hi Dave,

Are your users going to be set up with physical desk phones or do you need to achieve this click to call feature via the webonly?

I think there are browser plug-ins that can achieve this. I think there's a basic Chrome Plugin that achieved this exact scenario via the Web.

However I had to build out something else since it didn't fit the requirements for me. I use the Ring Central does have a c# sdk you can use. Where when my user clicks the call button, which goes to my apps mvc controller as an action with parameters, and we start the process of the Ringout using the c# sdk and the user's phone will ring first once answered will then route to the customer. The user experience would be the button click and their desk phone started ringing. But everything is working on the server back end vs the Javascript front end.

Anything that uses the sdk (at least from the c# one I've worked with), you should only need to Auth once... as the sdk should take care the token code for you and thr reauth process. You wouldn't need to reauth for every ringout call, just need to pass on the parameters.
Photo of Dave Harrison 301

Dave Harrison 301

  • 152 Points 100 badge 2x thumb

Thanks, Brandon.  I appreciate your reply.  My staff will have physical phones so the setup is like yours, except I’m Web Forms. 

So I’ve created this class, give or take a bit of error handling:

public static class RingCentralCall

{

 

  private static RestClient rc = new RestClient("ClientId", "ClientSecret", false);

 

  public static async Task<TokenInfo> GetTokenAsync(string authCode, string redirectUrl)

  {

    TokenInfo token = await rc.Authorize(authCode, redirectUrl);

    return token;

  }

 

  public static async Task<string> MakeCallAsync(string from, string to)

  {

    var extension = rc.Restapi().Account().Extension();

    var requestBody = new

    {

      from = new { phoneNumber = from },

      to = new { phoneNumber = to },

      playPrompt = true

    };

    var response = await extension.RingOut().Post(requestBody);

    string CallId = response.id;

    return CallId;

  }

 

  public static async Task CancelCallAsync(string callId)

  {

    var extension = rc.Restapi().Account().Extension().RingOut(callId);

    var Url = extension.Url();

    var response = await extension.Delete();

  }

 

  public static async Task<string> GetAuthorizeUri()

  {

    var authorizeUri = rc.AuthorizeUri("https://mycompany/Callback.aspx";);

    return authorizeUri;

  }

}

 

Then I have a page – let’s call it CustomerDetail.aspx – that I can use to get to the Authorization URL:

protected void Page_Load(object sender, EventArgs e)

{

  RegisterAsyncTask(new PageAsyncTask(() => GoToAuthorizeUri()));

}

 

private async Task GoToAuthorizeUri()

{

  var AuthorizeUri = await RingCentralCall.GetAuthorizeUri();

  Response.Redirect(AuthorizeUri);

}

 

That returns an Authorise Uri and redirects me there to the page that says “Click Authorise to allow this app and RingCentral to use your information etc.”  On clicking Authorise I’m then redirected to my callback page – Callback.aspx.  But now I’ve lost the details of who I was trying to call.

On Callback.aspx I get the token, and then I can make or cancel a call to a hardcoded number – that’s all working successfully.

protected async void Page_Load(object sender, EventArgs e)

{

  var authCode = Request.QueryString["code"].ToString();

  var state = Request.QueryString["state"].ToString();

  TokenInfo token = await RingCentralCall.GetTokenAsync(authCode, " https://mycompany/Callback.aspx";);

}

 

protected void btnCall_Click(object sender, EventArgs e)

{

  RegisterAsyncTask(new PageAsyncTask(() => PlaceCall(from: txtFrom.Text, to: txtTo.Text)));

}

 

protected void btnCancel_Click(object sender, EventArgs e)

{

  RegisterAsyncTask(new PageAsyncTask(() => CancelCall()));

}

 

private async Task PlaceCall(string from, string to)

{

  lblCallId.Text = await RingCentralCall.MakeCallAsync(from, to);

}

 

private async Task CancelCall()

{

  await RingCentralCall.CancelCallAsync(lblCallId.Text);

}

But again, the issue is that I knew the customer’s phone number at CustomerDetail.aspx, but I’ve lost it by Callback.aspx.  How can I retain it through that process?

Is there anything obvious I’m doing badly or inefficiently? 

Do I need to do all these things each time?  Or is the Authorise URL always the same, per organisation?  So once I get it once I can just store it and re-use it?  Or is it per user?  Or does it expire?

Also should I be getting a new token like this each time I make a call?  Or is that bad practice?  Should I store it – if so how?  I see there’s a refresh token option – what are the pros/cons of using this?  Seems like just as much work as getting a fresh token, if not more?

I would really appreciate any guidance in the areas of best practice and best user experience, as well as the practical problem of retaining the destination phone number after being redirected to the Authorization URI.

   
Photo of Brandon Hein

Brandon Hein

  • 388 Points 250 badge 2x thumb
You really shouldnt be calling auth call back for every single call. My personal opinion. It should be called at the beginning of a session and only then. Because you really just need the bearer token to access any api call after than.

What I did was at the start of the session for my user I had them log in to RC with my own custom screen, asking for a phone number and password to RC. (My RC app uses the password flow, no call back oauth needed) And similar to your static class... it's a singleton to retrieve the auth token needed to make the ringouts, text commands and more.

Let me see if I can find my sample windows forms solution and put it in Github for reference
Photo of Tyler Long

Tyler Long, Official Rep

  • 8,724 Points 5k badge 2x thumb
I  agree with Brandon that you should not do authorization for every single call.  As soon as user launches your page, you should check whether this user has RC token. If not, redirect him to do authorization. Then you save the token in cookies or somewhere else, so that user will not lose that token even upon browser refresh or computer reboot.  Then for every call you just use the token to invoke RC API, no more authorization is needed.
Photo of Dave Harrison 301

Dave Harrison 301

  • 152 Points 100 badge 2x thumb
Thanks for your reply, Tyler.  Things are becoming a little clearer.  I put the token in a cookie, while ever that cookie is there I don't have to worry about things, if it's not there/expired, I go and get a token.

However the problem with that picture is that as far as I can see the token is never actually used to make the phone calls - see my MakeCallAsync method.  It seems to me as though the important bit is not actually the token value itself, but the call to rc.Authorize - is that correct?  Once that is called, I can make and cancel calls.  

So really the correct logic is, if rc.Authorize hasn't been called, call it.  If it has already been called, don't. 

The token is just a way of recording whether or not it's happened, and when it happened.  When the token is 7 days old, I know I have to call rc.Authorize again.  

Is that a correct interpretation?

Also, does rc.Authorize handle whether or not to use the refresh token or get a new token the long way?  Is that something I don't need to worry about?
Photo of Dave Harrison 301

Dave Harrison 301

  • 152 Points 100 badge 2x thumb
If my above interpretation is correct, I guess I can put this together.  I just wonder though what would happen in a situation where the cookie was still available but for whatever reason I needed to re-authorize.  Presumably an error would be returned - which one?

Also am I right to assume that after a call to rc.Authorize I should store the cookie for 7 days?  The expires_in value no longer seems to be returned - any reason why?  If this changes in future how will I know?

Finally, this is pretty difficult to test with only one test account number, because I am now authorised for the next 7 days - is there any way to replicate what would happen if I wasn't authorised?
Photo of Tyler Long

Tyler Long, Official Rep

  • 8,724 Points 5k badge 2x thumb
If you are writing code on server side, you probably should save the token in database instead of the cookie. I know for a pure client side app (JavaScript SPA) it is a good idea to store token in cookie. But for ASP.net the C# code is running on server side so it's different.


As long as you have a token, you don't have to do authorization. Because authorization is just to retrieve a token:

var token = <read token from DB or somewhere else>
rc.token = token
rc.<make an API call>

https://github.com/ringcentral/ringcentral-csharp-client/blob/master/RingCentral/RestClient.cs#L82


The token object contains access_token and refresh_token properties, the former expires in an hour, the latter expires in 7 days.  If the former expires, you refresh the token. If the latter expired, you need to authorization again.

There is also auto token refresh. You can disable it if you save the token and mange it yourself: https://github.com/ringcentral/ringcentral-csharp-client#auto-refresh
Photo of Dave Harrison 301

Dave Harrison 301

  • 152 Points 100 badge 2x thumb

Okay so the process is:

User clicks  button to make call.

If no token in database newer than 7 days, get authorization URL and show it in a pop up for user to log in.  With returned token call rc.Authorize and store token in database.  (In order to store the token against a particular username I will pass username as the “state” parameter, because the callback URL is an external (public) site that won’t be able to reference the user’s username otherwise.  Is that usual/acceptable use of the state parameter?)

If token present in database between 1 hour and 7 days old, call rc.Refresh and store returned token in database.

If token present less than 1 hour old, use that.

Does that sound about right?

Are you able to advise on maximum field sizes for the token properties for database storage please?

Side note is there a reason most of the code here is commented out?  https://github.com/ringcentral/ringcentral-csharp-client/blob/master/RingCentral/TokenInfo.cs
Photo of Tyler Long

Tyler Long, Official Rep

  • 8,644 Points 5k badge 2x thumb
The process you described is correct. No major problem for me.

Only one concern: checking timestamp may not be 100% reliable. You can try catch exception instead, check the exception to tell whether token has expired.
Photo of Dave Harrison 301

Dave Harrison 301

  • 152 Points 100 badge 2x thumb
Thanks, yes I will add that in, checking for a 401.

Any advice on maximum sizes for the various token values?
Photo of Tyler Long

Tyler Long, Official Rep

  • 8,644 Points 5k badge 2x thumb
The C# tokenInfo object serialized to JSON is like below:

{"access_token":"....","refresh_token": "....", "token_type":"bearer","expires_in":2147483647,"scope":"ReadAccounts EditExtensions SubscriptionWebhook Glip","owner_id":"8989432432432","endpoint_id":"fdsafdsafdsa"}

Normally it won't be longer than 1000 characters.  But in case you have lots of values in "scope" property, use 2000 for safe.



Photo of Dave Harrison 301

Dave Harrison 301

  • 152 Points 100 badge 2x thumb
Sure, but I mean I'll be storing each part separately in the database - token, expires_in, etc.  It would be good to have an official definition of each value individually.
Photo of Tyler Long

Tyler Long, Official Rep

  • 8,644 Points 5k badge 2x thumb
I don't think there is documentation about properties maximum length. But as far as I can tell, access_token and refresh_token length don't fluctuate too much.  So you can just use one real example to design your db schema.
(Edited)