Calling Twilio Voice API with Blazor

I am working on a project with blazor server and I am trying to make a voice call using Twilio.

I followed the documentation online from twilio for this to the letter but getting this error:

System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found).
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
   at QUBeMyGuest.Pages.GuestArrivals.EmergencyContact.GetClientToken() in C:PagesGuestArrivalsEmergencyContact.razor:line 98
   at QUBeMyGuest.Pages.GuestArrivals.EmergencyContact.OnAfterRenderAsync(Boolean firstRender) in C:Users\PagesGuestArrivalsEmergencyContact.razor:line 58
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)

The documentation stated that I should open a new asp.net core api project within my solution and add this class to the controller folder:

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Twilio.Jwt;
using Twilio.Jwt.Client;
using Twilio.TwiML;
using Twilio.Types;
using System.Net.Http;

namespace api.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TwilioBackEndController : ControllerBase
    {
        public readonly string AccountSid = "xxxxxxxxxx";
        public readonly string AuthToken = "xxxxxxxx";
        public readonly string AppSid = "xxxxxxxx";
        public readonly string PhoneNumber = "xxxxxxxx";

        [HttpGet("token")]
        public async Task<IActionResult> GetToken()
        {
            var scopes = new HashSet<IScope>
            {
                new OutgoingClientScope(AppSid),
                new IncomingClientScope("tester")
            };

            var capability = new ClientCapability(AccountSid, AuthToken, scopes: scopes);
            return await Task.FromResult(Content(capability.ToJwt(), "application/jwt"));
        }

        [HttpPost("voice")]
        public async Task<IActionResult> PostVoiceRequest([FromForm] string phone)
        {
            var destination = !phone.StartsWith('+') ? $"+{phone}" : phone;

            var response = new VoiceResponse();
            var dial = new Twilio.TwiML.Voice.Dial
            {
                CallerId = PhoneNumber
            };
            dial.Number(new PhoneNumber(destination));

            response.Append(dial);

            return await Task.FromResult(Content(response.ToString(), "application/xml"));
        }
    }
}

I then set up the page to make the call with my blazor project

@page "/guest/emergencycall"
@using System.ComponentModel.DataAnnotations
@inject HttpClient httpClient
@using Microsoft.Extensions.DependencyInjection
@using System.Net.Http


<EditForm Model="Input" OnValidSubmit="InitiatePhoneCall">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label for="phoneNumber">Enter Phone Number:</label>
        <InputText id="phoneNumber" @bind-Value="Input.PhoneNumber"></InputText>
        <button type="submit" class="btn btn-primary" disabled="@IsDialDisabled">DIAL</button>
        <button type="button" id="endBtn" class="btn btn-primary" disabled="@IsEndDisabled" @onclick="EndPhoneCall">END</button>
        <button type="button" id="clearBtn" class="btn btn-primary" disabled="@IsClearDisabled" @onclick="ClearPhoneNumber">CLEAR</button>
    </p>
</EditForm>

<hr />

@if (Logs.Count == 0)
{
    <p>No Logs available yet</p>
}
else
{
    <ul>
        @foreach (var log in Logs)
        {

            <li>@log</li>
        }
    </ul>
}

@code {
    private string _tokenUrl = "https://xxxxxxxxxxxxxxx";
    private bool appSetupRun = false;

    protected bool IsDialDisabled { get; set; } = false;
    protected bool IsEndDisabled { get { return !IsDialDisabled; } }

    protected bool IsClearDisabled { get { return string.IsNullOrEmpty(Input.PhoneNumber); } }
    protected List<string> Logs { get; set; } = new List<string>();

    protected InputModel Input { get; set; } = new InputModel();
    [Inject]
    protected IJSRuntime JSRuntime { get; set; }
    [Inject]
    protected IHttpClientFactory HttpClientFactory { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && !appSetupRun)
        {
            var token = await GetClientToken();
            await JSRuntime.InvokeVoidAsync("appFunctions.setup", token);
            appSetupRun = true;
        }
    }

    protected async Task InitiatePhoneCall()
    {
        IsDialDisabled = true;
        await LogMessage($"Calling the number {Input.PhoneNumber}");
        await JSRuntime.InvokeVoidAsync("appFunctions.placeCall", Input.PhoneNumber);
        await LogMessage($"Called the number {Input.PhoneNumber}");
        StateHasChanged();
    }

    protected async Task EndPhoneCall()
    {
        IsDialDisabled = false;
        await LogMessage($"Ending the call to {Input.PhoneNumber}");
        await JSRuntime.InvokeVoidAsync("appFunctions.endCall");
        await LogMessage($"Ended the call to {Input.PhoneNumber}");
        StateHasChanged();

    }

    protected async Task ClearPhoneNumber()
    {
        await LogMessage("Clearing the phone number entry");
        Input.PhoneNumber = string.Empty;
        await LogMessage("Cleared the phone number entry");
        StateHasChanged();
    }

    private async Task<string> GetClientToken()
    {
        var uri = new Uri(_tokenUrl);

        using var client = HttpClientFactory.CreateClient();
        var response = await client.GetAsync(uri);

        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }

    [JSInvokable]
    public async Task LogMessage(string message)
    {
        Logs.Add($"{DateTimeOffset.Now} - {message}");
        await Task.CompletedTask;
    }

    public class InputModel
    {
        [Required]
        [Phone(ErrorMessage = "Please enter your phone number in a proper format")]
        public string PhoneNumber { get; set; }
    }
}

and then added this JS function:

window.appFunctions = {
    setup: function (token) {
        console.log('Getting connected');

        // Setup Twilio Device
        Twilio.Device.setup(token);

        Twilio.Device.ready(() => {
            console.log('We are connected and ready to do the thing');
        });

        Twilio.Device.error((err) => {
            console.error('This should not have been reached. We need to do something here');
            console.error(err);
        });
    },
    placeCall: function (destination) {
        console.log(`Calling ${destination}`);
        Twilio.Device.connect({ phone: destination });
        console.log(`Successfully called ${destination}`);
    },
    endCall: function () {
        console.log('Ending the call');
        Twilio.Device.disconnectAll();
        console.log('Successfully ended the call');
    }
};

in my startup file I then added this, but doesn’t seem to have made a difference:

 services.AddHttpClient();

            if (!services.Any(x => x.ServiceType == typeof(HttpClient)))
            {
                services.AddScoped<HttpClient>(s =>
                {
                    var uriHelper = s.GetRequiredService<NavigationManager>();
                    return new HttpClient
                    {
                        BaseAddress = new Uri(uriHelper.BaseUri)
                    };
                });
            }

Any suggestions on what I could be doing wrong?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

When seeing a 404, the first thing that should come to mind is an incorrect URL. Since the 404 is coming from GetClientToken in your razor file. That means your _tokenUrl is probably incorrect. Looking at your controller, your URL should look something like https://{host}/token/. It’s possible that you hardcoded the link in the documentation, meanwhile, it’s supposed to be based on your controller.

PS: HTTP 404 means that the requested resource cannot be found. Get familiar with other HTTP response codes too, might save you a ton of debugging.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x