7

How would I convert this to HttpClient? What I'm looking to do is submit a Tweet to the Twitter api and get the response as Json. The HttpWebRequest is working fine but I just want to port it to HttpClient. I made an attempt at it in the second code example, but it's not actually sending or receiving the response.

HttpWebRequest request = null;
WebResponse response = null;
string responseCode = String.Empty;
try
{
    string postBody = "status=" + EncodingUtils.UrlEncode(status);

    request = (HttpWebRequest)HttpWebRequest.Create(resource_url);        
    request.ServicePoint.Expect100Continue = true;
    request.UseDefaultCredentials = true;
    request.PreAuthenticate = true;
    request.Credentials = CredentialCache.DefaultCredentials;
    request.ServicePoint.ConnectionLimit = 1;
    request.Headers.Add("Authorization", authHeader);
    request.Method = "POST";
    request.ContentType = "application/x-www-form-urlencoded";

    using (Stream stream = request.GetRequestStream())
    {
        using (StreamWriter writer = new StreamWriter(stream))
        {                       
            writer.Write(postBody);
        }
    }
    using (response = request.GetResponse())
    {
        response.ContentType = "application/json";
        responseCode = ((HttpWebResponse)response).StatusCode.ToString();
    }
}
catch (WebException ex)
{
    if (ex.Status != WebExceptionStatus.NameResolutionFailure)
    {
        request.Abort();
        request = null;
    }
    throw ex;
}
return responseCode;

This is what I've tried to get it work:

private async Task<string> MakeWebRequest1(string status, string resource_url, string authHeader)
    {
        HttpClientHandler clientHandler = new HttpClientHandler();
        clientHandler.Credentials = CredentialCache.DefaultCredentials;
        clientHandler.PreAuthenticate = true;
        clientHandler.AllowAutoRedirect = true;
        string responseCode = "";
        string postBody = "status=" + EncodingUtils.UrlEncode(status);
        var request = new HttpRequestMessage()
        {
            RequestUri = new Uri(resource_url),
            Method = HttpMethod.Post,

        };
        request.Headers.Add("Authorization", authHeader);
       // request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
        request.Content = new StringContent(postBody, Encoding.UTF8,"application/x-www-form-urlencoded");//CONTENT-TYPE header

        using (HttpClient client = new HttpClient(clientHandler))
        {
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
            //  Stream stuff = await client.GetStreamAsync(resource_url);
            using (HttpResponseMessage response = await client.SendAsync(request))
            {
                response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                if(response.StatusCode == HttpStatusCode.OK)
                {
                    responseCode = "OK";
                }
            }
        }
        clientHandler.Dispose();
        return responseCode;
    }
enter code here

I've tried to add another parameter to the request and it's always coming back as 401 unauthorized. I'm trying to create a Twitter thread. If I remove the in_reply_to_status_id then it's fine.

data = new Dictionary<string, string> {
      ["status"] = "@username + status,
      ["in_reply_to_status_id"] = "1167588690929115136"
};

The Twitter API describes it here https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
Charles Owen
  • 2,403
  • 1
  • 14
  • 25

1 Answers1

17

Reference You're using HttpClient wrong to understand why a static client is being used.

static Lazy<HttpClient> client = new Lazy<HttpClient>(() => {
    HttpClientHandler clientHandler = new HttpClientHandler {
        Credentials = CredentialCache.DefaultCredentials,
        PreAuthenticate = true,
        AllowAutoRedirect = true
    };
    return new HttpClient(clientHandler);
});

private async Task<string> PostStatusRequestAsync(string status, string resource_url, string authHeader) {
    using (var request = new HttpRequestMessage(HttpMethod.Post, resource_url)) {
        request.Headers.TryAddWithoutValidation("Authorization", authHeader);
        request.Headers.Accept.Clear();
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        var data = new Dictionary<string, string> {
            ["status"] = status
        };

        request.Content = new FormUrlEncodedContent(data);

        using (HttpResponseMessage response = await client.Value.SendAsync(request)) {
            return response.StatusCode.ToString();
        }
    }
}

Note the use of the FormUrlEncodedContent for the request body, which will encode and concatenate the data as well as take care of the mime type header

...but it's not actually sending or receiving the response.

Ensure that the above is not invoked as a synchronous blocking call, like .Result, which could cause a deadlock.

For example, an async event handler can be used to make the async call

public async void onButtonClick(object sender, EventArgs args) {
    //Non-blocking call
    var tweetRequestCode = await PostStatusRequestAsync(TweetText, AuthUtils.GetResourceUrl(), AuthUtils.GetWebRequestHeader())); 

    //back on UI thread
    //...
}

Reference Async/Await - Best Practices in Asynchronous Programming

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Thanks that works. Is the proper way to call this, var t = Task.Run(() => MakeWebRequest1(TweetText, AuthUtils.GetResourceUrl(), AuthUtils.GetWebRequestHeader())); t.Wait(); tweetRequestCode = t.Result; – Charles Owen Aug 30 '19 at 00:30
  • 1
    @CharlesOwen No the `.Result` and `.Wait()` are blocking calls that can cause deadlocks. Because of the async nature of HttpClient, it should be awaited. – Nkosi Aug 30 '19 at 00:33
  • @CharlesOwen is that how you currently call it? – Nkosi Aug 30 '19 at 00:36
  • Because it blocks the UI. Use an async event handler instead. – Nkosi Aug 30 '19 at 00:38
  • @CharlesOwen provide more details/context about how and where the call is being made. Lets see if we can find a proper solution to making a safe non blocking call. – Nkosi Aug 30 '19 at 00:40
  • public async Task SubmitTweet(string TweetText) { string tweetRequestCode = String.Empty; try { AuthUtils.SetupAuthentication(TweetText); LogUtils.LogTransmissionStart(new WindowsPrincipal(WindowsIdentity.GetCurrent()).Identity.Name); tweetRequestCode = await MakeWebRequest1(TweetText, AuthUtils.GetResourceUrl(), AuthUtils.GetWebRequestHeader()); } – Charles Owen Aug 30 '19 at 01:05
  • @CharlesOwen I provided a simplified example in the answer. You should be able to follow a similar pattern with the code you just provided. – Nkosi Aug 30 '19 at 01:07
  • Yes my method now uses that syntax. Thanks! – Charles Owen Aug 30 '19 at 01:23