Click To Call with C# and ASP.NET MVC

Wish your users could get in touch as easily as they can surf? It's your lucky day!

Let's go over the steps necessary to implement click-to-call in a C# and ASP.NET MVC application.

Click to Call

 

  1. A website visitor submits a web form with a phone number.
  2. Your web application receives the submission and initiates an HTTP request to Twilio asking to initiate an outbound call.
  3. Twilio receives the request and initiates a call to the user's phone number.
  4. The user picks up the call.
  5. After the call connects, we provide TwiML instructions to connect the user to our sales or support teams.

*Check out how iAdvize uses Twilio Click-to-call to connect online shoppers with customer support representatives.*

What We Will Learn

This tutorial demonstrates how to initialize a call using the Twilio REST API and how to create a call using the TwiML Say verb.

Loading Code Samples...
Language
using System.Configuration;
using System.Web.Mvc;
using ClickToCall.Web.Services;
using Twilio.AspNet.Mvc;
using Twilio.TwiML;

namespace ClickToCall.Web.Controllers
{
    public class CallController : TwilioController
    {
        private readonly IRequestValidationService _requestValidationService;

        public CallController() : this(new RequestValidationService())
        {
        }

        public CallController(IRequestValidationService requestValidationService)
        {
            _requestValidationService = requestValidationService;
        }

        [HttpPost]
        public ActionResult Connect(string salesNumber)
        {
            var twilioAuthToken = ConfigurationManager.AppSettings["TwilioAuthToken"];
            if (!_requestValidationService.IsValidRequest(System.Web.HttpContext.Current, twilioAuthToken))
            {
                return new HttpUnauthorizedResult();
            }

            var response = new VoiceResponse();
            response
                .Say("Thanks for contacting our sales department. Our " +
                     "next available representative will take your call.")
                .Dial(salesNumber)
                .Hangup();

            return TwiML(response);
        }
    }
}
ClickToCall.Web/Controllers/CallController.cs
The Call Controller

ClickToCall.Web/Controllers/CallController.cs

Let's get started! Click the button below to move to the next step of the tutorial.

Setup Our Environment

To get started you should create a Local.config file under the ClickToCall.Web/ directory with the following content:

  <?xml version="1.0" encoding="utf-8"?>
  <appSettings>
    <add key="TwilioAccountSID" value="your_account_SID" />
    <add key="TwilioAuthToken" value="your_twilio_auth_token" />
    <add key="TwilioNumber" value="your_twilio_number" />
  </appSettings>

You can find your Twilio credentials in the console.

Retrieve Your Twilio Credentials

For more instructions on how to run the application refer to the app's README.

Loading Code Samples...
Language
<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=301880
  -->
<configuration>
  <configSections>
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    
  <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --></configSections>
  <connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-ClickToCall.Web-20151030041424.mdf;Initial Catalog=aspnet-ClickToCall.Web-20151030041424;Integrated Security=True" providerName="System.Data.SqlClient" />
  </connectionStrings>
  <appSettings file="Local.config">
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="TwilioAccountSID" value="your_account_SID" />
    <add key="TwilioAuthToken" value="your_twilio_auth_token" />
    <add key="TwilioNumber" value="your_twilio_number" />
  </appSettings>
  <!--
    For a description of web.config changes see http://go.microsoft.com/fwlink/?LinkId=235367.

    The following attributes can be set on the <httpRuntime> tag.
      <system.Web>
        <httpRuntime targetFramework="4.5" />
      </system.Web>
  -->
  <system.web>
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.5.2" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <modules>
      <remove name="FormsAuthentication" />
    </modules>
  </system.webServer>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed" />
        <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.1.3.0" newVersion="5.1.3.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.IdentityModel.Tokens" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.1.3.0" newVersion="5.1.3.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="mssqllocaldb" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" />
      <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+" />
    </compilers>
  </system.codedom>
</configuration>
ClickToCall.Web/Web.config
Setup your environment

