Http Post never returns from Twilio but Twilio action is performed

Viewed 29

I can't use the Twilio SDK in Microsoft Dynamics 365 (Twilio library is not installed in Dynamics and can't include the dll in my plugin registration) so I've had to do a http post using the HttpClient. The call to Twilio happens successfully because Twilio is able to send me an verification email but the breakpoint after PostAsync never gets hit, nor does an exception get caught. I need to capture the output from the PostAsync. What am I doing wrong?

    public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    public class TwilioMessageInput
    {
        public string To { get; set; }
        public string Channel { get; set; }
    }


    public class TwilioMessageOutput
    {
        public string Message { get; set; }
    }


    private void button1_Click(object sender, EventArgs e)
    {
        // https://www.twilio.com/docs/verify
        // https://www.twilio.com/docs/verify/email

        string url = "https://verify.twilio.com/v2/Services/VA********************************/Verifications/";
        string authToken = "AC********************************:********************************"; //-u $TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN
        string email = "***************@************.com";

        var formContent = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("To", email),
            new KeyValuePair<string, string>("Channel", "email")
        });

        using (var client = new Rest(url))
        {
            var response = client.PostAsync<TwilioMessageOutput>(url, formContent, authToken).Result;
        }
    }
}

public class Rest : IDisposable
{
    private readonly TimeSpan _timeout;
    private HttpClient _httpClient;
    private HttpClientHandler _httpClientHandler;
    private readonly string _baseUrl;
    private const string ClientUserAgent = "twillio-client-v1";
    private const string MediaTypeJson = "application/json";

    public Rest(string baseUrl, TimeSpan? timeout = null)
    {
        _baseUrl = NormalizeBaseUrl(baseUrl);
        _timeout = timeout ?? TimeSpan.FromSeconds(90);
        //_timeout = TimeSpan.FromSeconds(1);
    }

    private async Task<string> PostAsyncInternal(string url, FormUrlEncodedContent input, string authToken)
    {
        try
        {
            EnsureHttpClientCreated();

            var byteArray = Encoding.ASCII.GetBytes(authToken);
            _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));

            using (var response = await _httpClient.PostAsync(url, input))
            {
                response.EnsureSuccessStatusCode();
                return await response.Content.ReadAsStringAsync();
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }

    }

    public async Task<TResult> PostAsync<TResult>(string url, FormUrlEncodedContent input, string authToken) where TResult : class, new()
    {
        var strResponse = await PostAsyncInternal(url, input, authToken);

        return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        });
    }


    public void Dispose()
    {
        _httpClientHandler?.Dispose();
        _httpClient?.Dispose();
    }

    private void CreateHttpClient()
    {
        _httpClientHandler = new HttpClientHandler
        {
            AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
        };

        _httpClient = new HttpClient(_httpClientHandler, false)
        {
            Timeout = _timeout
        };

        _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(ClientUserAgent);

        if (!string.IsNullOrWhiteSpace(_baseUrl))
        {
            _httpClient.BaseAddress = new Uri(_baseUrl);
        }

        _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeJson));
    }

    private void EnsureHttpClientCreated()
    {
        if (_httpClient == null)
        {
            CreateHttpClient();
        }
    }

    private static string ConvertToJsonString(object obj)
    {
        if (obj == null)
        {
            return string.Empty;
        }

        return JsonConvert.SerializeObject(obj, new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        });
    }

    private static string NormalizeBaseUrl(string url)
    {
        return url.EndsWith("/") ? url : url + "/";
    }
}
1 Answers

Twilio developer evangelist here.

I'm not a C# or Dynamics developer, so sorry if this doesn't help. When you make the request:

var response = client.PostAsync<TwilioMessageOutput>(url, formContent, authToken).Result;

it is an asynchronous request, but you do not seem to be waiting for the asynchronous response at all. Should that be?

var response = await client.PostAsync<TwilioMessageOutput>(url, formContent, authToken).Result;
Related