question

dave-harrison- avatar image
dave-harrison- asked ·

Process flow for RingOut on ASP.NET WebForms

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 Id 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 Ive lost the details about who Im 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 Im 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 isnt too strong and I cant 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.

 
topic-default
1 |1000 characters needed characters left characters exceeded

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

brandon-hein avatar image
brandon-hein answered ·
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.
Share
1 |1000 characters needed characters left characters exceeded

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

dave-harrison- avatar image
dave-harrison- answered ·

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

So Ive 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 lets 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 Im then redirected to my callback page Callback.aspx.  But now Ive 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 thats 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 customers phone number at CustomerDetail.aspx, but Ive lost it by Callback.aspx.  How can I retain it through that process?

Is there anything obvious Im 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 theres 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.

   
Share
1 |1000 characters needed characters left characters exceeded

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

brandon-hein avatar image
brandon-hein answered ·
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
Share
1 |1000 characters needed characters left characters exceeded

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Tyler Liu avatar image
Tyler Liu answered ·
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.
Share
1 |1000 characters needed characters left characters exceeded

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

dave-harrison- avatar image
dave-harrison- answered ·
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?
Share
1 |1000 characters needed characters left characters exceeded

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

dave-harrison- avatar image
dave-harrison- answered ·
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?
Share
1 |1000 characters needed characters left characters exceeded

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Tyler Liu avatar image
Tyler Liu answered ·
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
Share
1 |1000 characters needed characters left characters exceeded

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

dave-harrison- avatar image
dave-harrison- answered ·

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 wont be able to reference the users 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
6 comments Share
1 |1000 characters needed characters left characters exceeded

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

For your last question:  https://github.com/ringcentral/ringcentral-csharp-client/blob/master/RingCentral/TokenInfo.cs#L3

The class is a partial one. The commented out properties are defined somewhere else.
0 Likes 0 · ·
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.
0 Likes 0 · ·
Thanks, yes I will add that in, checking for a 401.

Any advice on maximum sizes for the various token values?
0 Likes 0 · ·
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.



0 Likes 0 · ·
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.
0 Likes 0 · ·
Show more comments

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 10 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.