ClickToCall.Web/Web.config

Now let's look at how we build our click-to-call application's user facing form

Make a Nice Web Form

For our customers to contact support, we need to first build a web form where they can submit a phone number.

No need to overthink this, the real goal is to POST the user's and sales team's phone numbers to your controller.

What information does this form need?

  • An input for the phone number
  • An input for the sales team number
  • A submit button
Loading Code Samples...
Language
@{
    ViewBag.Title = "Click To Call";
}

<div class="jumbotron">
    <h1>Click To Call</h1>
    <p>
        Click To Call converts your website's users into engaged customers
        by creating an easy way for your customers to contact your sales and
        support teams right on your website.
    </p>
    <p>Here's an example of how it's done!</p>
    <hr>
    <div class="row">
        <div class="col-md-12">
            <form id="contactform" role="form" action="#" method="POST">
                <div class="form-group">
                    <h3>Call Sales</h3>
                    <p class="help-block">
                        Are you interested in impressing your friends and confounding your enemies?
                        Enter your phone number below, and our team will contact you right away.
                    </p>
                </div>
                <label>Your number</label>
                <div class="form-group">
                    <input class="form-control"
                           type="text"
                           name="userNumber"
                           id="userNumber"
                           min-lenght="4"
                           max-lenght="14"
                           placeholder="(651) 555-7889">
                </div>

                <label>Sales team number</label>
                <div class="form-group">
                    <input class="form-control"
                           type="text"
                           name="salesNumber"
                           id="salesNumber"
                           min-lenght="4"
                           max-lenght="14"
                           placeholder="(651) 555-7889">
                </div>
                <button type="submit" class="btn btn-default">Contact Sales</button>
            </form>
        </div>
    </div>
</div>
ClickToCall.Web/Views/CallCenter/Index.cshtml
User facing web form

ClickToCall.Web/Views/CallCenter/Index.cshtml

Since the page doesn't need to render new content after clicking, we decided to implement the POST action via AJAX using jQuery. Let's take a look at that next.

Submit the Form

To make the user's experience more pleasant, we used AJAX to send the form asynchronously.

This code shows one way you could implement this functionality using jQuery:

  • Watch for the user "submitting" the form element
  • Submit the form's data to our controller
  • Let the user know if the submission was successful or not

This is a common implementation of jQuery's jQuery.post() method. Notice that we are returning the response message when the call has connected.

Loading Code Samples...
Language
// Execute JavaScript on page load
$(function () {
    $("#userNumber, #salesNumber").intlTelInput({
        responsiveDropdown: true,
        autoFormat: true
    });
    var $form = $("#contactform"),
        $submit = $("#contactform input[type=submit]");

    // Intercept form submission
    $form.on("submit", function (e) {
        // Prevent form submission and repeat clicks
        e.preventDefault();
        $submit.attr("disabled", "disabled");

        // Submit the form via AJAX
        $.post("/CallCenter/Call", $form.serialize(), null, "json")
        .done(function (data) {
            alert(data.message);
            if (data.success) {
                $form[0].reset();
            }
        }).fail(function () {
            alert("There was a problem calling you - please try again later.");
        }).always(function () {
            $submit.removeAttr("disabled");
        });
    });
});
ClickToCall.Web/Scripts/app.js
Submit a form with Ajax

ClickToCall.Web/Scripts/app.js

Now that we have the front end done let's build the back end that will receive this form data and call the user.

Outbound Call Routing

Back on the server, we define a route that handles HTTP POST requests to the /Call URL.

This is the code that we're calling via our AJAX request in the browser. It will be responsible for placing the outbound call.

Before we place the call we first check that the POST data is valid, which requires that our CallViewModel has a user number and a sales number present.

Next, we'll use the REST API to make an outgoing phone call which requires we pass it a From number, a To number and the URL Parameter uriHandler that tells Twilio what to do after it connects the call to our customer.

Loading Code Samples...
Language
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Mvc;
using ClickToCall.Web.Models;
using ClickToCall.Web.Services;

namespace ClickToCall.Web.Controllers
{
    public class CallCenterController : Controller
    {
        private readonly INotificationService _notificationService;
        private const string OriginHeader = "Origin";

        public CallCenterController() : this(new NotificationService())
        {
        }

        public CallCenterController(INotificationService notificationService)
        {
            _notificationService = notificationService;
        }

        public ActionResult Index()
        {
            return View();
        }

        /// <summary>
        /// Handle a POST from our web form and connect a call via REST API
        /// </summary>
        [HttpPost]
        public async Task<ActionResult> Call(CallViewModel callViewModel)
        {
            if (!ModelState.IsValid)
            {
                var errors = ModelState.Values.SelectMany(m => m.Errors)
                    .Select(e => e.ErrorMessage)
                    .ToList();
                var errorMessage = string.Join(". ", errors);
                return Json(new { success = false, message = errorMessage });
            }

            var twilioNumber = ConfigurationManager.AppSettings["TwilioNumber"];
            var uriHandler = GetUri(callViewModel.SalesNumber);
            await _notificationService.MakePhoneCallAsync(callViewModel.UserNumber, twilioNumber, uriHandler);

            return Json(new { success = true, message = "Phone call incoming!"});
        }

        private string GetUri(string salesNumber)
        {
            if (IsProduction())
            {
                return Url.Action("Connect", "Call", new {salesNumber}, Request.Url.Scheme);
            }

            var urlAction = Url.Action("Connect", "Call", new {salesNumber});

            var origin = Request.Headers[OriginHeader];
            return $"{origin}{urlAction}";
        }

        private bool IsProduction()
        {
            var origin = Request.Headers[OriginHeader];
            return !new List<string> {"ngrok.io", "localhost"}.Any(domain => origin.Contains(domain));
        }
    }
}
ClickToCall.Web/Controllers/CallCenterController.cs
Call Center Controller

ClickToCall.Web/Controllers/CallCenterController.cs

Now let's look at how we prepare TwiML to send to Twilio.

The TwiML Generating Route

The first thing we do is validate that the incoming request is coming from Twilio. For this we'll use a Twilio Validation Service implemented by us using the Twilio .NET Helper Library's RequestValidator class.

TwiML is a set of verbs and nouns written in XML that Twilio reads as instructions. In this case our instructions inform Twilio to SAY something to the user and then DIAL the support agent's number so the customer can talk to him/her.

We use the Twilio .NET Helper Library to create a TwiML response.

Loading Code Samples...
Language
using System.Configuration;
using System.Web.Mvc;
using ClickToCall.Web.Services;
using Twilio.AspNet.Mvc;
using Twilio.TwiML;

namespace ClickToCall.Web.Controllers
{
    public class CallController : TwilioController
    {
        private readonly IRequestValidationService _requestValidationService;

        public CallController() : this(new RequestValidationService())
        {
        }

        public CallController(IRequestValidationService requestValidationService)
        {
            _requestValidationService = requestValidationService;
        }

        [HttpPost]
        public ActionResult Connect(string salesNumber)
        {
            var twilioAuthToken = ConfigurationManager.AppSettings["TwilioAuthToken"];
            if (!_requestValidationService.IsValidRequest(System.Web.HttpContext.Current, twilioAuthToken))
            {
                return new HttpUnauthorizedResult();
            }

            var response = new VoiceResponse();
            response
                .Say("Thanks for contacting our sales department. Our " +
                     "next available representative will take your call.")
                .Dial(salesNumber)
                .Hangup();

            return TwiML(response);
        }
    }
}
ClickToCall.Web/Controllers/CallController.cs
The Call Controller

ClickToCall.Web/Controllers/CallController.cs

And with that you've helped us get it working!  Now you should be prepared to bring it back to your own application.

In the next pane, we'll look at other features you might like to add to your application.

Where to Next?

If you're a .NET developer working with Twilio, you'll surely enjoy these other tutorials:

  • Appointment Reminders - automate the process of reaching out to your customers in advance of an upcoming appointment.

  • Automated Survey - instantly collect structured data from your customers with a survey conducted over a voice call or SMS text messages.

Did this help?

Thanks for checking out this tutorial! Tweet to us @twilio and let us know what you're building!

Paul Kamp
Hector Ortega
Andrew Baker
Jose Oliveros
Agustin Camino

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
using System.Configuration;
using System.Web.Mvc;
using ClickToCall.Web.Services;
using Twilio.AspNet.Mvc;
using Twilio.TwiML;

namespace ClickToCall.Web.Controllers
{
    public class CallController : TwilioController
    {
        private readonly IRequestValidationService _requestValidationService;

        public CallController() : this(new RequestValidationService())
        {
        }

        public CallController(IRequestValidationService requestValidationService)
        {
            _requestValidationService = requestValidationService;
        }

        [HttpPost]
        public ActionResult Connect(string salesNumber)
        {
            var twilioAuthToken = ConfigurationManager.AppSettings["TwilioAuthToken"];
            if (!_requestValidationService.IsValidRequest(System.Web.HttpContext.Current, twilioAuthToken))
            {
                return new HttpUnauthorizedResult();
            }

            var response = new VoiceResponse();
            response
                .Say("Thanks for contacting our sales department. Our " +
                     "next available representative will take your call.")
                .Dial(salesNumber)
                .Hangup();

            return TwiML(response);
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=301880
  -->
<configuration>
  <configSections>
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    
  <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --></configSections>
  <connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-ClickToCall.Web-20151030041424.mdf;Initial Catalog=aspnet-ClickToCall.Web-20151030041424;Integrated Security=True" providerName="System.Data.SqlClient" />
  </connectionStrings>
  <appSettings file="Local.config">
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="TwilioAccountSID" value="your_account_SID" />
    <add key="TwilioAuthToken" value="your_twilio_auth_token" />
    <add key="TwilioNumber" value="your_twilio_number" />
  </appSettings>
  <!--
    For a description of web.config changes see http://go.microsoft.com/fwlink/?LinkId=235367.

    The following attributes can be set on the <httpRuntime> tag.
      <system.Web>
        <httpRuntime targetFramework="4.5" />
      </system.Web>
  -->
  <system.web>
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.5.2" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <modules>
      <remove name="FormsAuthentication" />
    </modules>
  </system.webServer>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed" />
        <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.1.3.0" newVersion="5.1.3.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.IdentityModel.Tokens" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.1.3.0" newVersion="5.1.3.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="mssqllocaldb" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" />
      <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+" />
    </compilers>
  </system.codedom>
</configuration>
@{
    ViewBag.Title = "Click To Call";
}

<div class="jumbotron">
    <h1>Click To Call</h1>
    <p>
        Click To Call converts your website's users into engaged customers
        by creating an easy way for your customers to contact your sales and
        support teams right on your website.
    </p>
    <p>Here's an example of how it's done!</p>
    <hr>
    <div class="row">
        <div class="col-md-12">
            <form id="contactform" role="form" action="#" method="POST">
                <div class="form-group">
                    <h3>Call Sales</h3>
                    <p class="help-block">
                        Are you interested in impressing your friends and confounding your enemies?
                        Enter your phone number below, and our team will contact you right away.
                    </p>
                </div>
                <label>Your number</label>
                <div class="form-group">
                    <input class="form-control"
                           type="text"
                           name="userNumber"
                           id="userNumber"
                           min-lenght="4"
                           max-lenght="14"
                           placeholder="(651) 555-7889">
                </div>

                <label>Sales team number</label>
                <div class="form-group">
                    <input class="form-control"
                           type="text"
                           name="salesNumber"
                           id="salesNumber"
                           min-lenght="4"
                           max-lenght="14"
                           placeholder="(651) 555-7889">
                </div>
                <button type="submit" class="btn btn-default">Contact Sales</button>
            </form>
        </div>
    </div>
</div>
// Execute JavaScript on page load
$(function () {
    $("#userNumber, #salesNumber").intlTelInput({
        responsiveDropdown: true,
        autoFormat: true
    });
    var $form = $("#contactform"),
        $submit = $("#contactform input[type=submit]");

    // Intercept form submission
    $form.on("submit", function (e) {
        // Prevent form submission and repeat clicks
        e.preventDefault();
        $submit.attr("disabled", "disabled");

        // Submit the form via AJAX
        $.post("/CallCenter/Call", $form.serialize(), null, "json")
        .done(function (data) {
            alert(data.message);
            if (data.success) {
                $form[0].reset();
            }
        }).fail(function () {
            alert("There was a problem calling you - please try again later.");
        }).always(function () {
            $submit.removeAttr("disabled");
        });
    });
});
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Mvc;
using ClickToCall.Web.Models;
using ClickToCall.Web.Services;

namespace ClickToCall.Web.Controllers
{
    public class CallCenterController : Controller
    {
        private readonly INotificationService _notificationService;
        private const string OriginHeader = "Origin";

        public CallCenterController() : this(new NotificationService())
        {
        }

        public CallCenterController(INotificationService notificationService)
        {
            _notificationService = notificationService;
        }

        public ActionResult Index()
        {
            return View();
        }

        /// <summary>
        /// Handle a POST from our web form and connect a call via REST API
        /// </summary>
        [HttpPost]
        public async Task<ActionResult> Call(CallViewModel callViewModel)
        {
            if (!ModelState.IsValid)
            {
                var errors = ModelState.Values.SelectMany(m => m.Errors)
                    .Select(e => e.ErrorMessage)
                    .ToList();
                var errorMessage = string.Join(". ", errors);
                return Json(new { success = false, message = errorMessage });
            }

            var twilioNumber = ConfigurationManager.AppSettings["TwilioNumber"];
            var uriHandler = GetUri(callViewModel.SalesNumber);
            await _notificationService.MakePhoneCallAsync(callViewModel.UserNumber, twilioNumber, uriHandler);

            return Json(new { success = true, message = "Phone call incoming!"});
        }

        private string GetUri(string salesNumber)
        {
            if (IsProduction())
            {
                return Url.Action("Connect", "Call", new {salesNumber}, Request.Url.Scheme);
            }

            var urlAction = Url.Action("Connect", "Call", new {salesNumber});

            var origin = Request.Headers[OriginHeader];
            return $"{origin}{urlAction}";
        }

        private bool IsProduction()
        {
            var origin = Request.Headers[OriginHeader];
            return !new List<string> {"ngrok.io", "localhost"}.Any(domain => origin.Contains(domain));
        }
    }
}
using System.Configuration;
using System.Web.Mvc;
using ClickToCall.Web.Services;
using Twilio.AspNet.Mvc;
using Twilio.TwiML;

namespace ClickToCall.Web.Controllers
{
    public class CallController : TwilioController
    {
        private readonly IRequestValidationService _requestValidationService;

        public CallController() : this(new RequestValidationService())
        {
        }

        public CallController(IRequestValidationService requestValidationService)
        {
            _requestValidationService = requestValidationService;
        }

        [HttpPost]
        public ActionResult Connect(string salesNumber)
        {
            var twilioAuthToken = ConfigurationManager.AppSettings["TwilioAuthToken"];
            if (!_requestValidationService.IsValidRequest(System.Web.HttpContext.Current, twilioAuthToken))
            {
                return new HttpUnauthorizedResult();
            }

            var response = new VoiceResponse();
            response
                .Say("Thanks for contacting our sales department. Our " +
                     "next available representative will take your call.")
                .Dial(salesNumber)
                .Hangup();

            return TwiML(response);
        }
    }
}